From 39c96c07410689c95f02a847e8e0736b3d10d4bf Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Wed, 20 May 2026 22:17:49 +0200 Subject: [PATCH 01/56] Add flexible Node.js dependencies parser supporting CLI and JSON formats (#33) * feat: accept text format for sandbox nodeDependencies input Switch nodeDependencies from a JSON object editor to a textarea, matching the look-and-feel of pythonRequirementsTxt and envVars. Inputs are parsed as either npm CLI-style lines (one `package@version` per line, scoped packages and `latest`-default supported) or a JSON object for back-compat. Adds unit tests for parseNodeDependencies under tests/unit/ and updates AGENTS.md with guidance for adding future unit tests. https://claude.ai/code/session_011t31rBsLpeNZBwUUsPtFkV * chore: add commented example to nodeDependencies prefill Mirrors the style of initShellScript so the textarea hints at the expected line format on first open. https://claude.ai/code/session_011t31rBsLpeNZBwUUsPtFkV * docs: explain parseJsonObject coercion and graceful failure https://claude.ai/code/session_011t31rBsLpeNZBwUUsPtFkV --------- Co-authored-by: Claude --- sandbox/.actor/actor.json | 8 +- sandbox/AGENTS.md | 41 +++++- sandbox/README.md | 13 +- sandbox/src/main.ts | 7 +- sandbox/src/node-deps.ts | 90 +++++++++++++ sandbox/src/types.ts | 8 +- sandbox/tests/e2e.ts | 4 +- sandbox/tests/unit/node-deps.test.ts | 181 +++++++++++++++++++++++++++ 8 files changed, 333 insertions(+), 19 deletions(-) create mode 100644 sandbox/src/node-deps.ts create mode 100644 sandbox/tests/unit/node-deps.test.ts diff --git a/sandbox/.actor/actor.json b/sandbox/.actor/actor.json index 54806ff..afa4cff 100644 --- a/sandbox/.actor/actor.json +++ b/sandbox/.actor/actor.json @@ -29,10 +29,10 @@ }, "nodeDependencies": { "title": "Node.js dependencies", - "type": "object", - "description": "npm package.json dependencies object for JavaScript and TypeScript code execution (/sandbox/js-ts). Paste your dependencies in npm format. Example: {\"zod\": \"^3.0\", \"axios\": \"latest\"}", - "editor": "json", - "prefill": {} + "type": "string", + "description": "npm packages to install for JavaScript and TypeScript code execution (/sandbox/js-ts).\n\nAccepts either format:\n- **One `package@version` per line** (npm CLI style):\n ```\n zod@^3.0\n axios@latest\n lodash\n ```\n Lines without `@version` default to `latest`. Blank lines and `#` comments are ignored. Scoped packages like `@types/node@^20` are supported.\n- **JSON object** (package.json `dependencies` style):\n ```\n {\"zod\": \"^3.0\", \"axios\": \"latest\"}\n ```", + "editor": "textarea", + "prefill": "# One package@version per line. Omit @version for latest.\n# zod@^3.0\n# axios@latest\n# @types/node@^20" }, "pythonRequirementsTxt": { "title": "Python requirements", diff --git a/sandbox/AGENTS.md b/sandbox/AGENTS.md index 4e2596a..b0394fc 100644 --- a/sandbox/AGENTS.md +++ b/sandbox/AGENTS.md @@ -34,7 +34,8 @@ tsc -p tsconfig.build.json # Full build with output # Building is slow and unnecessary for development/testing - tsx runs TypeScript directly # Testing -npm test # Run tests (placeholder - no unit tests) +npm test # Run unit + e2e (full suite, local dev) +npm run test:unit # Run unit tests only (tests/unit/*.test.ts via node --test + tsx) npm run test:e2e # Run E2E platform tests (deploys to Apify) # Apify CLI @@ -45,11 +46,20 @@ apify push # Deploy to Apify platform ### Running a Single Test -Currently there are no unit tests. The E2E test suite deploys to Apify platform and tests all endpoints: +Unit tests live in `tests/unit/*.test.ts` and run via Node's built-in test runner with `tsx` as the loader (no extra deps). The Dockerfile runs `npm run test:unit` after `npm run build`, so a regression in pure-logic code fails the build. ```bash +# Run all unit tests +npm run test:unit + +# Run a single unit test file +node --import tsx --test tests/unit/node-deps.test.ts + # Run E2E platform tests (deploys Actor, runs tests, cleans up) npm run test:e2e + +# Run the full suite (unit + e2e) +npm test ``` ## Code Style Guidelines @@ -120,7 +130,32 @@ app.post('/endpoint', async (req: Request, res: Response) => { ### Testing - Tests in `tests/` directory with `.ts` extension, executable with `tsx` -- E2E platform test suite: `tests/e2e.ts` (deploys to Apify, runs 47 endpoint tests, cleans up) +- **Unit tests:** files matching `tests/unit/*.test.ts`, run via `npm run test:unit` (Node's built-in `node:test` runner via `node --import tsx`, no extra deps). Also run during the Docker build. +- **E2E platform test suite:** `tests/e2e.ts` (deploys to Apify, runs endpoint tests, cleans up) + +**When to add a unit test:** any new pure function (parser, validator, transform) — anything you can exercise without spinning up the Actor, Express, or external services. Prefer unit tests for I/O-free logic; reserve E2E for endpoint behavior. Bug fixes in pure logic should land with a regression test. + +**How to add a unit test:** + +1. Create `tests/unit/.test.ts` next to existing test files. +2. Use `node:test` (`describe`/`it`) and `node:assert/strict`: + + ```typescript + /* eslint-disable @typescript-eslint/no-floating-promises -- node:test's describe/it return promises by design */ + import assert from 'node:assert/strict'; + import { describe, it } from 'node:test'; + + import { myFn } from '../../src/my-module.js'; // .js extension required + + describe('myFn', () => { + it('does the thing', () => { + assert.deepEqual(myFn('input'), { ok: true }); + }); + }); + ``` + +3. Run `npm run test:unit` to verify. Test files are picked up by the `tests/unit/*.test.ts` glob — no further wiring needed. +4. Keep tests pure: no network, no filesystem writes, no `apify`/`Actor.init()`. If you need fixtures, inline them in the test file. ## What are Apify Actors? diff --git a/sandbox/README.md b/sandbox/README.md index a0ac5fc..fa68e16 100644 --- a/sandbox/README.md +++ b/sandbox/README.md @@ -371,9 +371,16 @@ The sandbox provides isolated execution environments for different code language Specify dependencies to install via Actor input: -- **Node.js Dependencies**: npm packages for JS/TS code execution in native npm format - - Input as a JSON object: `{"package-name": "version", ...}` - - Example: `{"zod": "^3.0", "axios": "latest", "lodash": "4.17.21"}` +- **Node.js Dependencies**: npm packages for JS/TS code execution. Accepts either format: + - **One `package@version` per line** (npm CLI style): + ``` + zod@^3.0 + axios@latest + lodash + @types/node@^20 + ``` + Lines without `@version` default to `latest`. Blank lines and `#` comments are ignored. + - **JSON object** (package.json `dependencies` style): `{"zod": "^3.0", "axios": "latest"}` - **Python Requirements**: pip packages for Python code execution in requirements.txt format - Input as multi-line text: one package per line with optional version specifiers - Example: diff --git a/sandbox/src/main.ts b/sandbox/src/main.ts index 7992433..c071fd3 100644 --- a/sandbox/src/main.ts +++ b/sandbox/src/main.ts @@ -15,6 +15,7 @@ import { SANDBOX_DIR } from './consts.js'; import { parseEnvVars } from './env-vars.js'; import { executeInitScript, setupExecutionEnvironment, setUserEnvVars } from './environment.js'; import { createMcpServer } from './mcp.js'; +import { parseNodeDependencies } from './node-deps.js'; import { appendFile, createDirectory, @@ -68,9 +69,11 @@ const input = await Actor.getInput(); const userEnvVars = parseEnvVars(input?.envVars); setUserEnvVars(userEnvVars); +const nodeDependencies = parseNodeDependencies(input?.nodeDependencies); + log.info('Actor input retrieved', { mode: isLocalMode ? 'local' : 'production', - hasNodeDependencies: !!input?.nodeDependencies && Object.keys(input.nodeDependencies).length > 0, + hasNodeDependencies: Object.keys(nodeDependencies).length > 0, hasPythonRequirements: !!input?.pythonRequirementsTxt?.trim().length, hasInitScript: !!input?.initShellScript?.trim().length, envVarKeys: Object.keys(userEnvVars), @@ -102,7 +105,7 @@ if (restoredFromMigration) { log.info('Setting up execution environment...'); setupResult = await setupExecutionEnvironment({ skills: input?.skills, - nodeDependencies: input?.nodeDependencies, + nodeDependencies, pythonRequirementsTxt: input?.pythonRequirementsTxt, }); } diff --git a/sandbox/src/node-deps.ts b/sandbox/src/node-deps.ts new file mode 100644 index 0000000..cc0fa83 --- /dev/null +++ b/sandbox/src/node-deps.ts @@ -0,0 +1,90 @@ +import { log } from 'apify'; + +/** + * Parse a `{ "package": "version" }` object. Coerces numeric versions to + * strings and null/empty values to `latest`; malformed JSON degrades to `{}` + * with a warning so a single bad character does not abort the run. + */ +const parseJsonObject = (raw: string): Record => { + let parsed: unknown; + try { + parsed = JSON.parse(raw); + } catch (error) { + const err = error as Error; + log.warning('nodeDependencies: failed to parse JSON input, ignoring', { error: err.message }); + return {}; + } + + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { + log.warning('nodeDependencies: JSON must be a flat object of package names to version strings'); + return {}; + } + + const out: Record = {}; + for (const [name, value] of Object.entries(parsed as Record)) { + const pkg = name.trim(); + if (!pkg) continue; + if (value === null || value === undefined || value === '') { + out[pkg] = 'latest'; + continue; + } + if (typeof value !== 'string' && typeof value !== 'number') { + log.warning('nodeDependencies: skipping non-string version', { package: pkg }); + continue; + } + out[pkg] = String(value); + } + return out; +}; + +/** + * Split a `package@version` line on the version separator. Returns `[name, version]`. + * + * Handles scoped packages (`@scope/name@version`) by splitting on the last `@`, + * not the first. A bare `package` (no `@version`) returns `version = 'latest'`. + */ +const splitSpec = (spec: string): [string, string] | null => { + const trimmed = spec.trim(); + if (!trimmed) return null; + + const isScoped = trimmed.startsWith('@'); + const versionAt = isScoped ? trimmed.indexOf('@', 1) : trimmed.indexOf('@'); + + if (versionAt < 0) return [trimmed, 'latest']; + + const name = trimmed.slice(0, versionAt).trim(); + const version = trimmed.slice(versionAt + 1).trim(); + if (!name) return null; + return [name, version || 'latest']; +}; + +const parseLines = (raw: string): Record => { + const out: Record = {}; + for (const rawLine of raw.split(/\r?\n/)) { + const line = rawLine.trim(); + if (!line || line.startsWith('#')) continue; + + const split = splitSpec(line); + if (!split) { + log.warning('nodeDependencies: skipping malformed line', { line: rawLine }); + continue; + } + out[split[0]] = split[1]; + } + return out; +}; + +/** + * Parse the user-supplied `nodeDependencies` input. Accepts either: + * - npm CLI-style lines: one `package@version` per line (`#` comments ignored, + * missing `@version` defaults to `latest`, scoped packages supported), or + * - a JSON object (input starts with `{`): `{ "package-name": "version", ... }`. + * + * Returns a `{ name: version }` object suitable for `installNodeLibraries`. + */ +export const parseNodeDependencies = (raw: string | undefined | null): Record => { + if (!raw) return {}; + const trimmed = raw.trim(); + if (!trimmed) return {}; + return trimmed.startsWith('{') ? parseJsonObject(trimmed) : parseLines(trimmed); +}; diff --git a/sandbox/src/types.ts b/sandbox/src/types.ts index 7fae7a9..79872d3 100644 --- a/sandbox/src/types.ts +++ b/sandbox/src/types.ts @@ -22,11 +22,11 @@ export interface ActorInput { skills?: string[]; /** - * Node.js dependencies object for JavaScript and TypeScript code execution - * Format: { "package-name": "version", ... } - * Example: { "zod": "^3.0", "axios": "latest" } + * Node.js dependencies for JavaScript and TypeScript code execution. + * Accepts either npm CLI-style lines (one `package@version` per line, missing + * `@version` defaults to `latest`) or a JSON object (`{ "pkg": "version" }`). */ - nodeDependencies?: Record; + nodeDependencies?: string; /** * Python requirements in requirements.txt format for Python code execution diff --git a/sandbox/tests/e2e.ts b/sandbox/tests/e2e.ts index dd69d62..d697f64 100644 --- a/sandbox/tests/e2e.ts +++ b/sandbox/tests/e2e.ts @@ -933,9 +933,7 @@ async function main(): Promise { console.log(`${colors.green}ℹ${colors.reset} Step 1: Preparing Actor input with dependencies...`); const input = { - nodeDependencies: { - zod: '^3.22.0', - }, + nodeDependencies: 'zod@^3.22.0', pythonRequirementsTxt: 'numpy>=1.24.0', envVars: 'TEST_E2E_SECRET=hunter2-do-not-leak', initShellScript: [ diff --git a/sandbox/tests/unit/node-deps.test.ts b/sandbox/tests/unit/node-deps.test.ts new file mode 100644 index 0000000..97ad0dd --- /dev/null +++ b/sandbox/tests/unit/node-deps.test.ts @@ -0,0 +1,181 @@ +/* eslint-disable @typescript-eslint/no-floating-promises -- node:test's describe/it return promises by design */ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; + +import { parseNodeDependencies } from '../../src/node-deps.js'; + +describe('parseNodeDependencies', () => { + describe('empty input', () => { + it('returns {} for undefined', () => { + assert.deepEqual(parseNodeDependencies(undefined), {}); + }); + + it('returns {} for null', () => { + assert.deepEqual(parseNodeDependencies(null), {}); + }); + + it('returns {} for empty string', () => { + assert.deepEqual(parseNodeDependencies(''), {}); + }); + + it('returns {} for whitespace-only input', () => { + assert.deepEqual(parseNodeDependencies(' \n \t \n'), {}); + }); + }); + + describe('npm CLI-style line format', () => { + it('parses a single package@version line', () => { + assert.deepEqual(parseNodeDependencies('zod@^3.0'), { zod: '^3.0' }); + }); + + it('parses multiple packages, one per line', () => { + const input = 'zod@^3.0\naxios@latest\nlodash@4.17.21'; + assert.deepEqual(parseNodeDependencies(input), { + zod: '^3.0', + axios: 'latest', + lodash: '4.17.21', + }); + }); + + it('defaults bare package names to latest', () => { + assert.deepEqual(parseNodeDependencies('lodash'), { lodash: 'latest' }); + }); + + it('defaults bare names to latest in mixed input', () => { + const input = 'zod@^3.0\nlodash\naxios@latest'; + assert.deepEqual(parseNodeDependencies(input), { + zod: '^3.0', + lodash: 'latest', + axios: 'latest', + }); + }); + + it('handles scoped packages without version', () => { + assert.deepEqual(parseNodeDependencies('@types/node'), { '@types/node': 'latest' }); + }); + + it('handles scoped packages with version (splits on last @)', () => { + assert.deepEqual(parseNodeDependencies('@types/node@^20'), { '@types/node': '^20' }); + }); + + it('handles mixed scoped and unscoped packages', () => { + const input = '@types/node@^20\nzod@^3.0\n@apify/sdk'; + assert.deepEqual(parseNodeDependencies(input), { + '@types/node': '^20', + zod: '^3.0', + '@apify/sdk': 'latest', + }); + }); + + it('ignores blank lines', () => { + const input = '\n\nzod@^3.0\n\n\naxios@latest\n\n'; + assert.deepEqual(parseNodeDependencies(input), { + zod: '^3.0', + axios: 'latest', + }); + }); + + it('ignores # comment lines', () => { + const input = '# my deps\nzod@^3.0\n# another comment\naxios@latest'; + assert.deepEqual(parseNodeDependencies(input), { + zod: '^3.0', + axios: 'latest', + }); + }); + + it('trims whitespace around package specs', () => { + const input = ' zod@^3.0 \n\t axios@latest\t'; + assert.deepEqual(parseNodeDependencies(input), { + zod: '^3.0', + axios: 'latest', + }); + }); + + it('handles \\r\\n line endings', () => { + assert.deepEqual(parseNodeDependencies('zod@^3.0\r\naxios@latest'), { + zod: '^3.0', + axios: 'latest', + }); + }); + + it('treats trailing @ as latest', () => { + assert.deepEqual(parseNodeDependencies('zod@'), { zod: 'latest' }); + }); + + it('lets later duplicate entries override earlier ones', () => { + assert.deepEqual(parseNodeDependencies('zod@^3.0\nzod@^4.0'), { zod: '^4.0' }); + }); + }); + + describe('JSON object format', () => { + it('parses a JSON object', () => { + const input = '{"zod": "^3.0", "axios": "latest"}'; + assert.deepEqual(parseNodeDependencies(input), { + zod: '^3.0', + axios: 'latest', + }); + }); + + it('parses pretty-printed JSON', () => { + const input = '{\n "zod": "^3.0",\n "axios": "latest"\n}'; + assert.deepEqual(parseNodeDependencies(input), { + zod: '^3.0', + axios: 'latest', + }); + }); + + it('parses JSON with leading whitespace', () => { + assert.deepEqual(parseNodeDependencies(' {"zod": "^3.0"} '), { zod: '^3.0' }); + }); + + it('coerces numeric versions to strings', () => { + const input = '{"lodash": 4}'; + assert.deepEqual(parseNodeDependencies(input), { lodash: '4' }); + }); + + it('treats null/empty values as latest', () => { + const input = '{"zod": null, "axios": ""}'; + assert.deepEqual(parseNodeDependencies(input), { + zod: 'latest', + axios: 'latest', + }); + }); + + it('skips object/array values', () => { + const input = '{"zod": "^3.0", "bad": {"nested": true}, "alsobad": [1, 2]}'; + assert.deepEqual(parseNodeDependencies(input), { zod: '^3.0' }); + }); + + it('returns {} for malformed JSON', () => { + assert.deepEqual(parseNodeDependencies('{not valid json'), {}); + }); + + it('returns {} for top-level JSON array (must be a flat object)', () => { + // Wrapped in `{}` so it routes to JSON parsing; bare `[...]` would be + // treated as line-format input by design. + assert.deepEqual(parseNodeDependencies('{"deps": ["zod", "axios"]}'), {}); + }); + + it('parses an empty JSON object as {}', () => { + assert.deepEqual(parseNodeDependencies('{}'), {}); + }); + + it('handles scoped packages in JSON', () => { + const input = '{"@types/node": "^20", "@apify/sdk": "latest"}'; + assert.deepEqual(parseNodeDependencies(input), { + '@types/node': '^20', + '@apify/sdk': 'latest', + }); + }); + }); + + describe('format detection', () => { + it('treats input starting with { as JSON', () => { + assert.deepEqual(parseNodeDependencies('{"zod": "^3.0"}'), { zod: '^3.0' }); + }); + + it('treats input not starting with { as line format', () => { + assert.deepEqual(parseNodeDependencies('zod@^3.0'), { zod: '^3.0' }); + }); + }); +}); From ba94181f765f0d099cfb64329e54e7b9a2b350dc Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Thu, 21 May 2026 00:07:11 +0200 Subject: [PATCH 02/56] Add mcpConnections input field and auto-generate /sandbox/mcp.json (#37) Adds a new `mcpConnections` array input (resourceType: mcpConnector, wildcard `*` server URL) that lets the user pick any MCP Connector they have authorized in Settings > API & Integrations. On sandbox start the Actor writes the chosen Connector IDs to /sandbox/mcp.json in the standard `mcpServers` shape so tools like `mcpc connect` can pick them up immediately. Authorization uses a `${APIFY_TOKEN}` placeholder so the secret never lands on disk. Co-authored-by: Claude --- sandbox/.actor/actor.json | 9 ++ sandbox/artifacts/AGENTS.md | 27 ++++++ sandbox/src/main.ts | 8 ++ sandbox/src/mcp-connections.ts | 102 +++++++++++++++++++++ sandbox/src/types.ts | 8 ++ sandbox/tests/unit/mcp-connections.test.ts | 86 +++++++++++++++++ 6 files changed, 240 insertions(+) create mode 100644 sandbox/src/mcp-connections.ts create mode 100644 sandbox/tests/unit/mcp-connections.test.ts diff --git a/sandbox/.actor/actor.json b/sandbox/.actor/actor.json index afa4cff..dea827a 100644 --- a/sandbox/.actor/actor.json +++ b/sandbox/.actor/actor.json @@ -86,6 +86,15 @@ }, "default": [], "prefill": [{"path": "/myapp", "target": "http://127.0.0.1:3000/myapp"}] + }, + "mcpConnections": { + "title": "MCP Connections", + "type": "array", + "description": "MCP Connectors the sandbox can call on your behalf — for example Slack, Notion, GitHub, or any other MCP server you have authorized in Settings > API & Integrations > MCP Connectors. At runtime the platform injects your credentials and exposes each Connector as a proxy at `${APIFY_MCP_PROXY_URL}/`. The sandbox writes the full list to `/sandbox/mcp.json` on startup so tools like `mcpc connect` can pick them up immediately.", + "resourceType": "mcpConnector", + "mcpServers": [{ "url": "*" }], + "editor": "resourcePicker", + "default": [] } }, diff --git a/sandbox/artifacts/AGENTS.md b/sandbox/artifacts/AGENTS.md index 2672416..1e4842e 100644 --- a/sandbox/artifacts/AGENTS.md +++ b/sandbox/artifacts/AGENTS.md @@ -144,6 +144,33 @@ mcpc @apify tools-list --json Access Apify platform features and thousands of Actors via `mcpc` tool. +### User-provided MCP Connections (`/sandbox/mcp.json`) + +If the user picked **MCP Connectors** in the Actor input (e.g. Slack, Notion, GitHub), the sandbox writes them to `/sandbox/mcp.json` on startup. Each entry is a ready-to-use HTTP MCP proxy, authenticated with the user's credentials server-side: + +```bash +cat /sandbox/mcp.json +# { +# "mcpServers": { +# "conn_abc123": { +# "url": "https://api.apify.com/v2/mcp-proxy/conn_abc123", +# "headers": { "Authorization": "Bearer ${APIFY_TOKEN}" } +# } +# } +# } +``` + +To connect with `mcpc`, read a server entry from the file and pass it to `mcpc connect`: + +```bash +# Connect to the first user-provided Connector as @user1 +URL=$(jq -r '.mcpServers | to_entries[0].value.url' /sandbox/mcp.json) +mcpc connect "$URL" @user1 --header "Authorization: Bearer $APIFY_TOKEN" +mcpc @user1 tools-list --json +``` + +If `/sandbox/mcp.json` has `"mcpServers": {}`, the user provided no Connectors — fall back to the Apify MCP server below. + ### 🚨 CRITICAL: Connect to Apify MCP Server First **Before using any MCP commands, you MUST create a named connection.** Direct URL connections are deprecated — always use named sessions with `@apify`. diff --git a/sandbox/src/main.ts b/sandbox/src/main.ts index c071fd3..f691e84 100644 --- a/sandbox/src/main.ts +++ b/sandbox/src/main.ts @@ -15,6 +15,7 @@ import { SANDBOX_DIR } from './consts.js'; import { parseEnvVars } from './env-vars.js'; import { executeInitScript, setupExecutionEnvironment, setUserEnvVars } from './environment.js'; import { createMcpServer } from './mcp.js'; +import { writeMcpConfig } from './mcp-connections.js'; import { parseNodeDependencies } from './node-deps.js'; import { appendFile, @@ -77,8 +78,15 @@ log.info('Actor input retrieved', { hasPythonRequirements: !!input?.pythonRequirementsTxt?.trim().length, hasInitScript: !!input?.initShellScript?.trim().length, envVarKeys: Object.keys(userEnvVars), + mcpConnectionCount: input?.mcpConnections?.length ?? 0, }); +// Write /sandbox/mcp.json with the configured MCP Connector proxies so +// tools like `mcpc connect` find them as soon as the shell opens. +if (!isLocalMode) { + writeMcpConfig(input?.mcpConnections); +} + // Check for migration state and restore if available let restoredFromMigration = false; if (!isLocalMode) { diff --git a/sandbox/src/mcp-connections.ts b/sandbox/src/mcp-connections.ts new file mode 100644 index 0000000..eeb5441 --- /dev/null +++ b/sandbox/src/mcp-connections.ts @@ -0,0 +1,102 @@ +/** + * MCP Connections Module + * + * Writes /sandbox/mcp.json on sandbox start so tools like `mcpc connect` + * can immediately find the MCP Connector proxies provided via Actor input. + * + * Each input value is a Connector ID (e.g. "conn_abc123"). At runtime the + * platform exposes the matching MCP server as a proxy at + * `${APIFY_MCP_PROXY_URL}/`, authenticated with `APIFY_TOKEN`. + */ + +/* eslint-disable no-template-curly-in-string -- ${APIFY_TOKEN} is a literal placeholder we write to mcp.json, not a JS template expression */ + +import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; +import { dirname } from 'node:path'; + +import { log } from 'apify'; + +import { SANDBOX_DIR } from './consts.js'; + +export const MCP_CONFIG_PATH = `${SANDBOX_DIR}/mcp.json`; + +export interface McpServerEntry { + url: string; + headers: Record; +} + +export interface McpConfig { + mcpServers: Record; +} + +/** + * Sanitize a Connector ID for use as a JSON object key. We keep the ID as the + * key when it's already safe to read in a shell (alphanumeric, `_`, `-`); if + * not, we fall back to a sanitized version so the file is still well-formed. + */ +const toServerKey = (id: string): string => { + if (/^[A-Za-z0-9_-]+$/.test(id)) return id; + return id.replace(/[^A-Za-z0-9_-]/g, '_'); +}; + +/** + * Build the MCP config object from a list of Connector IDs. + * Returns `{ mcpServers: {} }` when the input is empty/invalid so the file + * is still a valid, well-known shape downstream tools can read. + */ +export const buildMcpConfig = ( + connectionIds: string[] | undefined, + proxyUrl: string | undefined, +): McpConfig => { + const config: McpConfig = { mcpServers: {} }; + + if (!connectionIds || connectionIds.length === 0) return config; + + const base = (proxyUrl || '').replace(/\/+$/, ''); + + for (const raw of connectionIds) { + if (typeof raw !== 'string') continue; + const id = raw.trim(); + if (!id) continue; + + const key = toServerKey(id); + config.mcpServers[key] = { + url: `${base}/${id}`, + headers: { + Authorization: 'Bearer ${APIFY_TOKEN}', + }, + }; + } + + return config; +}; + +/** + * Write the MCP config to /sandbox/mcp.json. Always writes a file (even when + * the connection list is empty) so consumers can rely on its presence. + * Logs and swallows errors — a failed write must not abort sandbox startup. + */ +export const writeMcpConfig = (connectionIds: string[] | undefined): void => { + try { + const proxyUrl = process.env.APIFY_MCP_PROXY_URL; + const config = buildMcpConfig(connectionIds, proxyUrl); + + const dir = dirname(MCP_CONFIG_PATH); + if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); + + writeFileSync(MCP_CONFIG_PATH, `${JSON.stringify(config, null, 2)}\n`); + + const count = Object.keys(config.mcpServers).length; + log.info('Wrote MCP connections config', { path: MCP_CONFIG_PATH, count }); + if (count > 0 && !proxyUrl) { + log.warning( + 'APIFY_MCP_PROXY_URL is not set; mcp.json entries point to an empty proxy base URL', + ); + } + } catch (error) { + log.error('Failed to write MCP connections config', { + path: MCP_CONFIG_PATH, + error: (error as Error).message, + }); + } +}; diff --git a/sandbox/src/types.ts b/sandbox/src/types.ts index 79872d3..8ce8520 100644 --- a/sandbox/src/types.ts +++ b/sandbox/src/types.ts @@ -61,4 +61,12 @@ export interface ActorInput { * Example: [{ "path": "/openclaw", "target": "http://127.0.0.1:18789/openclaw" }] */ proxyMappings?: ProxyMapping[]; + + /** + * MCP Connector IDs the Actor can use. At runtime the platform exposes + * each Connector as a proxy at `${APIFY_MCP_PROXY_URL}/`, + * and the sandbox writes the list to `/sandbox/mcp.json` on startup + * so tools like `mcpc connect` can pick them up. + */ + mcpConnections?: string[]; } diff --git a/sandbox/tests/unit/mcp-connections.test.ts b/sandbox/tests/unit/mcp-connections.test.ts new file mode 100644 index 0000000..3c539c6 --- /dev/null +++ b/sandbox/tests/unit/mcp-connections.test.ts @@ -0,0 +1,86 @@ +/* eslint-disable @typescript-eslint/no-floating-promises -- node:test's describe/it return promises by design */ +/* eslint-disable no-template-curly-in-string -- ${APIFY_TOKEN} is a literal placeholder we write to mcp.json, not a JS template expression */ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; + +import { buildMcpConfig } from '../../src/mcp-connections.js'; + +const PROXY = 'https://api.apify.com/v2/mcp-proxy'; + +describe('buildMcpConfig', () => { + describe('empty / nullish input', () => { + it('returns an empty mcpServers map for undefined', () => { + assert.deepEqual(buildMcpConfig(undefined, PROXY), { mcpServers: {} }); + }); + + it('returns an empty mcpServers map for an empty array', () => { + assert.deepEqual(buildMcpConfig([], PROXY), { mcpServers: {} }); + }); + + it('skips blank / non-string entries', () => { + // @ts-expect-error - intentionally passing non-string to exercise runtime guard + const config = buildMcpConfig(['', ' ', null, 42, 'conn_ok'], PROXY); + assert.deepEqual(Object.keys(config.mcpServers), ['conn_ok']); + }); + }); + + describe('valid Connector IDs', () => { + it('builds one server entry per Connector ID', () => { + const config = buildMcpConfig(['conn_abc123', 'conn_def456'], PROXY); + assert.deepEqual(config, { + mcpServers: { + conn_abc123: { + url: `${PROXY}/conn_abc123`, + headers: { Authorization: 'Bearer ${APIFY_TOKEN}' }, + }, + conn_def456: { + url: `${PROXY}/conn_def456`, + headers: { Authorization: 'Bearer ${APIFY_TOKEN}' }, + }, + }, + }); + }); + + it('uses ${APIFY_TOKEN} placeholder in Authorization header', () => { + const config = buildMcpConfig(['conn_abc'], PROXY); + assert.equal(config.mcpServers.conn_abc.headers.Authorization, 'Bearer ${APIFY_TOKEN}'); + }); + + it('strips trailing slashes from the proxy base URL', () => { + const config = buildMcpConfig(['conn_abc'], `${PROXY}//`); + assert.equal(config.mcpServers.conn_abc.url, `${PROXY}/conn_abc`); + }); + + it('trims whitespace around Connector IDs', () => { + const config = buildMcpConfig([' conn_abc '], PROXY); + assert.deepEqual(Object.keys(config.mcpServers), ['conn_abc']); + assert.equal(config.mcpServers.conn_abc.url, `${PROXY}/conn_abc`); + }); + }); + + describe('proxy URL handling', () => { + it('falls back to an empty base when proxy URL is undefined', () => { + const config = buildMcpConfig(['conn_abc'], undefined); + assert.equal(config.mcpServers.conn_abc.url, '/conn_abc'); + }); + + it('falls back to an empty base when proxy URL is empty string', () => { + const config = buildMcpConfig(['conn_abc'], ''); + assert.equal(config.mcpServers.conn_abc.url, '/conn_abc'); + }); + }); + + describe('key sanitization', () => { + it('uses the raw ID as the key when it is safe', () => { + const config = buildMcpConfig(['Conn-123_abc'], PROXY); + assert.ok('Conn-123_abc' in config.mcpServers); + }); + + it('sanitizes IDs that contain unsafe characters', () => { + const config = buildMcpConfig(['conn@abc/xyz'], PROXY); + assert.ok('conn_abc_xyz' in config.mcpServers); + // URL preserves the original ID so the proxy can route correctly + assert.equal(config.mcpServers.conn_abc_xyz.url, `${PROXY}/conn@abc/xyz`); + }); + }); +}); From 1a98a4e77e468559d9c0b51126a032e3331ef619 Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Thu, 21 May 2026 16:13:57 +0200 Subject: [PATCH 03/56] Simplify ASCII art and enhance documentation links in shell (#36) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add actor page link to shell welcome banner and fix Venv alignment Surface the running actor's top-level URL in the shell welcome screen so users can easily navigate from /shell/ back to the landing page. Also realign the Venv row so it matches the column used by the other System Info entries. * Shrink ASCII banner and add run-details link to welcome screen Halve the Apify logo (27 → 14 lines, ~50 → 27 chars wide) so the welcome screen takes up less vertical space when opening the shell. Also add a Documentation entry pointing at the Apify console run view, derived from ACTOR_RUN_ID so users can jump to the run details page from inside the terminal. --------- Co-authored-by: Claude --- sandbox/src/templates/shell.ts | 49 +++++++++++++++------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/sandbox/src/templates/shell.ts b/sandbox/src/templates/shell.ts index 982f7cf..dfd3a1b 100644 --- a/sandbox/src/templates/shell.ts +++ b/sandbox/src/templates/shell.ts @@ -17,33 +17,20 @@ NC='\\033[0m' # No Color BOLD='\\033[1m' # Print ASCII Art -echo -e "\${GREEN} *+++++++++++++++++++++ \${BLUE}**********************\${NC}" -echo -e "\${GREEN} *++++++++++++++++++++ \${BLUE}*********************\${NC}" -echo -e "\${GREEN} *+++++++++++++++++++ \${BLUE}********************\${NC}" -echo -e "\${GREEN} *++++++++++++++++++ \${BLUE}*******************\${NC}" -echo -e "\${GREEN} *++++++++++++++++ \${BLUE}*****************\${NC}" -echo -e "\${GREEN} *+++++++++++++++ \${BLUE}****************\${NC}" -echo -e "\${GREEN} *++++++++++++++ \${BLUE}***************\${NC}" -echo -e "\${GREEN} *+++++++++++++ \${BLUE}**************\${NC}" -echo -e "\${GREEN} *++++++++++++ \${BLUE}*************\${NC}" -echo -e "\${GREEN} *+++++++++++ \${BLUE}************\${NC}" -echo -e "\${GREEN} *+++++++++ \${BLUE}**********\${NC}" -echo -e "\${GREEN} *++++++++ \${BLUE}*********\${NC}" -echo -e "\${GREEN} *+++++++ \${BLUE}********\${NC}" -echo -e " \${GREEN}*++++++ \${ORANGE}+\${BLUE} *******" -echo -e " \${GREEN}*+++++ \${ORANGE}++++\${BLUE} ******" -echo -e " \${GREEN}*+++ \${ORANGE}++++++++\${BLUE} ****" -echo -e " \${GREEN}*++ \${ORANGE}++++++++++++\${BLUE} ***" -echo -e " \${GREEN}++ \${ORANGE}+++++++++++++++\${BLUE} **" -echo -e "\${ORANGE} +++++++++++++++++++ " -echo -e " ++++++++++++++++++++++ " -echo -e " ++++++++++++++++++++++++++ " -echo -e " ++++++++++++++++++++++++++++++ " -echo -e " +++++++++++++++++++++++++++++++++ " -echo -e " ++++++++++++++++++++++++++++++++++++ " -echo -e " ++++++++++++++++++++++++++++++++++++++++ " -echo -e " ++++++++++++++++++++++++++++++++++++++++++++ " -echo -e " +++++++++++++++++++++++++++++++++++++++++++++++\${NC}" +echo -e "\${GREEN} *++++++++++++ \${BLUE}************\${NC}" +echo -e "\${GREEN} *+++++++++++ \${BLUE}***********\${NC}" +echo -e "\${GREEN} *++++++++++ \${BLUE}**********\${NC}" +echo -e "\${GREEN} *+++++++++ \${BLUE}*********\${NC}" +echo -e "\${GREEN} *++++++++ \${BLUE}********\${NC}" +echo -e "\${GREEN} *+++++++ \${BLUE}*******\${NC}" +echo -e " \${GREEN}*+++++ \${ORANGE}+\${BLUE} ******" +echo -e " \${GREEN}*+++ \${ORANGE}+++++\${BLUE} ****" +echo -e " \${GREEN}++ \${ORANGE}+++++++++\${BLUE} **" +echo -e "\${ORANGE} +++++++++++++" +echo -e " +++++++++++++++++" +echo -e " +++++++++++++++++++++" +echo -e " +++++++++++++++++++++++++" +echo -e " +++++++++++++++++++++++++++\${NC}" echo "" echo -e "\${BOLD}Welcome to Apify AI Sandbox!\${NC}" @@ -67,10 +54,16 @@ echo -e " - Claude: \$CLAUDE_VER" echo -e " - OpenCode: \$OPENCODE_VER" echo -e " - CWD: \$(pwd)" if [ -n "\$VIRTUAL_ENV" ]; then - echo -e " - Venv: Active (\$VIRTUAL_ENV)" + echo -e " - Venv: Active (\$VIRTUAL_ENV)" fi echo "" echo -e "\${BLUE}Documentation:\${NC}" +if [ -n "\$ACTOR_WEB_SERVER_URL" ]; then + echo -e " - Actor page: \$ACTOR_WEB_SERVER_URL" +fi +if [ -n "\$ACTOR_RUN_ID" ]; then + echo -e " - Run details: https://console.apify.com/view/runs/\$ACTOR_RUN_ID" +fi echo -e " - Homepage: https://apify.com/apify/ai-sandbox" echo -e " - Git repo: https://github.com/apify/actor-ai-sandbox" echo -e " - AI tools: Claude Code & OpenCode configured with Apify OpenRouter" From 330ebc084d3e22e5553372055661391e2e63e887 Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Tue, 26 May 2026 11:15:08 +0200 Subject: [PATCH 04/56] Align /llms.txt and / content (#38) * Align /llms.txt and / content Both endpoints now cover the same sections in the same order: Quick links, MCP, Code execution, Filesystem, Proxy Mappings, Response format, Working directories, Configuration. - llms.txt: add Proxy Mappings section; replace SDK code examples with curl commands matching the landing page - landing page: add collapsible Response format, Working directories, and Configuration cards https://claude.ai/code/session_01Ey8UdUPzR4ZbcLssnoZKis * Generate llms.txt from the landing page HTML Replace the standalone llms.md template with HTML-to-Markdown conversion of the rendered landing page. The landing page is now the single source of truth for the content of both endpoints. - Use node-html-markdown + node-html-parser to convert landing HTML to MD - Custom
 translator reads data-lang for fenced code language
- Strip script/style and elements marked data-no-md, plus copy/collapse
  buttons and status badge before conversion
- Hero h1 becomes "Apify AI Sandbox" (the eyebrow + "Landing page" h1 are
  collapsed into a single proper title)
- Quick Links card now contains a visible endpoint list rendered in both
  HTML and MD; action buttons are kept for HTML and marked data-no-md
- Card titles unified at h2 and example labels at h3 (with CSS to keep
  the previous visual sizing) so the heading hierarchy is consistent in MD
- Pre blocks have a data-lang attribute so fences render with proper
  language tags

https://claude.ai/code/session_01Ey8UdUPzR4ZbcLssnoZKis

---------

Co-authored-by: Claude 
---
 sandbox/package-lock.json         |  54 ++++++++++---
 sandbox/package.json              |   4 +-
 sandbox/src/templates/landing.ejs | 127 ++++++++++++++++++++---------
 sandbox/src/templates/landing.ts  |  30 ++++++-
 sandbox/src/templates/llms.md     | 130 ------------------------------
 5 files changed, 164 insertions(+), 181 deletions(-)
 delete mode 100644 sandbox/src/templates/llms.md

diff --git a/sandbox/package-lock.json b/sandbox/package-lock.json
index fc95af0..097765d 100644
--- a/sandbox/package-lock.json
+++ b/sandbox/package-lock.json
@@ -18,6 +18,8 @@
                 "express": "^5.2.1",
                 "http-proxy": "^1.18.1",
                 "mime-types": "^3.0.2",
+                "node-html-markdown": "^2.0.0",
+                "node-html-parser": "^7.1.0",
                 "zod": "^4.3.6"
             },
             "devDependencies": {
@@ -146,7 +148,6 @@
             "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
             "dev": true,
             "license": "MIT",
-            "peer": true,
             "dependencies": {
                 "@rtsao/scc": "^1.1.0",
                 "array-includes": "^3.1.9",
@@ -1496,7 +1497,6 @@
             "integrity": "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==",
             "dev": true,
             "license": "MIT",
-            "peer": true,
             "dependencies": {
                 "@typescript-eslint/scope-manager": "8.57.0",
                 "@typescript-eslint/types": "8.57.0",
@@ -1762,7 +1762,6 @@
             "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
             "dev": true,
             "license": "MIT",
-            "peer": true,
             "bin": {
                 "acorn": "bin/acorn"
             },
@@ -2281,7 +2280,6 @@
                 }
             ],
             "license": "MIT",
-            "peer": true,
             "dependencies": {
                 "baseline-browser-mapping": "^2.9.0",
                 "caniuse-lite": "^1.0.30001759",
@@ -3250,7 +3248,6 @@
             "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
             "dev": true,
             "license": "MIT",
-            "peer": true,
             "dependencies": {
                 "@eslint-community/eslint-utils": "^4.8.0",
                 "@eslint-community/regexpp": "^4.12.1",
@@ -3608,7 +3605,6 @@
             "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
             "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
             "license": "MIT",
-            "peer": true,
             "dependencies": {
                 "accepts": "^2.0.0",
                 "body-parser": "^2.2.1",
@@ -4423,6 +4419,15 @@
                 "node": ">= 0.4"
             }
         },
+        "node_modules/he": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+            "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+            "license": "MIT",
+            "bin": {
+                "he": "bin/he"
+            }
+        },
         "node_modules/header-generator": {
             "version": "2.1.78",
             "resolved": "https://registry.npmjs.org/header-generator/-/header-generator-2.1.78.tgz",
@@ -4443,7 +4448,6 @@
             "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.7.tgz",
             "integrity": "sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==",
             "license": "MIT",
-            "peer": true,
             "engines": {
                 "node": ">=16.9.0"
             }
@@ -5417,6 +5421,38 @@
                 "node": ">= 0.6"
             }
         },
+        "node_modules/node-html-markdown": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/node-html-markdown/-/node-html-markdown-2.0.0.tgz",
+            "integrity": "sha512-DqUC3GGP7pwSYxS93SwHoP+qCw78xcMP6C6H2DuC8rPD2AweJRjBzQb5SdXpKtDlqAQ7hVotJcfhgU7hU5Gthw==",
+            "license": "MIT",
+            "dependencies": {
+                "node-html-parser": "^6.1.13"
+            },
+            "engines": {
+                "node": ">=20.0.0"
+            }
+        },
+        "node_modules/node-html-markdown/node_modules/node-html-parser": {
+            "version": "6.1.13",
+            "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz",
+            "integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==",
+            "license": "MIT",
+            "dependencies": {
+                "css-select": "^5.1.0",
+                "he": "1.2.0"
+            }
+        },
+        "node_modules/node-html-parser": {
+            "version": "7.1.0",
+            "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-7.1.0.tgz",
+            "integrity": "sha512-iJo8b2uYGT40Y8BTyy5ufL6IVbN8rbm/1QK2xffXU/1a/v3AAa0d1YAoqBNYqaS4R/HajkWIpIfdE6KcyFh1AQ==",
+            "license": "MIT",
+            "dependencies": {
+                "css-select": "^5.1.0",
+                "he": "1.2.0"
+            }
+        },
         "node_modules/node-releases": {
             "version": "2.0.27",
             "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
@@ -5837,7 +5873,6 @@
             "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
             "dev": true,
             "license": "MIT",
-            "peer": true,
             "engines": {
                 "node": ">=12"
             },
@@ -7092,7 +7127,6 @@
             "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
             "dev": true,
             "license": "Apache-2.0",
-            "peer": true,
             "bin": {
                 "tsc": "bin/tsc",
                 "tsserver": "bin/tsserver"
@@ -7107,7 +7141,6 @@
             "integrity": "sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA==",
             "dev": true,
             "license": "MIT",
-            "peer": true,
             "dependencies": {
                 "@typescript-eslint/eslint-plugin": "8.57.0",
                 "@typescript-eslint/parser": "8.57.0",
@@ -7515,7 +7548,6 @@
             "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
             "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
             "license": "MIT",
-            "peer": true,
             "funding": {
                 "url": "https://github.com/sponsors/colinhacks"
             }
diff --git a/sandbox/package.json b/sandbox/package.json
index 2f16e69..d3f3bda 100644
--- a/sandbox/package.json
+++ b/sandbox/package.json
@@ -16,6 +16,8 @@
         "express": "^5.2.1",
         "http-proxy": "^1.18.1",
         "mime-types": "^3.0.2",
+        "node-html-markdown": "^2.0.0",
+        "node-html-parser": "^7.1.0",
         "zod": "^4.3.6"
     },
     "devDependencies": {
@@ -40,7 +42,7 @@
         "start:dev": "tsx src/main.ts",
         "start:standby": "APIFY_META_ORIGIN=\"STANDBY\" npm run start:dev",
         "build": "tsc -p tsconfig.build.json && npm run copy-templates",
-        "copy-templates": "mkdir -p dist/templates && cp src/templates/*.ejs src/templates/*.md src/templates/*.ico dist/templates/",
+        "copy-templates": "mkdir -p dist/templates && cp src/templates/*.ejs src/templates/*.ico dist/templates/",
         "lint": "eslint",
         "lint:fix": "eslint --fix",
         "format": "prettier --write .",
diff --git a/sandbox/src/templates/landing.ejs b/sandbox/src/templates/landing.ejs
index bce83e9..a19d589 100644
--- a/sandbox/src/templates/landing.ejs
+++ b/sandbox/src/templates/landing.ejs
@@ -87,8 +87,8 @@
         .copy-btn:active { transform: translateY(-50%) scale(0.98); }
         .copy-btn.copied { background: #22c55e; color: #0f172a; }
         .copy-btn.copied::before { content: "Copied!"; opacity: 1; }
-        .example-label { font-weight: 600; color: #cbd5e1; margin-top: 16px; margin-bottom: 4px; }
-        .example-label:first-child { margin-top: 0; }
+        h3.example-label { font-size: 14px; font-weight: 600; color: #cbd5e1; margin-top: 16px; margin-bottom: 4px; line-height: 1.4; }
+        h3.example-label:first-child { margin-top: 0; }
         
         /* Syntax highlighting */
         .hl-command { color: #60a5fa; }
@@ -154,11 +154,10 @@
     
-

Apify AI Sandbox

-

Landing page

-

Connect through HTTP, MCP, or the interactive shell.

+

Apify AI Sandbox

+

Containerized sandbox environment for AI coding operations. Connect through HTTP, MCP, or the interactive shell.

-
+
Checking... @@ -172,57 +171,63 @@

🔗 Quick links

- +
-

📡 Connect with MCP

-

URL

+

📡 Connect with MCP

+

URL

<%= serverUrl %>/mcp
-

Claude Code

+

Claude Code

-
claude mcp add --transport http sandbox <%= serverUrl %>/mcp
+
claude mcp add --transport http sandbox <%= serverUrl %>/mcp
-

⚡ Code execution

+

⚡ Code execution

-

Run bash command

+

Run bash command

-
curl -X POST <%= serverUrl %>/exec \
+                
curl -X POST <%= serverUrl %>/exec \
   -H "Content-Type: application/json" \
   -d '{"command": "ls -la", "language": "bash", "cwd": "/sandbox", "timeoutSecs": 5}'
-

Run Python code

+

Run Python code

-
curl -X POST <%= serverUrl %>/exec \
+                
curl -X POST <%= serverUrl %>/exec \
   -H "Content-Type: application/json" \
   -d '{"command": "print(\"hello\")", "language": "py", "timeoutSecs": 10}'
-

Run TypeScript code

+

Run TypeScript code

-
curl -X POST <%= serverUrl %>/exec \
+                
curl -X POST <%= serverUrl %>/exec \
   -H "Content-Type: application/json" \
   -d '{"command": "console.log(\"hello\")", "language": "ts", "timeoutSecs": 10}'
@@ -231,66 +236,66 @@
-

📁 Filesystem endpoints

+

📁 Filesystem endpoints

Direct file operations using HTTP methods. All paths relative to /sandbox.

-

Read file or list directory

+

Read file or list directory

-
curl <%= serverUrl %>/fs/app/log.txt
+
curl <%= serverUrl %>/fs/app/log.txt
-

Write or replace file

+

Write or replace file

-
curl -X PUT <%= serverUrl %>/fs/config.json \
+                
curl -X PUT <%= serverUrl %>/fs/config.json \
   -H "Content-Type: application/json" \
   -d '{"key": "value"}'
-

Create directory

+

Create directory

-
curl -X POST <%= serverUrl %>/fs/project/src?mkdir=1
+
curl -X POST <%= serverUrl %>/fs/project/src?mkdir=1
-

Append to file

+

Append to file

-
curl -X POST <%= serverUrl %>/fs/log.txt?append=1 \
+                
curl -X POST <%= serverUrl %>/fs/log.txt?append=1 \
   -d "New log entry"
-

Delete file or directory

+

Delete file or directory

-
curl -X DELETE <%= serverUrl %>/fs/temp?recursive=1
+
curl -X DELETE <%= serverUrl %>/fs/temp?recursive=1
-

Get file metadata

+

Get file metadata

-
curl -I <%= serverUrl %>/fs/data.json
+
curl -I <%= serverUrl %>/fs/data.json
-

🔀 Proxy Mappings

+

🔀 Proxy Mappings

Map local web servers to paths. Changes are applied immediately and persist across restarts.

-
+
-
+
Add Mapping
-

API Examples

+

API Examples

-
# Get current mappings
+                
# Get current mappings
 curl <%= serverUrl %>/proxy-config
 
 # Add a mapping
@@ -332,6 +337,56 @@
             
+ +
+
+

📋 Response format

+ +
+ +
+

All /exec requests return:

+
+ +
{
+    "stdout": "string",
+    "stderr": "string",
+    "exitCode": 0,
+    "language": "shell|js|ts|py"
+}
+
+
+
+ +
+
+

📂 Working directories

+ +
+ +
+
    +
  • Shell commands: /sandbox (default)
  • +
  • JavaScript/TypeScript: /sandbox/js-ts (default)
  • +
  • Python: /sandbox/py (default)
  • +
  • Override with cwd parameter (must be within /sandbox)
  • +
+
+
+ +
+
+

⚙️ Configuration

+ +
+ +
+
    +
  • Idle Timeout: The container automatically shuts down after inactivity (default 10m).
  • +
  • Execution Timeout: Recommended to set to 0 (infinite) on the platform; use the idleTimeoutSeconds input to control lifecycle.
  • +
+
+
+ + +`; +} diff --git a/sandbox/src/templates/landing.ejs b/sandbox/src/templates/landing.ejs index 099bb7c..18fdb19 100644 --- a/sandbox/src/templates/landing.ejs +++ b/sandbox/src/templates/landing.ejs @@ -175,6 +175,7 @@ Interactive shell Claude Code OpenCode + Browse files LLMs.txt @@ -182,6 +183,7 @@
  • Live shell (home): <%= serverUrl %>/
  • Docs page: <%= serverUrl %>/info
  • Shell terminal: <%= serverUrl %>/shell/
  • +
  • File browser: <%= serverUrl %>/browse
  • Health check: <%= serverUrl %>/health
  • MCP endpoint: <%= serverUrl %>/mcp
  • From c7c3ec6a8ab2381d5f2f745ce3dda4bc42d99984 Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Fri, 29 May 2026 23:31:19 +0200 Subject: [PATCH 11/56] Add Codex CLI, `?launch=` URL helper, and product rename (#45) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Rename inputs and product name, clarify nodeDependencies format - "Apify AI Sandbox" → "Apify AI Code Sandbox" across UI, READMEs, logs - Drop the eyebrow + "Landing page" stub; promote the product name to H1 - Input: pythonRequirementsTxt → pythonRequirements - Input: idleTimeoutSeconds → idleTimeoutSecs (sandbox + sub-actors) - nodeDependencies description: note we accept the same syntax as `npm install ` https://claude.ai/code/session_01Vd2LZY2wcnUKayh5anMsAe * Add /shell?launch=, rename mcpConnectors, polish landing page - /shell?launch= translates to ttyd's arg=-c, arg=source bashrc; for HTTP + WebSocket; works for arbitrary commands, not just agents - Sub-actor output URLs now use ?launch=claude / ?launch=opencode / ?launch=openclaw%20tui (instead of the long arg= form) - Quick links: replace the agent buttons with ?launch=, add an "Actor on Apify" link to https://apify.com/apify/ai-sandbox - Input: mcpConnections → mcpConnectors (matches the platform's "MCP Connectors" terminology) - Landing/llms: "Code execution" → "Code execution API", "Filesystem endpoints" → "Filesystem API", "Proxy Mappings" → "Proxy mappings", "Add Mapping" → "Add mapping" - Proxy mappings: explain what they're for (in landing copy and the actor.json input description) https://claude.ai/code/session_01Vd2LZY2wcnUKayh5anMsAe * Install agent CLIs via npm and add Codex Switch Claude Code and OpenCode from curl install scripts to npm-global, which gives the same binaries (Claude uses Node as launcher; OpenCode bundles Bun). Also add @openai/codex — a self-contained Rust binary delivered the same way. Single install mechanism, version-pinnable, no more curl pipelines. Welcome banner and the build-time version capture now include Codex; the landing page Quick links gain a Codex button. https://claude.ai/code/session_01Vd2LZY2wcnUKayh5anMsAe * Add codex sub-actor Mirrors the claude-code / opencode metamorph pattern: a thin wrapper that metamorphs into the main AI Code Sandbox and lands the user in a terminal running codex (output URL: /shell?launch=codex). Codex CLI itself is already installed in the main sandbox image. https://claude.ai/code/session_01Vd2LZY2wcnUKayh5anMsAe * Revert sub-actor changes; defer to a follow-up PR Drops the claude-code/ and opencode/ updates and the new codex/ sub-actor from this PR so they can be redone as a focused follow-up. The /shell?launch= plumbing in the main sandbox stays, so the new URLs will Just Work once the sub-actors are updated separately. https://claude.ai/code/session_01Vd2LZY2wcnUKayh5anMsAe * Revert openclaw changes; defer to a follow-up PR --------- Co-authored-by: Claude --- README.md | 2 +- sandbox/.actor/actor.json | 18 ++++++------- sandbox/AGENTS.md | 4 +-- sandbox/Dockerfile | 13 +++++---- sandbox/README.md | 4 +-- sandbox/artifacts/AGENTS.md | 4 +-- sandbox/package.json | 2 +- sandbox/scripts/capture-versions.sh | 8 ++++++ sandbox/src/environment.ts | 4 +-- sandbox/src/main.ts | 17 +++++++----- sandbox/src/shell-launch.ts | 32 ++++++++++++++++++++++ sandbox/src/templates/landing.ejs | 18 +++++++------ sandbox/src/templates/shell.ts | 4 ++- sandbox/src/types.ts | 8 +++--- sandbox/tests/e2e.ts | 8 +++--- sandbox/tests/unit/shell-launch.test.ts | 36 +++++++++++++++++++++++++ 16 files changed, 134 insertions(+), 48 deletions(-) create mode 100644 sandbox/src/shell-launch.ts create mode 100644 sandbox/tests/unit/shell-launch.test.ts diff --git a/README.md b/README.md index 813182c..ad919cf 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 🧪 Apify AI Sandbox +# 🧪 Apify AI Code Sandbox A suite of Apify Actors providing secure, containerized environments for AI coding agents. diff --git a/sandbox/.actor/actor.json b/sandbox/.actor/actor.json index ceba8c6..561c87e 100644 --- a/sandbox/.actor/actor.json +++ b/sandbox/.actor/actor.json @@ -1,8 +1,8 @@ { "actorSpecification": 1, "name": "apify-ai-sandbox", - "title": "Apify AI Sandbox", - "description": "Apify AI Sandbox Actor that allows execution of code and operations in a containerized environment for AI coding agents", + "title": "Apify AI Code Sandbox", + "description": "Apify AI Code Sandbox Actor that allows execution of code and operations in a containerized environment for AI coding agents", "version": "0.0", "buildTag": "latest", "usesStandbyMode": false, @@ -27,11 +27,11 @@ "nodeDependencies": { "title": "Node.js dependencies", "type": "string", - "description": "npm packages for JavaScript/TypeScript code execution (/sandbox/js-ts). One `package@version` per line (omit `@version` for `latest`; blank lines and `#` comments are ignored), or a JSON object like `{\"zod\": \"^3.0\", \"axios\": \"latest\"}`.", + "description": "npm packages for JavaScript/TypeScript code execution (/sandbox/js-ts). One `package@version` per line — same syntax `npm install ` accepts (omit `@version` for `latest`; blank lines and `#` comments are ignored). Also accepts a JSON object like `{\"zod\": \"^3.0\", \"axios\": \"latest\"}`.", "editor": "textarea", "prefill": "# One package@version per line (omit @version for latest):\n# zod@^3.0\n# axios" }, - "pythonRequirementsTxt": { + "pythonRequirements": { "title": "Python requirements", "type": "string", "description": "Python packages for Python code execution (/sandbox/py), in requirements.txt format — one package per line.", @@ -52,7 +52,7 @@ "editor": "textarea", "isSecret": true }, - "idleTimeoutSeconds": { + "idleTimeoutSecs": { "title": "Idle timeout seconds", "type": "integer", "description": "The container shuts down automatically after this many seconds of inactivity (no requests or shell interaction). Set to 0 to disable. Recommended: set the Actor timeout to 0 (infinite) when using this.", @@ -63,7 +63,7 @@ "proxyMappings": { "title": "Proxy mappings", "type": "array", - "description": "Map external paths to local web servers. Example: [{\"path\": \"/myapp\", \"target\": \"http://127.0.0.1:3000/myapp\"}]", + "description": "Expose web servers that you start **inside** the sandbox at a public URL path on this container, so they're reachable from the browser or other services. Each mapping forwards `/` to `http://127.0.0.1:/...` over HTTP and WebSocket. Use for your own dev server, a TUI gateway, an admin UI, etc.\n\nExample: `[{\"path\": \"/myapp\", \"target\": \"http://127.0.0.1:3000/myapp\"}]` — visiting `/myapp` proxies to the local server on port 3000.\n\nMappings can also be added/removed at runtime via the `/proxy-config` API.", "editor": "json", "items": { "type": "object", @@ -84,8 +84,8 @@ "default": [], "prefill": [{"path": "/myapp", "target": "http://127.0.0.1:3000/myapp"}] }, - "mcpConnections": { - "title": "MCP Connections", + "mcpConnectors": { + "title": "MCP Connectors", "type": "array", "description": "MCP Connectors the sandbox can call on your behalf (Slack, Notion, GitHub, or any MCP server authorized in Settings > API & Integrations > MCP Connectors). At runtime the platform injects your credentials, exposes each as a proxy at `${APIFY_MCP_PROXY_URL}/`, and writes the list to `/sandbox/mcp.json` so tools like `mcpc connect` pick them up immediately.", "resourceType": "mcpConnector", @@ -100,7 +100,7 @@ "output": { "actorOutputSchemaVersion": 1, - "title": "Apify AI Sandbox", + "title": "Apify AI Code Sandbox", "description": "", "type": "object", diff --git a/sandbox/AGENTS.md b/sandbox/AGENTS.md index b0394fc..e97870d 100644 --- a/sandbox/AGENTS.md +++ b/sandbox/AGENTS.md @@ -1,6 +1,6 @@ -# Apify AI Sandbox - Development Guide +# Apify AI Code Sandbox - Development Guide -This guide is for AI coding agents working on the Apify AI Sandbox Actor codebase. +This guide is for AI coding agents working on the Apify AI Code Sandbox Actor codebase. Important: Before you begin, fill in the `generatedBy` property in the meta section of `.actor/actor.json`. Replace it with the model you're currently using. This helps Apify monitor and improve AGENTS.md for specific LLM models. diff --git a/sandbox/Dockerfile b/sandbox/Dockerfile index a07b082..cd735f1 100644 --- a/sandbox/Dockerfile +++ b/sandbox/Dockerfile @@ -67,11 +67,14 @@ RUN npm install -g --ignore-scripts apify-cli # Install Apify MCP CLI globally (package: @apify/mcpc, binary: mcpc) RUN npm install -g @apify/mcpc -# Install Claude Code CLI -RUN curl -fsSL https://claude.ai/install.sh | bash - -# Install OpenCode CLI -RUN curl -fsSL https://opencode.ai/install | bash +# Install AI coding agent CLIs via npm. All three ship platform-specific +# native binaries through optional deps; --include=optional ensures the +# Linux binary is fetched. Claude Code uses Node as a launcher; Codex and +# OpenCode bundle their own runtimes (Rust / Bun) inside the binary. +RUN npm install -g --include=optional \ + @anthropic-ai/claude-code \ + @openai/codex \ + opencode-ai # Create sandbox directory for operations RUN mkdir -p /sandbox && chmod 755 /sandbox diff --git a/sandbox/README.md b/sandbox/README.md index 5c155db..936370a 100644 --- a/sandbox/README.md +++ b/sandbox/README.md @@ -1,4 +1,4 @@ -# Apify AI Sandbox +# Apify AI Code Sandbox Isolated sandbox for running AI coding operations in a containerized environment. 🚀 @@ -340,7 +340,7 @@ Mappings can also be modified by writing JSON to `/sandbox/.proxy-mappings.json` ## Configuration - **Memory & timeout:** Configure run options to set memory allocation and execution timeout -- **Idle timeout:** The container automatically shuts down after a period of inactivity (default: 15 minutes). Activity includes HTTP requests and shell interaction. You can adjust this via the `idleTimeoutSeconds` input. +- **Idle timeout:** The container automatically shuts down after a period of inactivity (default: 15 minutes). Activity includes HTTP requests and shell interaction. You can adjust this via the `idleTimeoutSecs` input. - **Recommendation:** For cost efficiency, set the standard Actor **Execution Timeout to 0 (infinite)** in the Apify Console. The internal idle logic will then manage the lifecycle based on your usage. - **Request timeout:** All requests to the Actor have a 5-minute timeout ceiling. All operations (code execution, commands, file operations) must complete within this time limit. The `timeout` parameter in requests cannot exceed this 5-minute window - **Check logs:** Open the Actor run log console to view connection details and operation output diff --git a/sandbox/artifacts/AGENTS.md b/sandbox/artifacts/AGENTS.md index 1e4842e..195cc91 100644 --- a/sandbox/artifacts/AGENTS.md +++ b/sandbox/artifacts/AGENTS.md @@ -1,6 +1,6 @@ -# Agent Instructions for Apify AI Sandbox +# Agent Instructions for Apify AI Code Sandbox -This document contains instructions for AI coding agents working inside the Apify AI Sandbox Actor. +This document contains instructions for AI coding agents working inside the Apify AI Code Sandbox Actor. ## Sharing Files and Data with Users diff --git a/sandbox/package.json b/sandbox/package.json index 2424e5d..07c3f9a 100644 --- a/sandbox/package.json +++ b/sandbox/package.json @@ -2,7 +2,7 @@ "name": "apify-ai-sandbox", "version": "0.0.1", "type": "module", - "description": "Apify AI Sandbox for running AI coding operations in a containerized environment.", + "description": "Apify AI Code Sandbox for running AI coding operations in a containerized environment.", "engines": { "node": ">=20.0.0" }, diff --git a/sandbox/scripts/capture-versions.sh b/sandbox/scripts/capture-versions.sh index 639239e..4425864 100644 --- a/sandbox/scripts/capture-versions.sh +++ b/sandbox/scripts/capture-versions.sh @@ -58,6 +58,14 @@ else echo "⚠️ OpenCode: not installed" fi +# Capture Codex CLI version (optional) +if codex --version > "$VERSION_DIR/codex.txt" 2>/dev/null; then + echo "✅ Codex: $(cat "$VERSION_DIR/codex.txt")" +else + echo "not installed" > "$VERSION_DIR/codex.txt" + echo "⚠️ Codex: not installed" +fi + echo "" echo "🎉 Version capture complete! Files stored in $VERSION_DIR" echo " This will make shell startup 30-120x faster!" diff --git a/sandbox/src/environment.ts b/sandbox/src/environment.ts index 4d2216b..191edaf 100644 --- a/sandbox/src/environment.ts +++ b/sandbox/src/environment.ts @@ -310,7 +310,7 @@ export const installSkills = async ( export const setupExecutionEnvironment = async (input: { skills?: string[]; nodeDependencies?: Record; - pythonRequirementsTxt?: string; + pythonRequirements?: string; }): Promise<{ success: boolean; skillsSetup: { success: boolean; installed: string[]; failed: { skill: string; error: string }[] }; @@ -349,7 +349,7 @@ export const setupExecutionEnvironment = async (input: { const [skillsSetup, nodeSetup, pythonSetup] = await Promise.all([ installSkills(input.skills), installNodeLibraries(input.nodeDependencies), - installPythonLibraries(input.pythonRequirementsTxt), + installPythonLibraries(input.pythonRequirements), ]); const success = errors.length === 0 && skillsSetup.success && nodeSetup.success && pythonSetup.success; diff --git a/sandbox/src/main.ts b/sandbox/src/main.ts index b273454..c3dbb39 100644 --- a/sandbox/src/main.ts +++ b/sandbox/src/main.ts @@ -14,6 +14,7 @@ import { parseEnvVars } from './env-vars.js'; import { executeInitScript, setupExecutionEnvironment, setUserEnvVars } from './environment.js'; import { createMcpServer } from './mcp.js'; import { writeMcpConfig } from './mcp-connections.js'; +import { translateLaunchParam } from './shell-launch.js'; import { parseNodeDependencies } from './node-deps.js'; import { appendFile, @@ -79,16 +80,16 @@ log.info('Actor input retrieved', { mode: isLocalMode ? 'local' : 'production', hasSkills: skills.length > 0, hasNodeDependencies: Object.keys(nodeDependencies).length > 0, - hasPythonRequirements: !!input?.pythonRequirementsTxt?.trim().length, + hasPythonRequirements: !!input?.pythonRequirements?.trim().length, hasInitScript: !!input?.initShellScript?.trim().length, envVarKeys: Object.keys(userEnvVars), - mcpConnectionCount: input?.mcpConnections?.length ?? 0, + mcpConnectorCount: input?.mcpConnectors?.length ?? 0, }); // Write /sandbox/mcp.json with the configured MCP Connector proxies so // tools like `mcpc connect` find them as soon as the shell opens. if (!isLocalMode) { - writeMcpConfig(input?.mcpConnections); + writeMcpConfig(input?.mcpConnectors); } // Check for migration state and restore if available @@ -118,7 +119,7 @@ if (restoredFromMigration) { setupResult = await setupExecutionEnvironment({ skills, nodeDependencies, - pythonRequirementsTxt: input?.pythonRequirementsTxt, + pythonRequirements: input?.pythonRequirements, }); } @@ -957,6 +958,7 @@ app.all('/shell{*rest}', (req, res) => { if (path.startsWith('?')) { path = `/${ path}`; } + path = translateLaunchParam(path); const options = { hostname: '127.0.0.1', port: shellPort, @@ -991,6 +993,7 @@ const wsProxy = httpProxy.createProxyServer({ server.on('upgrade', (req, socket, head) => { if (req.url?.startsWith('/shell')) { req.url = req.url.replace(/^\/shell/, '') || '/'; + req.url = translateLaunchParam(req.url); log.info('Proxying shell WebSocket upgrade', { url: req.url }); // Track activity on WebSocket data @@ -1235,12 +1238,12 @@ app.use((req: Request, res: Response, next) => { // Start server server.listen(port, () => { - log.info(`Apify AI Sandbox listening on port ${port}`); + log.info(`Apify AI Code Sandbox listening on port ${port}`); log.info(`Server URL: ${serverUrl}`); // Print startup information console.log('\n====================================='); - console.log('🚀 Apify AI Sandbox Started'); + console.log('🚀 Apify AI Code Sandbox Started'); console.log('=====================================\n'); console.log('🖥️ Live shell (shown in the run Live View):'); @@ -1301,7 +1304,7 @@ server.listen(port, () => { console.log('=====================================\n'); // Start idle timeout check - const idleTimeoutSecs = input?.idleTimeoutSeconds ?? 900; + const idleTimeoutSecs = input?.idleTimeoutSecs ?? 900; if (idleTimeoutSecs > 0) { log.info(`Idle timeout monitor started (${idleTimeoutSecs}s)`); setInterval(async () => { diff --git a/sandbox/src/shell-launch.ts b/sandbox/src/shell-launch.ts new file mode 100644 index 0000000..a588986 --- /dev/null +++ b/sandbox/src/shell-launch.ts @@ -0,0 +1,32 @@ +/** + * Translate `?launch=` on a /shell URL into the `?arg=-c&arg=...` form + * ttyd expects. `?launch=` is a convenience: bash invoked with `-c` does not + * source rcfiles, so we explicitly prepend `source /app/sandbox_bashrc;` to + * ensure the launched command sees the same env as an interactive shell. + * + * Idempotent: if `launch` is absent, the path is returned unchanged. Other + * query params are preserved in order. + */ +const BASHRC_SOURCE = 'source /app/sandbox_bashrc;'; + +export const translateLaunchParam = (path: string): string => { + const queryIdx = path.indexOf('?'); + if (queryIdx < 0) return path; + + const basePath = path.slice(0, queryIdx); + const params = new URLSearchParams(path.slice(queryIdx + 1)); + const launch = params.get('launch'); + if (launch === null) return path; + + params.delete('launch'); + const cmd = launch.trim() ? `${BASHRC_SOURCE} ${launch}` : BASHRC_SOURCE; + + const parts: string[] = [ + `arg=${encodeURIComponent('-c')}`, + `arg=${encodeURIComponent(cmd)}`, + ]; + const rest = params.toString(); + if (rest) parts.push(rest); + + return `${basePath}?${parts.join('&')}`; +}; diff --git a/sandbox/src/templates/landing.ejs b/sandbox/src/templates/landing.ejs index 18fdb19..8a774fc 100644 --- a/sandbox/src/templates/landing.ejs +++ b/sandbox/src/templates/landing.ejs @@ -154,7 +154,7 @@
    -

    Apify AI Sandbox

    +

    Apify AI Code Sandbox

    Containerized sandbox environment for AI coding operations. Connect through HTTP, MCP, or the interactive shell.

    @@ -206,7 +208,7 @@
    -

    ⚡ Code execution

    +

    ⚡ Code execution API

    @@ -239,7 +241,7 @@
    -

    📁 Filesystem endpoints

    +

    📁 Filesystem API

    @@ -289,12 +291,12 @@
    -

    🔀 Proxy Mappings

    +

    🔀 Proxy mappings

    -

    Map local web servers to paths. Changes are applied immediately and persist across restarts.

    +

    Expose web servers that you start inside the sandbox (your own dev server, a TUI gateway, etc.) at a public URL path on this container, so they're reachable from the browser or other services. Each mapping forwards <container-url>/<path> to http://127.0.0.1:<port>/... over HTTP and WebSocket. Changes apply immediately and persist across restarts.

    @@ -321,7 +323,7 @@ color: #fff; border: none; cursor: pointer; box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3); transition: all 0.3s ease; white-space: nowrap; - ">Add Mapping + ">Add mapping

    API Examples

    diff --git a/sandbox/src/templates/shell.ts b/sandbox/src/templates/shell.ts index fb5af47..6c63a85 100644 --- a/sandbox/src/templates/shell.ts +++ b/sandbox/src/templates/shell.ts @@ -33,7 +33,7 @@ echo -e " +++++++++++++++++++++++++" echo -e " +++++++++++++++++++++++++++\${NC}" echo "" -echo -e "\${BOLD}Welcome to Apify AI Sandbox!\${NC}" +echo -e "\${BOLD}Welcome to Apify AI Code Sandbox!\${NC}" echo "" echo -e "\${GREEN}System Info:\${NC}" @@ -45,6 +45,7 @@ APIFY_VER=\$(cat "\$VERSION_DIR/apify.txt" 2>/dev/null || apify --version 2>/dev MCPC_VER=\$(cat "\$VERSION_DIR/mcpc.txt" 2>/dev/null || mcpc --version 2>/dev/null || echo 'not installed') CLAUDE_VER=\$(cat "\$VERSION_DIR/claude.txt" 2>/dev/null || claude --version 2>/dev/null || echo 'not installed') OPENCODE_VER=\$(cat "\$VERSION_DIR/opencode.txt" 2>/dev/null || opencode --version 2>/dev/null || echo 'not installed') +CODEX_VER=\$(cat "\$VERSION_DIR/codex.txt" 2>/dev/null || codex --version 2>/dev/null || echo 'not installed') echo -e " - Node.js: \$NODE_VER" echo -e " - Python: \$PYTHON_VER" @@ -52,6 +53,7 @@ echo -e " - Apify CLI: \$APIFY_VER" echo -e " - MCP CLI: \$MCPC_VER (https://github.com/apify/mcp-cli)" echo -e " - Claude: \$CLAUDE_VER" echo -e " - OpenCode: \$OPENCODE_VER" +echo -e " - Codex: \$CODEX_VER" echo -e " - CWD: \$(pwd)" if [ -n "\$VIRTUAL_ENV" ]; then echo -e " - Venv: Active (\$VIRTUAL_ENV)" diff --git a/sandbox/src/types.ts b/sandbox/src/types.ts index 20a16aa..d1b026e 100644 --- a/sandbox/src/types.ts +++ b/sandbox/src/types.ts @@ -1,5 +1,5 @@ /** - * Type definitions for the Apify AI Sandbox Actor + * Type definitions for the Apify AI Code Sandbox Actor */ /** @@ -32,7 +32,7 @@ export interface ActorInput { * Format: one package per line with optional version specifiers * Example: "requests==2.31.0\npandas>=2.0.0\nnumpy" */ - pythonRequirementsTxt?: string; + pythonRequirements?: string; /** * Optional bash script to customize the sandbox environment @@ -52,7 +52,7 @@ export interface ActorInput { * Activity includes HTTP requests and shell interaction. * @default 900 (15 minutes) */ - idleTimeoutSeconds?: number; + idleTimeoutSecs?: number; /** * Proxy mappings for routing requests to local servers @@ -67,5 +67,5 @@ export interface ActorInput { * and the sandbox writes the list to `/sandbox/mcp.json` on startup * so tools like `mcpc connect` can pick them up. */ - mcpConnections?: string[]; + mcpConnectors?: string[]; } diff --git a/sandbox/tests/e2e.ts b/sandbox/tests/e2e.ts index d697f64..e029def 100644 --- a/sandbox/tests/e2e.ts +++ b/sandbox/tests/e2e.ts @@ -1,5 +1,5 @@ /** - * E2E Platform Test for Apify AI Sandbox Actor + * E2E Platform Test for Apify AI Code Sandbox Actor * * This script: * 1. Deploys and starts the Actor on Apify platform @@ -433,7 +433,7 @@ async function testFsEndpoint( // ============================================================================ async function runAllTests(baseUrl: string): Promise { - console.log(`\n${colors.blue}Testing Apify AI Sandbox REST Endpoints${colors.reset}`); + console.log(`\n${colors.blue}Testing Apify AI Code Sandbox REST Endpoints${colors.reset}`); console.log(`Base URL: ${baseUrl}\n`); // Health check @@ -922,7 +922,7 @@ async function runAllTests(baseUrl: string): Promise { async function main(): Promise { console.log('==================================='); - console.log('🚀 Apify AI Sandbox E2E Platform Test'); + console.log('🚀 Apify AI Code Sandbox E2E Platform Test'); console.log('===================================\n'); let runId: string | null = null; @@ -934,7 +934,7 @@ async function main(): Promise { const input = { nodeDependencies: 'zod@^3.22.0', - pythonRequirementsTxt: 'numpy>=1.24.0', + pythonRequirements: 'numpy>=1.24.0', envVars: 'TEST_E2E_SECRET=hunter2-do-not-leak', initShellScript: [ '#!/bin/bash', diff --git a/sandbox/tests/unit/shell-launch.test.ts b/sandbox/tests/unit/shell-launch.test.ts new file mode 100644 index 0000000..3bba6e0 --- /dev/null +++ b/sandbox/tests/unit/shell-launch.test.ts @@ -0,0 +1,36 @@ +/* eslint-disable @typescript-eslint/no-floating-promises -- node:test's describe/it return promises by design */ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; + +import { translateLaunchParam } from '../../src/shell-launch.js'; + +describe('translateLaunchParam', () => { + it('returns path unchanged when no query string', () => { + assert.equal(translateLaunchParam('/'), '/'); + assert.equal(translateLaunchParam('/ws'), '/ws'); + }); + + it('returns path unchanged when launch is absent', () => { + assert.equal(translateLaunchParam('/?arg=-c&arg=foo'), '/?arg=-c&arg=foo'); + }); + + it('translates launch= to arg=-c arg=source bashrc; ', () => { + const out = translateLaunchParam('/?launch=claude'); + assert.equal(out, '/?arg=-c&arg=source%20%2Fapp%2Fsandbox_bashrc%3B%20claude'); + }); + + it('handles commands with spaces and special chars', () => { + const out = translateLaunchParam('/?launch=opencode%20tui'); + assert.equal(out, '/?arg=-c&arg=source%20%2Fapp%2Fsandbox_bashrc%3B%20opencode%20tui'); + }); + + it('preserves other query params (after the injected args)', () => { + const out = translateLaunchParam('/ws?launch=claude&token=abc'); + assert.equal(out, '/ws?arg=-c&arg=source%20%2Fapp%2Fsandbox_bashrc%3B%20claude&token=abc'); + }); + + it('handles empty launch value by just sourcing bashrc', () => { + const out = translateLaunchParam('/?launch='); + assert.equal(out, '/?arg=-c&arg=source%20%2Fapp%2Fsandbox_bashrc%3B'); + }); +}); From 7d4ec1ba5ebf8d0fb1e21f5a782c2bd6eca9e157 Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Sun, 31 May 2026 00:33:24 +0200 Subject: [PATCH 12/56] Show landing page (not shell) in the run Live View at root path (#48) Reverts #43: serve the docs/API landing page at `/` again instead of a full-page iframe of /shell/. Removes the `/info` route, the `docsUrl` output property, and the now-unused live-view template, and repoints the internal links back to `/`. https://claude.ai/code/session_01Xxnc9G8bTasCXR7HhSVrJG Co-authored-by: Claude --- sandbox/.actor/actor.json | 7 +------ sandbox/src/main.ts | 19 +++---------------- sandbox/src/templates/browse.ts | 2 +- sandbox/src/templates/landing.ejs | 3 +-- sandbox/src/templates/live-view.ts | 25 ------------------------- sandbox/src/templates/shell.ts | 2 +- 6 files changed, 7 insertions(+), 51 deletions(-) delete mode 100644 sandbox/src/templates/live-view.ts diff --git a/sandbox/.actor/actor.json b/sandbox/.actor/actor.json index 561c87e..1e2adc2 100644 --- a/sandbox/.actor/actor.json +++ b/sandbox/.actor/actor.json @@ -107,14 +107,9 @@ "properties": { "url": { "type": "string", - "title": "Live shell", + "title": "Main page", "template": "{{run.containerUrl}}" }, - "docsUrl": { - "type": "string", - "title": "Docs & API", - "template": "{{links.containerUrl}}/info" - }, "shellUrl": { "type": "string", "title": "Shell terminal", diff --git a/sandbox/src/main.ts b/sandbox/src/main.ts index c3dbb39..802f5a4 100644 --- a/sandbox/src/main.ts +++ b/sandbox/src/main.ts @@ -41,7 +41,6 @@ import { broadcastToTerminals, buildShutdownBanner } from './shutdown.js'; import { parseSkills } from './skills.js'; import { getBrowsePageHTML } from './templates/browse.js'; import { getLandingPageHTML, getLLMsMarkdown } from './templates/landing.js'; -import { getShellLiveViewHTML } from './templates/live-view.js'; import { SANDBOX_BASHRC, WELCOME_SCRIPT } from './templates/shell.js'; import type { ActorInput, ProxyMapping } from './types.js'; @@ -639,16 +638,8 @@ app.delete('/fs/*path', async (req: Request, res: Response) => { // Middleware for JSON parsing (applied to routes below) app.use(express.json({ limit: '50mb' })); -// Root serves the live shell terminal. Apify's run Live View always loads the -// container root, so embedding /shell/ here surfaces the interactive terminal -// directly in the run console. +// Landing page endpoint app.get('/', (_req: Request, res: Response) => { - res.setHeader('Content-Type', 'text/html; charset=utf-8'); - res.send(getShellLiveViewHTML()); -}); - -// Docs / API landing page (moved off `/` so the Live View can show the shell). -app.get('/info', (_req: Request, res: Response) => { res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.send( getLandingPageHTML({ @@ -1246,12 +1237,8 @@ server.listen(port, () => { console.log('🚀 Apify AI Code Sandbox Started'); console.log('=====================================\n'); - console.log('🖥️ Live shell (shown in the run Live View):'); + console.log('🏠 Landing page (open first):'); console.log(` GET ${serverUrl}/`); - console.log(' Interactive shell terminal, embedded\n'); - - console.log('🏠 Docs & endpoints page:'); - console.log(` GET ${serverUrl}/info`); console.log(' Connection details, quick links, and endpoint URLs\n'); console.log('🗂 File browser:'); @@ -1260,7 +1247,7 @@ server.listen(port, () => { // Shell terminal endpoint console.log(` GET ${serverUrl}/shell/`); - console.log(` Raw interactive shell terminal\n`); + console.log(` Interactive shell terminal\n`); // MCP Server URL console.log('📡 MCP Server Endpoint:'); diff --git a/sandbox/src/templates/browse.ts b/sandbox/src/templates/browse.ts index a7ef4fc..4316822 100644 --- a/sandbox/src/templates/browse.ts +++ b/sandbox/src/templates/browse.ts @@ -120,7 +120,7 @@ export function getBrowsePageHTML(): string {
    /sandbox
    diff --git a/sandbox/src/templates/landing.ejs b/sandbox/src/templates/landing.ejs index 8a774fc..e933b69 100644 --- a/sandbox/src/templates/landing.ejs +++ b/sandbox/src/templates/landing.ejs @@ -182,8 +182,7 @@
      -
    • Live shell (home): <%= serverUrl %>/
    • -
    • Docs page: <%= serverUrl %>/info
    • +
    • Landing page: <%= serverUrl %>/
    • Shell terminal: <%= serverUrl %>/shell/
    • File browser: <%= serverUrl %>/browse
    • Health check: <%= serverUrl %>/health
    • diff --git a/sandbox/src/templates/live-view.ts b/sandbox/src/templates/live-view.ts deleted file mode 100644 index a966977..0000000 --- a/sandbox/src/templates/live-view.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Full-page wrapper that embeds the interactive shell terminal in an iframe. - * - * Served at `/` so the Apify run's Live View — which always loads the container - * root — shows the live terminal directly in the run console. - */ -export function getShellLiveViewHTML(): string { - return ` - - - - - Sandbox | Shell - - - - - - - -`; -} diff --git a/sandbox/src/templates/shell.ts b/sandbox/src/templates/shell.ts index 6c63a85..25aa05e 100644 --- a/sandbox/src/templates/shell.ts +++ b/sandbox/src/templates/shell.ts @@ -61,7 +61,7 @@ fi echo "" echo -e "\${BLUE}Documentation:\${NC}" if [ -n "\$ACTOR_WEB_SERVER_URL" ]; then - echo -e " - Docs & API: \$ACTOR_WEB_SERVER_URL/info" + echo -e " - Actor page: \$ACTOR_WEB_SERVER_URL" fi if [ -n "\$ACTOR_RUN_ID" ]; then echo -e " - Run details: https://console.apify.com/view/runs/\$ACTOR_RUN_ID" From 21915e68fba90299c65109609899e9aefcb726fe Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Sun, 31 May 2026 10:22:39 +0200 Subject: [PATCH 13/56] Fix shell launch restart loop with persistent interactive shell (#49) * Change default terminal username from apify to actor Update the PS1 prompt in the sandbox shell template so the terminal shows actor@sandbox instead of apify@sandbox. * Auto-approve AI agents and fix /shell launch restart loop - /shell?launch= now runs in a persistent interactive shell that echoes the command and surfaces a non-zero exit, instead of a bash -c that exits and makes ttyd respawn it in a restart loop. - Claude: wrap the command with --dangerously-skip-permissions (settings bypass mode shows a blocking confirmation dialog); pre-accept onboarding. - Codex: ~/.codex/config.toml with approval_policy=never + danger-full-access. - OpenCode: permission "allow" so nothing prompts. - OpenClaw: open both YOLO policy layers (tools.exec + exec-approvals.json). - Point the wrapper actors' launch URLs at ?launch= so they get the fix. --------- Co-authored-by: Claude --- claude-code/.actor/actor.json | 2 +- openclaw/.actor/actor.json | 2 +- openclaw/src/main.ts | 17 ++++++++++++++ opencode/.actor/actor.json | 2 +- sandbox/Dockerfile | 12 +++++++--- sandbox/artifacts/opencode.json | 6 +---- sandbox/src/shell-launch.ts | 30 ++++++++++++++++++++----- sandbox/src/templates/shell.ts | 15 ++++++++++--- sandbox/tests/unit/shell-launch.test.ts | 21 ++++++++++++----- 9 files changed, 82 insertions(+), 25 deletions(-) diff --git a/claude-code/.actor/actor.json b/claude-code/.actor/actor.json index 0fb64df..60120df 100644 --- a/claude-code/.actor/actor.json +++ b/claude-code/.actor/actor.json @@ -84,7 +84,7 @@ "url": { "type": "string", "title": "Main page", - "template": "{{run.containerUrl}}/shell?arg=-c&arg=source%20/app/sandbox_bashrc%3B%20claude" + "template": "{{run.containerUrl}}/shell?launch=claude" } } }, diff --git a/openclaw/.actor/actor.json b/openclaw/.actor/actor.json index 6e66f29..e7f7448 100644 --- a/openclaw/.actor/actor.json +++ b/openclaw/.actor/actor.json @@ -60,7 +60,7 @@ "url": { "type": "string", "title": "Main page", - "template": "{{run.containerUrl}}/shell?arg=-c&arg=source%20/app/sandbox_bashrc%3B%20openclaw%20tui" + "template": "{{run.containerUrl}}/shell?launch=openclaw%20tui" } } }, diff --git a/openclaw/src/main.ts b/openclaw/src/main.ts index 7d5392d..ce3d421 100644 --- a/openclaw/src/main.ts +++ b/openclaw/src/main.ts @@ -40,6 +40,13 @@ cat > ~/.openclaw/openclaw.json << 'CLAWEOF' "workspace": "~/.openclaw/workspace" } }, + "tools": { + "exec": { + "host": "gateway", + "security": "full", + "ask": "off" + } + }, "models": { "mode": "merge", "providers": { @@ -73,6 +80,16 @@ openclaw onboard \\ --gateway-port 18789 \\ --skip-daemon \\ --skip-health +# Auto-approve all tool actions (YOLO) — safe inside the sandbox. The effective +# policy is the stricter of two layers, so open both: the requested policy in +# openclaw.json (tools.exec, set above) and the host-local approvals file below. +openclaw exec-policy preset yolo || true +cat > ~/.openclaw/exec-approvals.json << 'APPROVEEOF' +{ + "version": 1, + "defaults": { "security": "full", "ask": "off", "askFallback": "full" } +} +APPROVEEOF nohup openclaw gateway run --bind loopback --port 18789 > /tmp/openclaw-gateway.log 2>&1 & sleep 3 echo "OpenClaw started: http://127.0.0.1:18789/openclaw/"`; diff --git a/opencode/.actor/actor.json b/opencode/.actor/actor.json index 3a6fb45..d99c4b5 100644 --- a/opencode/.actor/actor.json +++ b/opencode/.actor/actor.json @@ -60,7 +60,7 @@ "url": { "type": "string", "title": "Main page", - "template": "{{run.containerUrl}}/shell?arg=-c&arg=source%20/app/sandbox_bashrc%3B%20opencode" + "template": "{{run.containerUrl}}/shell?launch=opencode" } } }, diff --git a/sandbox/Dockerfile b/sandbox/Dockerfile index cd735f1..496fee4 100644 --- a/sandbox/Dockerfile +++ b/sandbox/Dockerfile @@ -132,9 +132,15 @@ ENV IS_SANDBOX=1 # Set Claude Code max output tokens to 64000 ENV CLAUDE_CODE_MAX_OUTPUT_TOKENS=128000 -# Configure Claude Code to bypass permissions -RUN mkdir -p /root/.claude && \ - echo '{"permissions": {"defaultMode": "bypassPermissions"}}' > /root/.claude/settings.json +# Pre-accept Claude Code's first-run onboarding so the CLI starts immediately. +# Permission prompts are bypassed via --dangerously-skip-permissions in the shell +# wrapper (see SANDBOX_BASHRC); settings.json bypass mode triggers a blocking +# confirmation dialog, so we deliberately don't set it here. +RUN echo '{"hasCompletedOnboarding": true}' > /root/.claude.json + +# Configure Codex CLI to auto-approve everything — safe inside the sandbox. +RUN mkdir -p /root/.codex && \ + printf 'approval_policy = "never"\nsandbox_mode = "danger-full-access"\n' > /root/.codex/config.toml # Capture tool versions at build time for fast shell startup COPY scripts/capture-versions.sh /tmp/capture-versions.sh diff --git a/sandbox/artifacts/opencode.json b/sandbox/artifacts/opencode.json index b151ecd..30037f9 100644 --- a/sandbox/artifacts/opencode.json +++ b/sandbox/artifacts/opencode.json @@ -1,10 +1,6 @@ { "$schema": "https://opencode.ai/config.json", - "permission": { - "bash": "allow", - "edit": "allow", - "webfetch": "allow" - }, + "permission": "allow", "provider": { "apify-openrouter": { "npm": "@ai-sdk/openai-compatible", diff --git a/sandbox/src/shell-launch.ts b/sandbox/src/shell-launch.ts index a588986..1ac954b 100644 --- a/sandbox/src/shell-launch.ts +++ b/sandbox/src/shell-launch.ts @@ -1,13 +1,33 @@ /** * Translate `?launch=` on a /shell URL into the `?arg=-c&arg=...` form - * ttyd expects. `?launch=` is a convenience: bash invoked with `-c` does not - * source rcfiles, so we explicitly prepend `source /app/sandbox_bashrc;` to - * ensure the launched command sees the same env as an interactive shell. + * ttyd expects, running inside a persistent interactive shell. + * + * Why the interactive wrapper: ttyd has no `--once`, so when the spawned + * process exits the browser reconnects and ttyd respawns it. A bare + * `bash -c "...; "` exits the moment finishes (or fails to start), + * which produced a restart loop. Instead we: + * 1. source the sandbox rcfile (so sees the same env + wrappers), + * 2. echo the command so it's visible (as if typed at the prompt), + * 3. run it, surfacing a non-zero exit status, + * 4. `exec` an interactive shell so the terminal stays alive afterwards — + * keeping any output or errors on screen instead of looping away. * * Idempotent: if `launch` is absent, the path is returned unchanged. Other * query params are preserved in order. */ -const BASHRC_SOURCE = 'source /app/sandbox_bashrc;'; +const BASHRC = '/app/sandbox_bashrc'; +const INTERACTIVE_SHELL = `exec bash --rcfile ${BASHRC}`; + +/** Build the `bash -c` payload for a launched command. */ +const buildLaunchCommand = (launch: string): string => { + if (!launch.trim()) return INTERACTIVE_SHELL; + return [ + `source ${BASHRC};`, + `echo "$ ${launch}";`, + `${launch} || echo "[command exited with status $?]";`, + INTERACTIVE_SHELL, + ].join(' '); +}; export const translateLaunchParam = (path: string): string => { const queryIdx = path.indexOf('?'); @@ -19,7 +39,7 @@ export const translateLaunchParam = (path: string): string => { if (launch === null) return path; params.delete('launch'); - const cmd = launch.trim() ? `${BASHRC_SOURCE} ${launch}` : BASHRC_SOURCE; + const cmd = buildLaunchCommand(launch); const parts: string[] = [ `arg=${encodeURIComponent('-c')}`, diff --git a/sandbox/src/templates/shell.ts b/sandbox/src/templates/shell.ts index 25aa05e..6a2c8b4 100644 --- a/sandbox/src/templates/shell.ts +++ b/sandbox/src/templates/shell.ts @@ -92,15 +92,24 @@ export ANTHROPIC_AUTH_TOKEN="\${APIFY_TOKEN}" export ANTHROPIC_API_KEY="" # Colorful prompt -PS1='\\[\\033[01;32m\\]apify\\[\\033[00m\\]@\\[\\033[01;34m\\]sandbox\\[\\033[00m\\]:\\[\\033[01;33m\\]\\w\\[\\033[00m\\]\\$ ' +PS1='\\[\\033[01;32m\\]actor\\[\\033[00m\\]@\\[\\033[01;34m\\]sandbox\\[\\033[00m\\]:\\[\\033[01;33m\\]\\w\\[\\033[00m\\]\\$ ' # Aliases alias ll='ls -alF' alias la='ls -A' alias l='ls -CF' -# Print welcome message -if [ -f /app/welcome.sh ]; then +# Auto-approve all confirmations for AI coding agents — safe inside the sandbox. +# Claude Code's settings-based bypass mode shows a blocking confirmation dialog, +# so we pass --dangerously-skip-permissions explicitly. Defined as a function so +# it also applies on the non-interactive launch path (bash -c). Codex and +# OpenCode auto-approve via their own config files. +claude() { command claude --dangerously-skip-permissions "$@"; } + +# Print welcome message (once per session; the launch wrapper sources this +# rcfile twice — to set up the env, then again for the persistent shell). +if [ -z "$SANDBOX_WELCOME_SHOWN" ] && [ -f /app/welcome.sh ]; then + export SANDBOX_WELCOME_SHOWN=1 bash /app/welcome.sh fi `; diff --git a/sandbox/tests/unit/shell-launch.test.ts b/sandbox/tests/unit/shell-launch.test.ts index 3bba6e0..d689991 100644 --- a/sandbox/tests/unit/shell-launch.test.ts +++ b/sandbox/tests/unit/shell-launch.test.ts @@ -14,23 +14,32 @@ describe('translateLaunchParam', () => { assert.equal(translateLaunchParam('/?arg=-c&arg=foo'), '/?arg=-c&arg=foo'); }); - it('translates launch= to arg=-c arg=source bashrc; ', () => { + it('runs launch= in a persistent shell, echoing the command and any error', () => { const out = translateLaunchParam('/?launch=claude'); - assert.equal(out, '/?arg=-c&arg=source%20%2Fapp%2Fsandbox_bashrc%3B%20claude'); + assert.equal( + out, + '/?arg=-c&arg=source%20%2Fapp%2Fsandbox_bashrc%3B%20echo%20%22%24%20claude%22%3B%20claude%20%7C%7C%20echo%20%22%5Bcommand%20exited%20with%20status%20%24%3F%5D%22%3B%20exec%20bash%20--rcfile%20%2Fapp%2Fsandbox_bashrc', + ); }); it('handles commands with spaces and special chars', () => { const out = translateLaunchParam('/?launch=opencode%20tui'); - assert.equal(out, '/?arg=-c&arg=source%20%2Fapp%2Fsandbox_bashrc%3B%20opencode%20tui'); + assert.equal( + out, + '/?arg=-c&arg=source%20%2Fapp%2Fsandbox_bashrc%3B%20echo%20%22%24%20opencode%20tui%22%3B%20opencode%20tui%20%7C%7C%20echo%20%22%5Bcommand%20exited%20with%20status%20%24%3F%5D%22%3B%20exec%20bash%20--rcfile%20%2Fapp%2Fsandbox_bashrc', + ); }); it('preserves other query params (after the injected args)', () => { const out = translateLaunchParam('/ws?launch=claude&token=abc'); - assert.equal(out, '/ws?arg=-c&arg=source%20%2Fapp%2Fsandbox_bashrc%3B%20claude&token=abc'); + assert.equal( + out, + '/ws?arg=-c&arg=source%20%2Fapp%2Fsandbox_bashrc%3B%20echo%20%22%24%20claude%22%3B%20claude%20%7C%7C%20echo%20%22%5Bcommand%20exited%20with%20status%20%24%3F%5D%22%3B%20exec%20bash%20--rcfile%20%2Fapp%2Fsandbox_bashrc&token=abc', + ); }); - it('handles empty launch value by just sourcing bashrc', () => { + it('opens a persistent interactive shell when launch is empty', () => { const out = translateLaunchParam('/?launch='); - assert.equal(out, '/?arg=-c&arg=source%20%2Fapp%2Fsandbox_bashrc%3B'); + assert.equal(out, '/?arg=-c&arg=exec%20bash%20--rcfile%20%2Fapp%2Fsandbox_bashrc'); }); }); From 12294dedbcf13f1aa55a4147e7317f5f3ba8a0ed Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Sun, 31 May 2026 11:44:23 +0200 Subject: [PATCH 14/56] Redesign landing page and remove Actor on Apify button (#50) * feat(landing): redesign landing page, remove Actor on Apify button Refine to a calmer dark theme with a single accent and unified quick-link buttons, and fix inline code chips that rendered as misaligned full-width blocks. https://claude.ai/code/session_01JbUM5pBzpZRodXuWN4C7vf * refactor(landing): extract page styles into landing.css Move the inline +
      @@ -170,15 +27,14 @@
        @@ -191,13 +47,13 @@
      -

      📡 Connect with MCP

      +

      📡 Connect with MCP

      URL

      <%= serverUrl %>/mcp
      - +

      Claude Code

      @@ -207,10 +63,10 @@
      -

      ⚡ Code execution API

      +

      Code execution API

      - +

      Run bash command

      @@ -240,13 +96,13 @@
      -

      📁 Filesystem API

      +

      📁 Filesystem API

      - +
      -

      Direct file operations using HTTP methods. All paths relative to /sandbox.

      - +

      Direct file operations using HTTP methods. All paths relative to /sandbox.

      +

      Read file or list directory

      @@ -290,42 +146,28 @@
      -

      🔀 Proxy mappings

      +

      🔀 Proxy mappings

      -

      Expose web servers that you start inside the sandbox (your own dev server, a TUI gateway, etc.) at a public URL path on this container, so they're reachable from the browser or other services. Each mapping forwards <container-url>/<path> to http://127.0.0.1:<port>/... over HTTP and WebSocket. Changes apply immediately and persist across restarts.

      - -
      - -
      -
      - - +

      Expose web servers that you start inside the sandbox (your own dev server, a TUI gateway, etc.) at a public URL path on this container, so they're reachable from the browser or other services. Each mapping forwards <container-url>/<path> to http://127.0.0.1:<port>/... over HTTP and WebSocket. Changes apply immediately and persist across restarts.

      + +
      + +
      +
      + +
      -
      - - +
      + +
      - +
      -

      API Examples

      +

      API examples

      # Get current mappings
      @@ -344,12 +186,12 @@
       
               
      -

      📋 Response format

      +

      📋 Response format

      -

      All /exec requests return:

      +

      All /exec requests return:

      {
      @@ -364,30 +206,30 @@
       
               
      -

      📂 Working directories

      +

      📂 Working directories

        -
      • Shell commands: /sandbox (default)
      • -
      • JavaScript/TypeScript: /sandbox/js-ts (default)
      • -
      • Python: /sandbox/py (default)
      • -
      • Override with cwd parameter (must be within /sandbox)
      • +
      • Shell commands: /sandbox (default)
      • +
      • JavaScript/TypeScript: /sandbox/js-ts (default)
      • +
      • Python: /sandbox/py (default)
      • +
      • Override with cwd parameter (must be within /sandbox)
      -

      ⚙️ Configuration

      +

      ⚙️ Configuration

        -
      • Idle Timeout: The container automatically shuts down after inactivity (default 10m).
      • -
      • Execution Timeout: Recommended to set to 0 (infinite) on the platform; use the idleTimeoutSeconds input to control lifecycle.
      • +
      • Idle timeout: the container automatically shuts down after inactivity (default 10m).
      • +
      • Execution timeout: recommended to set to 0 (infinite) on the platform; use the idleTimeoutSeconds input to control lifecycle.
      @@ -397,12 +239,12 @@ function copyCode(button) { const codeBlock = button.nextElementSibling; const code = codeBlock.textContent; - + navigator.clipboard.writeText(code).then(() => { const originalText = button.textContent; button.textContent = 'Copied!'; button.classList.add('copied'); - + setTimeout(() => { button.textContent = originalText; button.classList.remove('copied'); @@ -411,11 +253,11 @@ console.error('Failed to copy:', err); }); } - + function toggleCollapse(id) { const content = document.getElementById(id); const button = document.getElementById(id + 'Btn'); - + if (content.classList.contains('collapsed')) { content.classList.remove('collapsed'); button.textContent = 'Hide'; @@ -424,15 +266,15 @@ button.textContent = 'Show'; } } - + async function checkHealth() { const badge = document.getElementById('statusBadge'); const text = document.getElementById('statusText'); - + try { const response = await fetch('/health'); const data = await response.json(); - + if (data.status === 'healthy') { badge.className = 'status-badge healthy'; text.textContent = 'Healthy'; @@ -445,10 +287,10 @@ text.textContent = 'Offline'; } } - + // Check health on page load checkHealth(); - + // Refresh health status every 30 seconds setInterval(checkHealth, 30000); @@ -462,56 +304,45 @@ console.error('Failed to load proxy mappings:', error); } } - + function renderProxyMappings(mappings) { const container = document.getElementById('proxyMappingsList'); - + if (mappings.length === 0) { - container.innerHTML = '

      No proxy mappings configured

      '; + container.innerHTML = '

      No proxy mappings configured

      '; return; } - + container.innerHTML = mappings.map(m => ` -
      - ${m.path} - - ${m.target} - Open - +
      + ${m.path} + + ${m.target} + Open +
      `).join(''); } - + async function addProxyMapping() { const pathInput = document.getElementById('newProxyPath'); const targetInput = document.getElementById('newProxyTarget'); - + const path = pathInput.value.trim(); const target = targetInput.value.trim(); - + if (!path || !target) { alert('Both path and target are required'); return; } - + try { const response = await fetch('/proxy-config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path, target }) }); - + if (response.ok) { const data = await response.json(); renderProxyMappings(data.mappings); @@ -525,15 +356,15 @@ alert('Failed to add mapping: ' + error.message); } } - + async function deleteProxyMapping(path) { if (!confirm(`Remove proxy mapping for ${path}?`)) return; - + try { const response = await fetch('/proxy-config' + path, { method: 'DELETE' }); - + if (response.ok) { const data = await response.json(); renderProxyMappings(data.mappings); @@ -545,12 +376,12 @@ alert('Failed to remove mapping: ' + error.message); } } - + // Load proxy mappings on page load loadProxyMappings(); - + // Refresh proxy mappings every 10 seconds to catch file-based changes setInterval(loadProxyMappings, 10000); - \ No newline at end of file + diff --git a/sandbox/src/templates/landing.ts b/sandbox/src/templates/landing.ts index b11dfa6..39b99cc 100644 --- a/sandbox/src/templates/landing.ts +++ b/sandbox/src/templates/landing.ts @@ -14,6 +14,9 @@ interface LandingPageOptions { const templatePath = join(dirname(fileURLToPath(import.meta.url)), 'landing.ejs'); const landingTemplate = readFileSync(templatePath, 'utf8'); +const stylesPath = join(dirname(fileURLToPath(import.meta.url)), 'landing.css'); +const landingStyles = readFileSync(stylesPath, 'utf8'); + const STRIP_SELECTOR = 'script, style, [data-no-md], .copy-btn, .collapse-btn, .status-badge'; const nhm = new NodeHtmlMarkdown( @@ -43,6 +46,7 @@ export function getLandingPageHTML({ serverUrl, isLocalMode }: LandingPageOption serverUrl, modeLabel, isLocalMode, + styles: landingStyles, }); } From e5d4b549702189564c925eef897d3bf222005351 Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Sun, 31 May 2026 23:35:57 +0200 Subject: [PATCH 15/56] Write shutdown banner directly to browser terminal sockets (#51) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Deliver shutdown banner directly over the terminal WebSocket The terminal is proxied (browser <-> Actor <-> ttyd), so writing the banner to the PTY and relaying it back through ttyd lost the race against process exit on platform-initiated stops (abort, migration, run timeout) — only the idle path's flush delay reliably worked. Write a ready-made ttyd output frame straight to the browser socket instead, so the bytes reach the kernel before the proxy dies; fall back to the PTY devices when no socket is tracked. https://claude.ai/code/session_01KrCLcsSMPpsyWwuTyCzYv8 * Pin ttyd to 1.7.7 The shutdown banner writes ttyd's own WebSocket output frames directly to the browser, so an unpinned ttyd could change its wire protocol and silently break banner rendering. Pin it and document how to re-check on a future bump. https://claude.ai/code/session_01KrCLcsSMPpsyWwuTyCzYv8 --------- Co-authored-by: Claude --- sandbox/Dockerfile | 10 +++- sandbox/src/main.ts | 74 ++++++++++++++++++++++++----- sandbox/src/shutdown.ts | 49 +++++++++++++++++++ sandbox/tests/unit/shutdown.test.ts | 43 ++++++++++++++++- 4 files changed, 162 insertions(+), 14 deletions(-) diff --git a/sandbox/Dockerfile b/sandbox/Dockerfile index 496fee4..6b0f26e 100644 --- a/sandbox/Dockerfile +++ b/sandbox/Dockerfile @@ -12,8 +12,14 @@ RUN apt-get update && apt-get install -y \ ca-certificates \ && rm -rf /var/lib/apt/lists/* -# Build ttyd from source -RUN git clone --depth 1 https://github.com/tsl0922/ttyd.git /tmp/ttyd \ +# Build ttyd from source, pinned for reproducible builds. The shutdown banner +# writes ttyd's own WebSocket output frames directly to the browser terminal +# (see sandbox/src/shutdown.ts, encodeTtydOutputMessage), so a protocol change +# could silently stop the banner from rendering. Before bumping this version, +# confirm terminal OUTPUT is still command byte '0' in ttyd's client protocol: +# https://github.com/tsl0922/ttyd/blob/main/html/src/components/terminal/xterm/index.ts (the Command enum). +ARG TTYD_VERSION=1.7.7 +RUN git clone --branch "${TTYD_VERSION}" --depth 1 https://github.com/tsl0922/ttyd.git /tmp/ttyd \ && cd /tmp/ttyd && mkdir build && cd build \ && cmake .. \ && make && make install diff --git a/sandbox/src/main.ts b/sandbox/src/main.ts index 802f5a4..cd09680 100644 --- a/sandbox/src/main.ts +++ b/sandbox/src/main.ts @@ -37,7 +37,7 @@ import { removeProxyMapping, saveProxyConfig, } from './proxy-config.js'; -import { broadcastToTerminals, buildShutdownBanner } from './shutdown.js'; +import { broadcastToTerminals, buildShutdownBanner, encodeTtydOutputMessage } from './shutdown.js'; import { parseSkills } from './skills.js'; import { getBrowsePageHTML } from './templates/browse.js'; import { getLandingPageHTML, getLLMsMarkdown } from './templates/landing.js'; @@ -904,23 +904,55 @@ if (!isLocalMode) { // Shutdown notifications // ============================================================================ -/** Delay before exiting so ttyd can flush the banner to connected browsers. */ +/** Delay before a self-initiated exit so the banner flushes to connected browsers. */ const TERMINAL_FLUSH_DELAY_MS = 1000; /** - * Show a shutdown banner in every open browser terminal. No-op in local mode, - * where /dev/pts would hold the developer's own terminals rather than ttyd's. + * Browser-facing WebSocket sockets for open /shell terminals. The terminal is + * served through a proxy (browser ↔ this Actor ↔ ttyd), so these are the sockets + * we write the shutdown banner to directly. Populated by the upgrade handler + * below; entries remove themselves when the connection closes. + */ +const terminalSockets = new Set(); + +/** Guard so a shutdown shows the banner once, even if several stop events fire. */ +let shutdownBannerSent = false; + +/** + * Show a shutdown banner in every open browser terminal. No-op in local mode. + * + * Writes a ready-made ttyd output frame straight to each browser socket rather + * than relaying it through ttyd: at shutdown this process (the proxy) is about + * to exit, and a direct write reaches the kernel — and thus the browser — even + * if the long way round (PTY → ttyd → proxy → browser) wouldn't finish in time. + * Falls back to the PTY devices only if no browser socket is tracked. * @param reason - Human-readable explanation of why the Actor is stopping. */ const notifyTerminalsOfShutdown = (reason: string): void => { - if (isLocalMode) return; - broadcastToTerminals(buildShutdownBanner(reason, process.env.ACTOR_RUN_ID)); + if (isLocalMode || shutdownBannerSent) return; + shutdownBannerSent = true; + + const banner = buildShutdownBanner(reason, process.env.ACTOR_RUN_ID); + const frame = encodeTtydOutputMessage(banner); + let delivered = 0; + for (const socket of terminalSockets) { + try { + socket.write(frame); + delivered += 1; + } catch { + // Socket already half-closed between iterations — ignore. + } + } + + // No browser proxied through us (e.g. a directly attached ttyd): fall back + // to writing the banner to the PTY devices ttyd reads from. + if (delivered === 0) broadcastToTerminals(banner); }; /** - * Notify open terminals, then exit the Actor. The brief delay lets ttyd flush - * the banner over the WebSocket before the process tears down the connection - * (after which the terminal only shows ttyd's "Press ⏎ to Reconnect" overlay). + * Notify open terminals, then exit the Actor. The brief delay lets the banner + * flush over the WebSocket before the process tears down the connection (after + * which the terminal only shows ttyd's "Press ⏎ to Reconnect" overlay). * @param reason - Human-readable explanation of why the Actor is stopping. */ const shutdownWithNotice = async (reason: string): Promise => { @@ -931,8 +963,10 @@ const shutdownWithNotice = async (reason: string): Promise => { await Actor.exit({ statusMessage: reason }); }; -// Surface platform-initiated stops (migration, abort) in the terminal too. These -// fire synchronously; the platform controls process exit, so we only broadcast. +// Surface platform-initiated stops in the terminal too. The migration/abort +// events are advance notices, and the platform stops the container with a +// termination signal; in every case the banner is written synchronously so it +// reaches the browser before the process exits. if (!isLocalMode) { Actor.on('migrating', () => { notifyTerminalsOfShutdown('Actor is migrating to a new host and will resume shortly. Reconnect in a moment.'); @@ -940,6 +974,14 @@ if (!isLocalMode) { Actor.on('aborting', () => { notifyTerminalsOfShutdown('Actor run is being aborted.'); }); + + // Only hook signals the SDK already handles, so we add the banner without + // taking over termination — a lone SIGTERM listener would suppress Node's + // default exit and leave the container hanging until the platform SIGKILLs it. + const notifyOnSignal = (): void => notifyTerminalsOfShutdown('Actor run is stopping.'); + for (const signal of ['SIGINT', 'SIGTERM'] as const) { + if (process.listenerCount(signal) > 0) process.prependListener(signal, notifyOnSignal); + } } // Manual HTTP Proxy for ttyd @@ -987,6 +1029,16 @@ server.on('upgrade', (req, socket, head) => { req.url = translateLaunchParam(req.url); log.info('Proxying shell WebSocket upgrade', { url: req.url }); + // Remember this browser socket so the shutdown banner can be written to + // it directly (see notifyTerminalsOfShutdown), and forget it on close. + terminalSockets.add(socket as Duplex); + const forgetSocket = (): void => { + terminalSockets.delete(socket as Duplex); + }; + socket.on('close', forgetSocket); + socket.on('end', forgetSocket); + socket.on('error', forgetSocket); + // Track activity on WebSocket data socket.on('data', () => { lastActivityAt = Date.now(); diff --git a/sandbox/src/shutdown.ts b/sandbox/src/shutdown.ts index ba0c08c..9b4a919 100644 --- a/sandbox/src/shutdown.ts +++ b/sandbox/src/shutdown.ts @@ -88,3 +88,52 @@ export const buildShutdownBanner = (reason: string, runId?: string): string => { return `${lines.join('\r\n')}\r\n`; }; + +/** + * ttyd command byte for terminal output. ttyd's WebSocket subprotocol prefixes + * every server→client message with a one-byte command; '0' (0x30) means "write + * the rest straight to the terminal". + */ +const TTYD_OUTPUT_COMMAND = 0x30; // '0' + +/** WebSocket header byte: FIN set, opcode 0x2 (binary frame). */ +const WS_FIN_BINARY = 0x82; + +/** + * Encode text as a ttyd output message inside a single binary WebSocket frame, + * ready to write straight to a browser's terminal socket. + * + * The terminal is served through a proxy (browser ↔ this Actor ↔ ttyd). When the + * Actor stops, the proxy stops with it, so relaying a banner the long way round + * (PTY → ttyd → proxy → browser) usually loses the race against process exit. + * Writing a ready-made frame directly to the browser-facing socket hands the + * bytes to the kernel immediately, so they reach the terminal even if the + * process exits a moment later. + * + * Server→client frames are never masked (RFC 6455 §5.1), so the frame is just a + * FIN+binary header, the payload length, and the payload (command byte + text). + * + * @param text - Text to display in the terminal. Use `\r\n` line breaks. + * @returns The encoded WebSocket frame. + */ +export const encodeTtydOutputMessage = (text: string): Buffer => { + const payload = Buffer.concat([Buffer.from([TTYD_OUTPUT_COMMAND]), Buffer.from(text, 'utf8')]); + const len = payload.length; + + let header: Buffer; + if (len < 126) { + header = Buffer.from([WS_FIN_BINARY, len]); + } else if (len < 0x10000) { + // 126 signals a 16-bit length follows. + // eslint-disable-next-line no-bitwise -- splitting the length into bytes needs shifts/masks + header = Buffer.from([WS_FIN_BINARY, 126, (len >> 8) & 0xff, len & 0xff]); + } else { + // 127 signals a 64-bit length follows. + header = Buffer.alloc(10); + header[0] = WS_FIN_BINARY; + header[1] = 127; + header.writeBigUInt64BE(BigInt(len), 2); + } + + return Buffer.concat([header, payload]); +}; diff --git a/sandbox/tests/unit/shutdown.test.ts b/sandbox/tests/unit/shutdown.test.ts index 8976ba4..56f229e 100644 --- a/sandbox/tests/unit/shutdown.test.ts +++ b/sandbox/tests/unit/shutdown.test.ts @@ -1,8 +1,9 @@ /* eslint-disable @typescript-eslint/no-floating-promises -- node:test's describe/it return promises by design */ +/* eslint-disable no-bitwise -- decoding a WebSocket frame in the test mirrors the bit-level wire format */ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { broadcastToTerminals, buildShutdownBanner } from '../../src/shutdown.js'; +import { broadcastToTerminals, buildShutdownBanner, encodeTtydOutputMessage } from '../../src/shutdown.js'; describe('buildShutdownBanner', () => { it('includes the shutdown headline and the reason', () => { @@ -37,3 +38,43 @@ describe('broadcastToTerminals', () => { assert.doesNotThrow(() => broadcastToTerminals('hi', '/no/such/pts/dir')); }); }); + +describe('encodeTtydOutputMessage', () => { + /** Decode a frame the way ttyd's browser client does: one binary frame, drop the command byte. */ + const decode = (frame: Buffer): { command: string; text: string } => { + assert.equal(frame[0], 0x82, 'first byte must be FIN + binary opcode'); + assert.equal(frame[1] & 0x80, 0, 'server-to-client frames must not be masked'); + + let len = frame[1] & 0x7f; + let offset = 2; + if (len === 126) { + len = frame.readUInt16BE(2); + offset = 4; + } else if (len === 127) { + len = Number(frame.readBigUInt64BE(2)); + offset = 10; + } + + const payload = frame.subarray(offset, offset + len); + return { command: String.fromCharCode(payload[0]), text: payload.subarray(1).toString('utf8') }; + }; + + it('wraps text as a ttyd OUTPUT ("0") message that round-trips', () => { + const { command, text } = decode(encodeTtydOutputMessage('hello world')); + assert.equal(command, '0'); + assert.equal(text, 'hello world'); + }); + + it('round-trips a full shutdown banner (length > 125 uses the extended header)', () => { + const banner = buildShutdownBanner('Actor shut down after 15 minutes of inactivity.', 'RUN123'); + assert.ok(banner.length > 125, 'banner should be long enough to exercise the 16-bit length path'); + const { command, text } = decode(encodeTtydOutputMessage(banner)); + assert.equal(command, '0'); + assert.equal(text, banner); + }); + + it('preserves multi-byte UTF-8 by measuring length in bytes, not characters', () => { + const { text } = decode(encodeTtydOutputMessage('héllo — 🚀')); + assert.equal(text, 'héllo — 🚀'); + }); +}); From 78f35b418dfd9d2a41915cb00803dc123169d057 Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Sun, 31 May 2026 23:38:15 +0200 Subject: [PATCH 16/56] Document GitHub repo URL support for skills input (#52) The skills CLI accepts a repo URL as well as the owner/repo shorthand, and parseSkills already passes both through unchanged. Surface this in the input description, prefill example, README, and doc comments, and add tests covering URL pass-through (line + JSON formats). https://claude.ai/code/session_016XgcmN1VU9uvoaAcD1rdLF Co-authored-by: Claude --- sandbox/.actor/actor.json | 4 ++-- sandbox/README.md | 2 +- sandbox/src/skills.ts | 6 ++++-- sandbox/src/types.ts | 5 +++-- sandbox/tests/unit/skills.test.ts | 23 +++++++++++++++++++++++ 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/sandbox/.actor/actor.json b/sandbox/.actor/actor.json index 1e2adc2..c5015d5 100644 --- a/sandbox/.actor/actor.json +++ b/sandbox/.actor/actor.json @@ -19,10 +19,10 @@ "skills": { "title": "Skills", "type": "string", - "description": "Skill packages to install for the AI coding agent — SKILLS.md files with specialized instructions (see https://skills.sh/). One skill per line, e.g. `anthropics/skills` (blank lines and `#` comments are ignored), or a JSON array like `[\"apify/agent-skills\", \"anthropics/skills\"]`.", + "description": "Skill packages to install for the AI coding agent — SKILLS.md files with specialized instructions (see https://skills.sh/). One skill per line — a GitHub `owner/repo` (e.g. `anthropics/skills`) or repo URL (e.g. `https://github.com/anthropics/skills`); blank lines and `#` comments are ignored. Also accepts a JSON array like `[\"apify/agent-skills\", \"anthropics/skills\"]`.", "editor": "textarea", "default": "apify/agent-skills", - "prefill": "apify/agent-skills\n# One skill per line, e.g. anthropics/skills" + "prefill": "apify/agent-skills\n# One skill per line — owner/repo or a GitHub repo URL:\n# anthropics/skills\n# https://github.com/anthropics/skills" }, "nodeDependencies": { "title": "Node.js dependencies", diff --git a/sandbox/README.md b/sandbox/README.md index 936370a..d6d44ed 100644 --- a/sandbox/README.md +++ b/sandbox/README.md @@ -416,7 +416,7 @@ mkdir -p /sandbox/custom-data && chmod 755 /sandbox/custom-data Install skill packages that provide specialized instructions for AI coding agents. Skills are SKILLS.md files that enhance agent capabilities. -- Specify skills via the "Skills" input — one package per line (e.g. `anthropics/skills`), or a JSON array +- Specify skills via the "Skills" input — one per line: a GitHub `owner/repo` (e.g. `anthropics/skills`) or repo URL (e.g. `https://github.com/anthropics/skills`), or a JSON array - Example: `apify/agent-skills` - Skills are installed globally during Actor startup - For more info see [skills.sh](https://skills.sh/) diff --git a/sandbox/src/skills.ts b/sandbox/src/skills.ts index 390e96c..d14197e 100644 --- a/sandbox/src/skills.ts +++ b/sandbox/src/skills.ts @@ -39,8 +39,10 @@ const parseLines = (raw: string): string[] => { /** * Parse the user-supplied `skills` input into a de-duplicated list of skill - * identifiers for `installSkills`. Accepts either: - * - one skill per line (e.g. `anthropics/skills`; blank lines and `#` comments ignored), or + * identifiers for `installSkills`. Each identifier is passed through unchanged to + * the `skills` CLI, which accepts a GitHub `owner/repo` (e.g. `anthropics/skills`) + * or a repo URL (e.g. `https://github.com/anthropics/skills`). Accepts either: + * - one skill per line (blank lines and `#` comments ignored), or * - a JSON array of skill name strings (input starting with `[` or `{` is parsed * as JSON; any non-array JSON yields no skills). * diff --git a/sandbox/src/types.ts b/sandbox/src/types.ts index d1b026e..809f7d4 100644 --- a/sandbox/src/types.ts +++ b/sandbox/src/types.ts @@ -15,8 +15,9 @@ export interface ProxyMapping { export interface ActorInput { /** * Skill packages to install for the AI coding agent (SKILLS.md files). - * Accepts one skill per line (e.g. `anthropics/skills`; blank lines and - * `#` comments ignored) or a JSON array of skill name strings. + * Accepts one skill per line — a GitHub `owner/repo` (e.g. `anthropics/skills`) + * or repo URL (e.g. `https://github.com/anthropics/skills`); blank lines and + * `#` comments ignored — or a JSON array of skill name strings. */ skills?: string; diff --git a/sandbox/tests/unit/skills.test.ts b/sandbox/tests/unit/skills.test.ts index 41df701..ffbfc36 100644 --- a/sandbox/tests/unit/skills.test.ts +++ b/sandbox/tests/unit/skills.test.ts @@ -100,6 +100,29 @@ describe('parseSkills', () => { }); }); + describe('GitHub repo URLs', () => { + it('passes a repo URL through unchanged (line format)', () => { + assert.deepEqual(parseSkills('https://github.com/anthropics/skills'), [ + 'https://github.com/anthropics/skills', + ]); + }); + + it('passes a repo URL with a subpath through unchanged', () => { + const input = 'https://github.com/anthropics/skills/tree/main/skills/web-design'; + assert.deepEqual(parseSkills(input), [input]); + }); + + it('mixes owner/repo shorthand and repo URLs across lines', () => { + const input = 'apify/agent-skills\nhttps://github.com/anthropics/skills'; + assert.deepEqual(parseSkills(input), ['apify/agent-skills', 'https://github.com/anthropics/skills']); + }); + + it('passes repo URLs through unchanged (JSON array)', () => { + const input = '["apify/agent-skills", "https://github.com/anthropics/skills"]'; + assert.deepEqual(parseSkills(input), ['apify/agent-skills', 'https://github.com/anthropics/skills']); + }); + }); + describe('array input (legacy stringList)', () => { it('cleans and de-duplicates a string array', () => { const input = [' apify/agent-skills ', 'anthropics/skills', 'apify/agent-skills', '']; From e8558c775a11b8d06d2cf679d861596ebda275de Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Tue, 2 Jun 2026 00:19:18 +0200 Subject: [PATCH 17/56] Shrink the sandbox Docker image (#53) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove OpenClaw and shrink the sandbox Docker image - Delete the openclaw proxy Actor and every reference to it (README, example proxy paths, and the leftover hardcoded /openclaw debug logging in the sandbox server). - Runtime stage now installs ttyd's runtime shared libs (libjson-c5, libwebsockets19t64) instead of the heavy -dev packages, dropping their whole transitive header/static-lib chain. - Clean the npm cache in each install layer and use pip --no-cache-dir so download caches don't ship in the image. - Verify ttyd loads its shared libs at build time so a renamed/missing runtime lib fails the build instead of breaking the shell at run time. https://claude.ai/code/session_01CTRd8dBQcaybs3fNjfNR3t * Restore OpenClaw, keep the sandbox image optimizations Reverts the OpenClaw removal: the openclaw proxy Actor and all its references (README, example proxy paths, /openclaw request logging) are back. The sandbox Dockerfile size optimizations from the previous commit are kept — runtime ttyd libs instead of -dev packages, npm cache cleanup, pip --no-cache-dir, and the build-time ttyd shared-lib check. https://claude.ai/code/session_01CTRd8dBQcaybs3fNjfNR3t * Clarify that ttyd is compiled in the builder, then copied to runtime The previous wording ("prebuilt binary") read as if ttyd were downloaded as a prebuilt release. It is compiled from source in the builder stage and only the finished binary is copied into the runtime stage — which is why the runtime needs the shared libs but not the -dev packages or build toolchain. https://claude.ai/code/session_01CTRd8dBQcaybs3fNjfNR3t --------- Co-authored-by: Claude --- sandbox/Dockerfile | 45 +++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/sandbox/Dockerfile b/sandbox/Dockerfile index 6b0f26e..5f9dab9 100644 --- a/sandbox/Dockerfile +++ b/sandbox/Dockerfile @@ -46,7 +46,12 @@ RUN [ -n "$APIFY_IS_AT_HOME" ] || npm run test:unit # Stage 2: Runtime - Node.js image FROM node:24-trixie-slim -# Install required dependencies for sandbox operations +# Install required dependencies for sandbox operations. +# ttyd is compiled from source in the builder stage and copied into this stage +# as a finished binary (see `COPY --from=builder .../ttyd` below), so the runtime +# only needs the *runtime* shared libraries it links against (libjson-c5, +# libwebsockets19t64) — not the `-dev` packages, headers, and build toolchain +# that the compile step required. RUN apt-get update && apt-get install -y \ bash \ curl \ @@ -56,22 +61,23 @@ RUN apt-get update && apt-get install -y \ python3-venv \ python3-pip \ ca-certificates \ - libjson-c-dev \ - libwebsockets-dev \ + libjson-c5 \ + libwebsockets19t64 \ libsecret-1-0 \ procps \ jq \ && rm -rf /var/lib/apt/lists/* -# Install tsx globally for TypeScript execution in execute-code endpoint -RUN npm install -g tsx - -# Install Apify CLI globally -# --ignore-scripts works around apify-client's `only-allow pnpm` preinstall hook -RUN npm install -g --ignore-scripts apify-cli - -# Install Apify MCP CLI globally (package: @apify/mcpc, binary: mcpc) -RUN npm install -g @apify/mcpc +# Install global CLI tooling in a single layer, then drop the npm cache so it +# doesn't bloat the image: +# - tsx: TypeScript execution in the execute-code endpoint +# - apify-cli: --ignore-scripts works around apify-client's `only-allow pnpm` +# preinstall hook +# - @apify/mcpc: Apify MCP CLI (binary: mcpc) +RUN npm install -g tsx \ + && npm install -g --ignore-scripts apify-cli \ + && npm install -g @apify/mcpc \ + && npm cache clean --force # Install AI coding agent CLIs via npm. All three ship platform-specific # native binaries through optional deps; --include=optional ensures the @@ -80,7 +86,8 @@ RUN npm install -g @apify/mcpc RUN npm install -g --include=optional \ @anthropic-ai/claude-code \ @openai/codex \ - opencode-ai + opencode-ai \ + && npm cache clean --force # Create sandbox directory for operations RUN mkdir -p /sandbox && chmod 755 /sandbox @@ -88,8 +95,8 @@ RUN mkdir -p /sandbox && chmod 755 /sandbox # Create Python sandbox directory with venv and pre-install apify-client RUN mkdir -p /sandbox/py && chmod 755 /sandbox/py && \ python3 -m venv /sandbox/py/venv && \ - /sandbox/py/venv/bin/pip install --upgrade pip && \ - /sandbox/py/venv/bin/pip install apify-client && \ + /sandbox/py/venv/bin/pip install --no-cache-dir --upgrade pip && \ + /sandbox/py/venv/bin/pip install --no-cache-dir apify-client && \ echo "apify-client pre-installed in Python venv" # Create JS/TS sandbox directory with proper package.json and pre-install apify-client @@ -97,6 +104,7 @@ RUN mkdir -p /sandbox/js-ts && chmod 755 /sandbox/js-ts && \ cd /sandbox/js-ts && \ echo '{"name":"apify-sandbox-js-ts","version":"1.0.0","description":"Sandbox for JS/TS code execution","type":"module","dependencies":{"apify-client":"*"}}' > package.json && \ npm install --ignore-scripts && \ + npm cache clean --force && \ echo "apify-client pre-installed in Node.js environment" # Copy AGENTS.md to sandbox for AI coding agents @@ -118,6 +126,11 @@ COPY --from=builder /build/dist /app/dist # Copy ttyd binary from builder COPY --from=builder /usr/local/bin/ttyd /usr/local/bin/ttyd +# Verify the copied-in ttyd binary can load its runtime shared libraries +# (libwebsockets, libjson-c). Fails the build early if a runtime lib package is +# missing or renamed, rather than shipping a broken interactive shell. +RUN ttyd --version + # Copy package.json and production node_modules from builder COPY --from=builder /build/package.json /app/package.json COPY --from=builder /build/package-lock.json /app/package-lock.json @@ -127,7 +140,7 @@ RUN mkdir -p /root/.config/opencode COPY --from=builder /build/artifacts/opencode.json /root/.config/opencode/opencode.json # Install production dependencies only -RUN npm install --production +RUN npm install --production && npm cache clean --force # Add local bin to PATH for CLI tools (Claude at ~/.local/bin, OpenCode at ~/.opencode/bin) ENV PATH="/root/.local/bin:/root/.opencode/bin:$PATH" From 5755ce397793f7cb2e16f9e390faa35ef4d22d4b Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Wed, 3 Jun 2026 22:46:56 +0200 Subject: [PATCH 18/56] Add libuv1t64 runtime lib so ttyd loads in sandbox image (#54) The runtime stage installed libjson-c5 and libwebsockets19t64 but not libuv, which ttyd also links against. The `ttyd --version` smoke test failed with 'libuv.so.1: cannot open shared object file', breaking the build. Install libuv1t64 (trixie's t64-renamed package) alongside the other ttyd runtime libs. Co-authored-by: Claude --- sandbox/Dockerfile | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sandbox/Dockerfile b/sandbox/Dockerfile index 5f9dab9..fc47ec1 100644 --- a/sandbox/Dockerfile +++ b/sandbox/Dockerfile @@ -50,8 +50,9 @@ FROM node:24-trixie-slim # ttyd is compiled from source in the builder stage and copied into this stage # as a finished binary (see `COPY --from=builder .../ttyd` below), so the runtime # only needs the *runtime* shared libraries it links against (libjson-c5, -# libwebsockets19t64) — not the `-dev` packages, headers, and build toolchain -# that the compile step required. +# libwebsockets19t64, libuv1t64) — not the `-dev` packages, headers, and build +# toolchain that the compile step required. The `t64` suffix reflects Debian +# trixie's 64-bit time_t transition. RUN apt-get update && apt-get install -y \ bash \ curl \ @@ -63,6 +64,7 @@ RUN apt-get update && apt-get install -y \ ca-certificates \ libjson-c5 \ libwebsockets19t64 \ + libuv1t64 \ libsecret-1-0 \ procps \ jq \ @@ -127,8 +129,8 @@ COPY --from=builder /build/dist /app/dist COPY --from=builder /usr/local/bin/ttyd /usr/local/bin/ttyd # Verify the copied-in ttyd binary can load its runtime shared libraries -# (libwebsockets, libjson-c). Fails the build early if a runtime lib package is -# missing or renamed, rather than shipping a broken interactive shell. +# (libwebsockets, libjson-c, libuv). Fails the build early if a runtime lib +# package is missing or renamed, rather than shipping a broken interactive shell. RUN ttyd --version # Copy package.json and production node_modules from builder From 698448f9c0c83b2160b4e24d3a85f27db5653c6b Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Wed, 3 Jun 2026 23:15:45 +0200 Subject: [PATCH 19/56] Surface ttyd startup failures behind the Shell Proxy Error (#55) - Capture ttyd's stdout/stderr (was stdio:'ignore') and log the real reason on exit, so a startup crash like the missing-libuv regression no longer hides behind a bare "ttyd process exited {code:1}". - /shell now returns 503 with ttyd's last output when the backend is down, instead of an opaque 500 "Shell Proxy Error". - Add the missing wsProxy 'error' handler so a WebSocket upgrade to a down ttyd can't escalate to an uncaught exception that takes down the server; restarts now back off exponentially instead of a fixed 5s loop. - Extract unit-tested helpers into ttyd.ts (crash classification, restart backoff, output buffering, unavailable-shell message). https://claude.ai/code/session_01PYQioHAT3hCejhwdY8HhLn Co-authored-by: Claude --- sandbox/src/main.ts | 88 ++++++++++++++++++++++++++++++--- sandbox/src/ttyd.ts | 65 ++++++++++++++++++++++++ sandbox/tests/unit/ttyd.test.ts | 74 +++++++++++++++++++++++++++ 3 files changed, 220 insertions(+), 7 deletions(-) create mode 100644 sandbox/src/ttyd.ts create mode 100644 sandbox/tests/unit/ttyd.test.ts diff --git a/sandbox/src/main.ts b/sandbox/src/main.ts index cd09680..0db4af6 100644 --- a/sandbox/src/main.ts +++ b/sandbox/src/main.ts @@ -42,6 +42,13 @@ import { parseSkills } from './skills.js'; import { getBrowsePageHTML } from './templates/browse.js'; import { getLandingPageHTML, getLLMsMarkdown } from './templates/landing.js'; import { SANDBOX_BASHRC, WELCOME_SCRIPT } from './templates/shell.js'; +import { + appendTtydOutput, + buildShellUnavailableMessage, + isTtydStartupCrash, + nextTtydRestartDelayMs, + TTYD_RESTART_MIN_MS, +} from './ttyd.js'; import type { ActorInput, ProxyMapping } from './types.js'; // Track initialization state @@ -875,24 +882,71 @@ app.post('/exec', async (req: Request, res: Response) => { // ============================================================================ const shellPort = 7681; +// ttyd's last words: the tail of its stdout/stderr (or a spawn error). Recorded +// so a crash — e.g. a missing shared library — shows up in the Actor log and in +// the /shell proxy response instead of a bare exit code. The delay grows as ttyd +// keeps failing to start (see nextTtydRestartDelayMs). +let lastTtydError = ''; +let ttydRestartDelayMs = TTYD_RESTART_MIN_MS; + // Spawn ttyd process const spawnTtyd = () => { log.info('Spawning ttyd process...', { port: shellPort }); + const startedAt = Date.now(); - // Run ttyd with custom bashrc for better UX and environment alignment + // Run ttyd with custom bashrc for better UX and environment alignment. Pipe + // its stdio (rather than ignoring it) so a startup failure is captured. const ttyd = spawn('ttyd', ['-p', shellPort.toString(), '-a', '-W', 'bash', '--rcfile', '/app/sandbox_bashrc'], { - stdio: 'ignore', + stdio: ['ignore', 'pipe', 'pipe'], cwd: SANDBOX_DIR, env: { ...process.env }, }); + // Keep only the tail of ttyd's output — its startup/error messages are short. + let recentOutput = ''; + const capture = (chunk: Buffer): void => { + recentOutput = appendTtydOutput(recentOutput, chunk.toString()); + }; + ttyd.stdout?.on('data', capture); + ttyd.stderr?.on('data', capture); + + // Schedule exactly one restart per spawn: a failed spawn emits 'error' (no + // 'exit'), a started process emits 'exit'; guard against both firing. + let settled = false; + const restartAfter = (crashed: boolean): void => { + if (settled) return; + settled = true; + const delay = crashed ? ttydRestartDelayMs : TTYD_RESTART_MIN_MS; + ttydRestartDelayMs = nextTtydRestartDelayMs(ttydRestartDelayMs, crashed); + setTimeout(spawnTtyd, delay); + }; + ttyd.on('error', (err) => { + lastTtydError = err.message; log.error('Failed to start ttyd', { error: err.message }); + restartAfter(true); }); - ttyd.on('exit', (code) => { - log.warning('ttyd process exited', { code }); - setTimeout(spawnTtyd, 5000); + ttyd.on('exit', (code, signal) => { + const aliveMs = Date.now() - startedAt; + const output = recentOutput.trim(); + if (output) lastTtydError = output; + + // A fast exit means ttyd never really came up (missing shared library, + // port already bound, bad args). Shout, since the old fixed-5s retry with + // no detail produced an invisible crash loop behind "Shell Proxy Error". + const crashed = isTtydStartupCrash(aliveMs); + if (crashed) { + log.error('ttyd exited immediately — interactive shell is unavailable', { + code, + signal, + aliveMs, + output: output || '(no output captured)', + }); + } else { + log.warning('ttyd process exited; restarting', { code, signal, aliveMs }); + } + restartAfter(crashed); }); }; @@ -1008,9 +1062,17 @@ app.all('/shell{*rest}', (req, res) => { }); proxyReq.on('error', (err) => { - log.error('Manual proxy error', { error: err.message }); + // ECONNREFUSED means ttyd isn't listening — it almost always crashed on + // startup (see spawnTtyd, which records its last output in lastTtydError). + // Surface that as a 503 instead of an opaque 500 so the cause is visible. + const ttydDown = (err as NodeJS.ErrnoException).code === 'ECONNREFUSED'; + log.error('Manual proxy error', { error: err.message, ttydDown }); if (!res.headersSent) { - res.status(500).send('Shell Proxy Error'); + if (ttydDown) { + res.status(503).type('text/plain').send(buildShellUnavailableMessage(lastTtydError)); + } else { + res.status(502).type('text/plain').send(`Shell proxy error: ${err.message}`); + } } }); @@ -1023,6 +1085,18 @@ const wsProxy = httpProxy.createProxyServer({ ws: true, }); +// Without this handler a WebSocket upgrade to a down ttyd emits an 'error' with +// no listener, which Node escalates to an uncaught exception that can take down +// the whole server. ttyd is restarted by spawnTtyd; just close the browser +// socket so the terminal shows its reconnect overlay and retries. +wsProxy.on('error', (err, _req, resOrSocket) => { + log.warning('Shell WebSocket proxy error', { error: (err as Error).message }); + const socket = resOrSocket as Duplex | undefined; + if (socket && typeof socket.destroy === 'function' && !socket.destroyed) { + socket.destroy(); + } +}); + server.on('upgrade', (req, socket, head) => { if (req.url?.startsWith('/shell')) { req.url = req.url.replace(/^\/shell/, '') || '/'; diff --git a/sandbox/src/ttyd.ts b/sandbox/src/ttyd.ts new file mode 100644 index 0000000..f832f3d --- /dev/null +++ b/sandbox/src/ttyd.ts @@ -0,0 +1,65 @@ +/** + * Supervision helpers for the ttyd process that backs the interactive /shell + * terminal. The process is spawned and restarted in main.ts; the pure decision + * logic lives here so it can be unit-tested without spawning a real process. + * + * Why this exists: ttyd used to be spawned with `stdio: 'ignore'` and restarted + * on a fixed 5s timer, so a startup failure (e.g. a missing shared library — + * the libuv regression that broke the shell) surfaced only as a repeating + * "ttyd process exited {code:1}" with no reason, while the /shell proxy returned + * an opaque 500 "Shell Proxy Error". These helpers support capturing ttyd's + * output, backing off between restarts, and telling the user why the shell is + * down. + */ + +/** Restart backoff bounds (ms). */ +export const TTYD_RESTART_MIN_MS = 1000; +export const TTYD_RESTART_MAX_MS = 30000; + +/** + * An exit within this window of startup means ttyd never really came up (bad + * args, missing shared library, port already in use) rather than a long-lived + * server that later died. We back off and log loudly in that case. + */ +export const TTYD_CRASH_WINDOW_MS = 2000; + +/** How many characters of ttyd's recent stdout/stderr to retain for diagnostics. */ +export const TTYD_OUTPUT_LIMIT = 2048; + +/** True if ttyd exited fast enough to count as a startup crash rather than a normal exit. */ +export const isTtydStartupCrash = (aliveMs: number): boolean => aliveMs < TTYD_CRASH_WINDOW_MS; + +/** + * The delay to use the next time ttyd needs restarting. Startup crashes back off + * exponentially up to the cap, so a permanently broken ttyd doesn't spam the log; + * a process that ran for a while before exiting resets to the minimum so it + * recovers promptly. + */ +export const nextTtydRestartDelayMs = (currentDelayMs: number, isCrash: boolean): number => { + if (!isCrash) return TTYD_RESTART_MIN_MS; + const doubled = Math.max(currentDelayMs, TTYD_RESTART_MIN_MS) * 2; + return Math.min(doubled, TTYD_RESTART_MAX_MS); +}; + +/** Append a chunk to the rolling output buffer, keeping only the last `limit` characters. */ +export const appendTtydOutput = (buffer: string, chunk: string, limit = TTYD_OUTPUT_LIMIT): string => + (buffer + chunk).slice(-limit); + +/** + * Plain-text body returned by the /shell proxy when ttyd isn't reachable. Includes + * ttyd's last output when we have it, so the cause (e.g. a missing shared library) + * is visible in the browser instead of an opaque error. + */ +export const buildShellUnavailableMessage = (lastTtydOutput: string): string => { + const lines = ['Interactive shell is not available — the terminal backend (ttyd) is not running.']; + const detail = lastTtydOutput.trim(); + if (detail) { + lines.push('', 'Last ttyd output:', detail); + } + lines.push( + '', + 'The sandbox keeps trying to restart it — reload this page in a few seconds.', + 'If the problem persists, check the Actor run log.', + ); + return `${lines.join('\n')}\n`; +}; diff --git a/sandbox/tests/unit/ttyd.test.ts b/sandbox/tests/unit/ttyd.test.ts new file mode 100644 index 0000000..12d3672 --- /dev/null +++ b/sandbox/tests/unit/ttyd.test.ts @@ -0,0 +1,74 @@ +/* eslint-disable @typescript-eslint/no-floating-promises -- node:test's describe/it return promises by design */ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; + +import { + appendTtydOutput, + buildShellUnavailableMessage, + isTtydStartupCrash, + nextTtydRestartDelayMs, + TTYD_CRASH_WINDOW_MS, + TTYD_OUTPUT_LIMIT, + TTYD_RESTART_MAX_MS, + TTYD_RESTART_MIN_MS, +} from '../../src/ttyd.js'; + +describe('isTtydStartupCrash', () => { + it('treats a fast exit as a startup crash', () => { + assert.equal(isTtydStartupCrash(0), true); + assert.equal(isTtydStartupCrash(TTYD_CRASH_WINDOW_MS - 1), true); + }); + + it('treats a long-lived process exit as a normal exit', () => { + assert.equal(isTtydStartupCrash(TTYD_CRASH_WINDOW_MS), false); + assert.equal(isTtydStartupCrash(60_000), false); + }); +}); + +describe('nextTtydRestartDelayMs', () => { + it('doubles the delay on each crash, capped at the maximum', () => { + let delay = TTYD_RESTART_MIN_MS; + const seen: number[] = []; + for (let i = 0; i < 8; i++) { + seen.push(delay); + delay = nextTtydRestartDelayMs(delay, true); + } + assert.deepEqual(seen, [1000, 2000, 4000, 8000, 16000, 30000, 30000, 30000]); + assert.ok(seen.every((d) => d <= TTYD_RESTART_MAX_MS)); + }); + + it('resets to the minimum after a normal exit', () => { + assert.equal(nextTtydRestartDelayMs(TTYD_RESTART_MAX_MS, false), TTYD_RESTART_MIN_MS); + }); +}); + +describe('appendTtydOutput', () => { + it('appends chunks', () => { + assert.equal(appendTtydOutput('foo', 'bar'), 'foobar'); + }); + + it('keeps only the last `limit` characters', () => { + assert.equal(appendTtydOutput('abcd', 'ef', 3), 'def'); + }); + + it('defaults to the output limit and never grows unbounded', () => { + const out = appendTtydOutput('x'.repeat(TTYD_OUTPUT_LIMIT), 'y'.repeat(100)); + assert.equal(out.length, TTYD_OUTPUT_LIMIT); + assert.ok(out.endsWith('y'.repeat(100))); + }); +}); + +describe('buildShellUnavailableMessage', () => { + it('includes ttyd output when available', () => { + const msg = buildShellUnavailableMessage('ttyd: error while loading shared libraries: libuv.so.1'); + assert.match(msg, /terminal backend \(ttyd\) is not running/); + assert.match(msg, /Last ttyd output:/); + assert.match(msg, /libuv\.so\.1/); + }); + + it('omits the output section when there is nothing to show', () => { + const msg = buildShellUnavailableMessage(' '); + assert.doesNotMatch(msg, /Last ttyd output:/); + assert.match(msg, /reload this page/); + }); +}); From ce5a06ecfd5e828bbf912dbb2aeef39cd81de49f Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Thu, 4 Jun 2026 12:09:42 +0200 Subject: [PATCH 20/56] Install libwebsockets evlib-uv plugin so the ttyd shell starts (#56) The image shrink (#53) trimmed the runtime libs down to libwebsockets19t64, dropping the libuv event-loop plugin that libwebsockets dlopen()s when ttyd creates its context. ttyd died with "lws_create_context: failed to load evlib_uv" on every start, so the shell only ever showed the proxy error. It's loaded at runtime, not linked, so neither `ldd` nor `ttyd --version` (nor #54's libuv fix) caught it. Verified on the live image: installing libwebsockets-evlib-uv lets ttyd reach "Listening on port". Also replace the `ttyd --version` build check with a real server-start smoke test so a missing plugin or runtime lib fails the build instead of the shell. https://claude.ai/code/session_01PYQioHAT3hCejhwdY8HhLn Co-authored-by: Claude --- sandbox/Dockerfile | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/sandbox/Dockerfile b/sandbox/Dockerfile index fc47ec1..2282557 100644 --- a/sandbox/Dockerfile +++ b/sandbox/Dockerfile @@ -49,10 +49,15 @@ FROM node:24-trixie-slim # Install required dependencies for sandbox operations. # ttyd is compiled from source in the builder stage and copied into this stage # as a finished binary (see `COPY --from=builder .../ttyd` below), so the runtime -# only needs the *runtime* shared libraries it links against (libjson-c5, -# libwebsockets19t64, libuv1t64) — not the `-dev` packages, headers, and build -# toolchain that the compile step required. The `t64` suffix reflects Debian -# trixie's 64-bit time_t transition. +# only needs the shared libraries it uses — not the `-dev` packages, headers, and +# build toolchain that the compile step required: +# - libjson-c5, libwebsockets19t64, libuv1t64: libraries ttyd links against +# (the `t64` suffix reflects Debian trixie's 64-bit time_t transition). +# - libwebsockets-evlib-uv: the libuv event-loop plugin that libwebsockets +# dlopen()s when ttyd creates its context. It is NOT a link-time dependency, +# so `ldd ttyd` and `ttyd --version` won't reveal it's missing — but without +# it ttyd dies at runtime with "lws_create_context: failed to load evlib_uv" +# and the interactive shell never starts. Keep it when trimming this list. RUN apt-get update && apt-get install -y \ bash \ curl \ @@ -65,6 +70,7 @@ RUN apt-get update && apt-get install -y \ libjson-c5 \ libwebsockets19t64 \ libuv1t64 \ + libwebsockets-evlib-uv \ libsecret-1-0 \ procps \ jq \ @@ -128,10 +134,15 @@ COPY --from=builder /build/dist /app/dist # Copy ttyd binary from builder COPY --from=builder /usr/local/bin/ttyd /usr/local/bin/ttyd -# Verify the copied-in ttyd binary can load its runtime shared libraries -# (libwebsockets, libjson-c, libuv). Fails the build early if a runtime lib -# package is missing or renamed, rather than shipping a broken interactive shell. -RUN ttyd --version +# Smoke-test that ttyd actually starts its server — not just that it loads +# (`ttyd --version`). Creating the libwebsockets context dlopen()s the libuv +# event-loop plugin (libwebsockets-evlib-uv), which `--version` never exercises, +# so a missing plugin or runtime lib fails the build here instead of breaking the +# shell at run time. ttyd is a long-running server, so cap it with `timeout` and +# assert it reached "Listening on port". +RUN OUTPUT="$(timeout 5 ttyd -p 7681 -a -W bash 2>&1 || true)"; \ + echo "$OUTPUT"; \ + echo "$OUTPUT" | grep -q "Listening on port" # Copy package.json and production node_modules from builder COPY --from=builder /build/package.json /app/package.json From 8ccf31f64e0235181425ecf638f1d1282ee5a8c4 Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Sat, 6 Jun 2026 17:23:48 +0200 Subject: [PATCH 21/56] Explain terminal disconnects via ttyd's reconnect overlay, client-side (#59) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The server-side shutdown banner couldn't catch the common stops. The Apify SDK installs no SIGTERM/SIGINT handlers (it's driven by the platform events WebSocket), so the `listenerCount(signal) > 0` guard was always false and the signal listener never attached — run timeouts, hard aborts and scale-downs showed nothing. A dying process also can't reliably flush a banner. Instead, inject a small script into ttyd's page that relabels its own reconnect overlay in the browser, where every disconnect is observable: "Connection lost — press ⏎ to reconnect", and "Actor probably finished" once a retry fails. Drop the dead signal-handler block; keep the `migrating`/ `aborting` event banners, which do arrive in advance. https://claude.ai/code/session_0127nqswxmANoa8VfkmRArmY Co-authored-by: Claude --- sandbox/src/main.ts | 63 ++++++++++++++++------ sandbox/src/templates/shell.ts | 92 ++++++++++++++++++++++++++++++++ sandbox/tests/unit/shell.test.ts | 60 +++++++++++++++++++++ 3 files changed, 198 insertions(+), 17 deletions(-) create mode 100644 sandbox/tests/unit/shell.test.ts diff --git a/sandbox/src/main.ts b/sandbox/src/main.ts index 0db4af6..4e4436e 100644 --- a/sandbox/src/main.ts +++ b/sandbox/src/main.ts @@ -41,7 +41,7 @@ import { broadcastToTerminals, buildShutdownBanner, encodeTtydOutputMessage } fr import { parseSkills } from './skills.js'; import { getBrowsePageHTML } from './templates/browse.js'; import { getLandingPageHTML, getLLMsMarkdown } from './templates/landing.js'; -import { SANDBOX_BASHRC, WELCOME_SCRIPT } from './templates/shell.js'; +import { injectTerminalReconnectScript, SANDBOX_BASHRC, WELCOME_SCRIPT } from './templates/shell.js'; import { appendTtydOutput, buildShellUnavailableMessage, @@ -1017,10 +1017,17 @@ const shutdownWithNotice = async (reason: string): Promise => { await Actor.exit({ statusMessage: reason }); }; -// Surface platform-initiated stops in the terminal too. The migration/abort -// events are advance notices, and the platform stops the container with a -// termination signal; in every case the banner is written synchronously so it -// reaches the browser before the process exits. +// Surface the platform stops the SDK tells us about in advance: `migrating` +// (before a host migration) and `aborting` (only on a *graceful* abort). Both +// arrive over the SDK's events WebSocket, ahead of the process being torn down, +// so the banner has time to flush. +// +// Everything else — run timeout, a hard abort, platform scale-down — kills the +// container with a signal and no advance event (the SDK installs no SIGTERM/ +// SIGINT handlers; it's event-driven). There's no reliable way to flush a banner +// from a dying process, so those cases are covered in the browser instead: the +// reconnect overlay injected into ttyd's page (see injectTerminalReconnectScript) +// relabels the disconnect and reports "Actor probably finished" on a failed retry. if (!isLocalMode) { Actor.on('migrating', () => { notifyTerminalsOfShutdown('Actor is migrating to a new host and will resume shortly. Reconnect in a moment.'); @@ -1028,14 +1035,6 @@ if (!isLocalMode) { Actor.on('aborting', () => { notifyTerminalsOfShutdown('Actor run is being aborted.'); }); - - // Only hook signals the SDK already handles, so we add the banner without - // taking over termination — a lone SIGTERM listener would suppress Node's - // default exit and leave the container hanging until the platform SIGKILLs it. - const notifyOnSignal = (): void => notifyTerminalsOfShutdown('Actor run is stopping.'); - for (const signal of ['SIGINT', 'SIGTERM'] as const) { - if (process.listenerCount(signal) > 0) process.prependListener(signal, notifyOnSignal); - } } // Manual HTTP Proxy for ttyd @@ -1046,19 +1045,49 @@ app.all('/shell{*rest}', (req, res) => { path = `/${ path}`; } path = translateLaunchParam(path); + // Ask ttyd for an uncompressed response so the terminal HTML can be rewritten + // (see injectTerminalReconnectScript below). ttyd's assets are tiny, so losing + // gzip here is negligible. + const headers = { ...req.headers, 'accept-encoding': 'identity' }; const options = { hostname: '127.0.0.1', port: shellPort, path, method: req.method, - headers: req.headers, + headers, }; const proxyReq = http.request(options, (proxyRes) => { - if (proxyRes.statusCode) { - res.writeHead(proxyRes.statusCode, proxyRes.headers); + // Inject the client-side reconnect notice into ttyd's terminal page. The + // server can't reliably push a shutdown message as the container is killed, + // so the browser relabels ttyd's reconnect overlay instead. Only the HTML + // document is rewritten; every other asset and status is piped through. + const isHtml = (proxyRes.headers['content-type'] || '').includes('text/html'); + if (!isHtml) { + if (proxyRes.statusCode) { + res.writeHead(proxyRes.statusCode, proxyRes.headers); + } + proxyRes.pipe(res); + return; } - proxyRes.pipe(res); + + const chunks: Buffer[] = []; + proxyRes.on('data', (chunk: Buffer) => chunks.push(chunk)); + proxyRes.on('end', () => { + const html = injectTerminalReconnectScript(Buffer.concat(chunks).toString('utf8')); + const outHeaders = { ...proxyRes.headers }; + // The body length changed and is now fixed: drop any stale length/ + // encoding framing and set the real one. + delete outHeaders['content-encoding']; + delete outHeaders['transfer-encoding']; + outHeaders['content-length'] = Buffer.byteLength(html).toString(); + res.writeHead(proxyRes.statusCode || 200, outHeaders); + res.end(html); + }); + proxyRes.on('error', (err) => { + log.error('Shell proxy response error', { error: err.message }); + if (!res.headersSent) res.status(502).type('text/plain').send('Shell proxy error'); + }); }); proxyReq.on('error', (err) => { diff --git a/sandbox/src/templates/shell.ts b/sandbox/src/templates/shell.ts index 9abe6f1..163d4ad 100644 --- a/sandbox/src/templates/shell.ts +++ b/sandbox/src/templates/shell.ts @@ -111,3 +111,95 @@ if [ -z "$SANDBOX_WELCOME_SHOWN" ] && [ -f /app/welcome.sh ]; then bash /app/welcome.sh fi `; + +/** + * Message shown when the terminal WebSocket drops but a reconnect may still work + * (replaces ttyd's bare "Press ⏎ to Reconnect"). + */ +export const TERMINAL_DISCONNECT_MESSAGE = 'Connection lost — press ⏎ to reconnect'; + +/** + * Message shown when a reconnect attempt fails — the Actor run has most likely + * stopped (idle timeout, abort, run timeout, migration). Pressing ⏎ still retries. + */ +export const TERMINAL_FINISHED_MESSAGE = 'Actor probably finished — press ⏎ to retry'; + +/** + * Browser script injected into ttyd's terminal page to explain *why* the session + * ended, client-side. + * + * Why client-side: the terminal is proxied (browser ↔ Actor ↔ ttyd), and most + * stops (run timeout, hard abort, platform scale-down) kill the container with a + * signal — there is no advance Actor event and no time to flush a banner before + * the process dies. The browser, however, can always see the socket drop and any + * failed reconnect, so we relabel ttyd's own reconnect overlay here. + * + * ttyd (1.7.7) drives a single overlay
      via `overlayAddon.showOverlay(text)` + * (html/src/components/terminal/xterm/index.ts). On a drop it shows + * "Press ⏎ to Reconnect"; a retry shows "Reconnecting..."; a fresh connection + * shows "Reconnected". We watch those exact strings: the first prompt means the + * link dropped (recoverable), but a prompt that follows a "Reconnecting..." means + * the retry failed — so the Actor is probably gone. The relabel is cosmetic; + * ttyd's Enter-to-reconnect handler stays intact, so retry still works. + */ +export const RECONNECT_OVERLAY_SCRIPT = `(function () { + var LOST = ${JSON.stringify(TERMINAL_DISCONNECT_MESSAGE)}; + var FINISHED = ${JSON.stringify(TERMINAL_FINISHED_MESSAGE)}; + + // ttyd's exact overlay strings; \\u23ce is the ⏎ glyph it uses. + var PRESS_ENTER = 'Press \\u23ce to Reconnect'; + var RECONNECTING = 'Reconnecting...'; + var RECONNECTED = 'Reconnected'; + + // True once a reconnect has been attempted since the last live connection. If + // we then fall back to the reconnect prompt, the attempt failed. + var triedReconnect = false; + + function relabel(el) { + var text = el.textContent; + if (text === RECONNECTING) { + triedReconnect = true; + } else if (text === RECONNECTED) { + triedReconnect = false; + } else if (text === PRESS_ENTER) { + var msg = triedReconnect ? FINISHED : LOST; + triedReconnect = false; + if (el.textContent !== msg) el.textContent = msg; + } + } + + // The overlay text is set via textContent, so a change shows up as a childList + // mutation on the overlay element (or as the element being (re)attached). + function elementOf(node) { + if (!node) return null; + return node.nodeType === 3 ? node.parentNode : node; + } + + var observer = new MutationObserver(function (records) { + for (var i = 0; i < records.length; i++) { + var r = records[i]; + var target = elementOf(r.target); + if (target && target.nodeType === 1) relabel(target); + for (var j = 0; j < r.addedNodes.length; j++) { + var added = elementOf(r.addedNodes[j]); + if (added && added.nodeType === 1) relabel(added); + } + } + }); + observer.observe(document.documentElement, { childList: true, subtree: true }); +})();`; + +/** + * Inject the reconnect-overlay script into ttyd's HTML page. The script only + * reacts to overlay text that appears long after load, so placement isn't + * critical — prefer right after , falling back to , then end of doc. + * + * @param html - The HTML document served by ttyd. + * @returns The HTML with the script injected. + */ +export const injectTerminalReconnectScript = (html: string): string => { + const tag = ``; + if (/]*>/i.test(html)) return html.replace(/]*>/i, (match) => match + tag); + if (/]*>/i.test(html)) return html.replace(/]*>/i, (match) => match + tag); + return html + tag; +}; diff --git a/sandbox/tests/unit/shell.test.ts b/sandbox/tests/unit/shell.test.ts new file mode 100644 index 0000000..1855c25 --- /dev/null +++ b/sandbox/tests/unit/shell.test.ts @@ -0,0 +1,60 @@ +/* eslint-disable @typescript-eslint/no-floating-promises -- node:test's describe/it return promises by design */ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; + +import { + injectTerminalReconnectScript, + RECONNECT_OVERLAY_SCRIPT, + TERMINAL_DISCONNECT_MESSAGE, + TERMINAL_FINISHED_MESSAGE, +} from '../../src/templates/shell.js'; + +describe('injectTerminalReconnectScript', () => { + it('injects the script right after ', () => { + const html = 'ttyd'; + const out = injectTerminalReconnectScript(html); + assert.match(out, /')); + }); + + it('injects only once (single match)', () => { + const out = injectTerminalReconnectScript(''); + assert.equal(out.match(/ diff --git a/sandbox/src/types.ts b/sandbox/src/types.ts index 8ca4034..b0df6e1 100644 --- a/sandbox/src/types.ts +++ b/sandbox/src/types.ts @@ -3,12 +3,13 @@ */ /** - * Proxy mapping configuration for routing requests to local servers + * A bridge exposes a local server (running inside the sandbox) at a public + * URL path on the container. */ -export interface ProxyMapping { +export interface Bridge { /** Exposed URL path on the container (e.g., /openclaw) */ path: string; - /** Full URL of the local service to proxy to (e.g., http://127.0.0.1:18789/openclaw) */ + /** Full URL of the local service to forward to (e.g., http://127.0.0.1:18789/openclaw) */ target: string; } @@ -56,11 +57,11 @@ export interface ActorInput { idleTimeoutSecs?: number; /** - * Proxy mappings for routing requests to local servers - * Maps exposed paths to local service URLs + * Bridges exposing local servers at public URL paths on the container. + * Maps exposed paths to local service URLs. * Example: [{ "path": "/openclaw", "target": "http://127.0.0.1:18789/openclaw" }] */ - proxyMappings?: ProxyMapping[]; + bridges?: Bridge[]; /** * MCP Connector IDs the Actor can use. At runtime the platform exposes From 59090405a3a7c8ca02a29619dc3b2f1ebe530687 Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Mon, 8 Jun 2026 17:09:46 +0200 Subject: [PATCH 43/56] Squeeze welcome banner ASCII art horizontally (#73) The Apify logo + APIFY wordmark banner rendered ~122 cols wide for 12 rows; with a terminal's ~2:1 cell ratio that read as a stretched rectangle. Drop one visible column in four across all rows (preserving vertical alignment and the original per-segment colors), bringing the logo to ~24 cols so it renders square and the banner down to ~92 cols. https://claude.ai/code/session_01MkQRhJaVpC11g3LTBDmLmG Co-authored-by: Claude --- sandbox/src/templates/shell.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/sandbox/src/templates/shell.ts b/sandbox/src/templates/shell.ts index 669d36c..58759f2 100644 --- a/sandbox/src/templates/shell.ts +++ b/sandbox/src/templates/shell.ts @@ -18,18 +18,18 @@ NC='\\033[0m' # No Color BOLD='\\033[1m' # Print ASCII Art (Apify logo) -echo -e "\${GREEN}GGGGGGGGGGGGGG.\${NC} \${BLUE}bBBBBBBBBBBBBBb\${NC} \${WHITE}+++++\${NC} \${WHITE}.+++####+#+\${NC}" -echo -e "\${GREEN}GGGGGGGGGGGGg\${NC} \${BLUE}.BBBBBBBBBBBBb\${NC} \${WHITE}#####\${NC} \${WHITE}+###########+\${NC}" -echo -e "\${GREEN}GGGGGGGGGGg\${NC} \${BLUE}.BBBBBBBBBBb\${NC} \${WHITE}...\${NC} \${WHITE}####+\${NC}" -echo -e "\${GREEN}GGGGGGGGG.\${NC} \${BLUE}bBBBBBBBBb\${NC} \${WHITE}.+#########++\${NC} \${WHITE}.###+.+#######+.\${NC} \${WHITE}+###+\${NC} \${WHITE}.#################+\${NC} \${WHITE}+###+\${NC}" -echo -e "\${GREEN}GGGGGGG.\${NC} \${BLUE}bBBBBBBb\${NC} \${WHITE}+###+..\${NC} \${WHITE}.+####.\${NC} \${WHITE}+######+++++#####.\${NC} \${WHITE}+####\${NC} \${WHITE}.++++#####+++++####+\${NC} \${WHITE}####+\${NC}" -echo -e "\${GREEN}GGGGG.\${NC} \${BLUE}bBBBBb\${NC} \${WHITE}..++++++++#####\${NC} \${WHITE}+####\${NC} \${WHITE}.####.\${NC} \${WHITE}+###+\${NC} \${WHITE}####.\${NC} \${WHITE}#####\${NC} \${WHITE}.####.\${NC}" -echo -e "\${GREEN}GGGg\${NC} \${ORANGE}.oOOo.\${NC} \${BLUE}bBBb\${NC} \${WHITE}#####+++++++####\${NC} \${WHITE}+####\${NC} \${WHITE}####.\${NC} \${WHITE}+###+\${NC} \${WHITE}####+\${NC} \${WHITE}+####\${NC} \${WHITE}+####\${NC}" -echo -e "\${GREEN}Gg\${NC} \${ORANGE}oOOOOOOOOo\${NC} \${BLUE}.Bb\${NC} \${WHITE}.####\${NC} \${WHITE}.#####\${NC} \${WHITE}+#####+.\${NC} \${WHITE}.+####+\${NC} \${WHITE}+###+\${NC} \${WHITE}####+\${NC} \${WHITE}.#######+\${NC}" -echo -e " \${ORANGE}oOOOOOOOOOOOOOO.\${NC} \${WHITE}.+#########+#####\${NC} \${WHITE}+####+#########+.\${NC} \${WHITE}+###+\${NC} \${WHITE}####+\${NC} \${WHITE}#####+\${NC}" -echo -e " \${ORANGE}.OOOOOOOOOOOOOOOOOOOo.\${NC} \${WHITE}...\${NC} \${WHITE}+####\${NC} \${WHITE}...\${NC} \${WHITE}####.\${NC}" -echo -e " \${ORANGE}.oOOOOOOOOOOOOOOOOOOOOOOOOo.\${NC} \${WHITE}+####\${NC} \${WHITE}.####.\${NC}" -echo -e " \${ORANGE}oOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO.\${NC} \${WHITE}.++++\${NC} \${WHITE}++++\${NC}" +echo -e "\${GREEN}GGGGGGGGGGG.\${NC} \${BLUE}BBBBBBBBBBb\${NC} \${WHITE}++++\${NC} \${WHITE}.++###+#\${NC}" +echo -e "\${GREEN}GGGGGGGGGg\${NC} \${BLUE}.BBBBBBBBBb\${NC} \${WHITE}####\${NC} \${WHITE}#########\${NC}" +echo -e "\${GREEN}GGGGGGGGg\${NC} \${BLUE}.BBBBBBBb\${NC} \${WHITE}..\${NC} \${WHITE}###\${NC}" +echo -e "\${GREEN}GGGGGGG.\${NC} \${BLUE}bBBBBBBb\${NC} \${WHITE}+#######+\${NC} \${WHITE}.##+.+#####+\${NC} \${WHITE}+##+\${NC} \${WHITE}.############+\${NC} \${WHITE}+##+\${NC}" +echo -e "\${GREEN}GGGGGG\${NC} \${BLUE}bBBBBb\${NC} \${WHITE}+##+.\${NC} \${WHITE}+###.\${NC} \${WHITE}+#####+++####.\${NC} \${WHITE}+###\${NC} \${WHITE}.+++###++++###+\${NC} \${WHITE}###+\${NC}" +echo -e "\${GREEN}GGGG.\${NC} \${BLUE}bBBBb\${NC} \${WHITE}.++++++####\${NC} \${WHITE}+###\${NC} \${WHITE}.###.\${NC}\${WHITE}+##+\${NC} \${WHITE}###\${NC} \${WHITE}####\${NC} \${WHITE}.###\${NC}" +echo -e "\${GREEN}GGG\${NC} \${ORANGE}.OOo\${NC} \${BLUE}bBb\${NC} \${WHITE}####+++++###\${NC} \${WHITE}+###\${NC} \${WHITE}###.\${NC}\${WHITE}+##+\${NC} \${WHITE}###\${NC} \${WHITE}###\${NC} \${WHITE}+###\${NC}" +echo -e "\${GREEN}Gg\${NC} \${ORANGE}oOOOOOOo\${NC} \${BLUE}Bb\${NC} \${WHITE}.###\${NC} \${WHITE}.####\${NC} \${WHITE}+####+\${NC} \${WHITE}+###+\${NC} \${WHITE}+##+\${NC} \${WHITE}###\${NC} \${WHITE}.#####+\${NC}" +echo -e " \${ORANGE}oOOOOOOOOOO.\${NC} \${WHITE}.#######+####\${NC}\${WHITE}+###+#######.\${NC} \${WHITE}+##+\${NC} \${WHITE}###\${NC} \${WHITE}####\${NC}" +echo -e " \${ORANGE}.OOOOOOOOOOOOOOo\${NC} \${WHITE}..\${NC} \${WHITE}+###\${NC} \${WHITE}..\${NC} \${WHITE}###.\${NC}" +echo -e " \${ORANGE}oOOOOOOOOOOOOOOOOOOo.\${NC} \${WHITE}+###\${NC} \${WHITE}.###.\${NC}" +echo -e " \${ORANGE}oOOOOOOOOOOOOOOOOOOOOOO.\${NC} \${WHITE}.+++\${NC} \${WHITE}+++\${NC}" echo "" echo -e "\${BOLD}Welcome to Apify AI Code Sandbox Actor!\${NC}" From d7d1bfa378e03ce4f947e8d6302c0ade96a29133 Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Mon, 8 Jun 2026 17:11:22 +0200 Subject: [PATCH 44/56] Pin Codex model to gpt-5.5 to silence model-metadata warning (#74) The baked Codex config used OpenRouter's "~openai/gpt-latest" floating alias, which Codex's bundled model registry can't match, so it warned "Model metadata not found" and fell back to defaults that mis-size the context window. Pinning the concrete "openai/gpt-5.5" slug lets Codex resolve its bundled metadata via suffix match. https://claude.ai/code/session_01H5NXwnYWBgJ3i26uVM2ube Co-authored-by: Claude --- sandbox/Dockerfile | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sandbox/Dockerfile b/sandbox/Dockerfile index 95d0bc3..a038e4e 100644 --- a/sandbox/Dockerfile +++ b/sandbox/Dockerfile @@ -203,14 +203,18 @@ RUN mkdir -p /root/.claude && \ # wire_api MUST be "responses": Codex removed support for the older "chat" # (chat/completions) wire protocol, and the Apify OpenRouter proxy speaks the # OpenAI Responses API, so Codex talks to it at /responses. -# model uses OpenRouter's "~openai/gpt-latest" alias — Codex's native OpenAI -# flagship, auto-tracking the newest release instead of pinning a stale version. +# model is pinned to OpenRouter's "openai/gpt-5.5", OpenAI's current flagship. +# Pinning the concrete version rather than the "~openai/gpt-latest" floating +# alias lets Codex resolve its bundled model metadata (context window etc.) by +# matching the "gpt-5.5" slug suffix, so it no longer warns that metadata is +# "not found" and silently falls back to defaults that mis-size the context +# window. Bump this when a newer flagship ships. # [projects."/sandbox"] pre-trusts the sandbox working dir (ttyd starts shells # there) so Codex skips its interactive "Do you trust this directory?" prompt on # first launch; Codex has no global trust-all switch. RUN mkdir -p /root/.codex && \ printf '%s\n' \ - 'model = "~openai/gpt-latest"' \ + 'model = "openai/gpt-5.5"' \ 'model_provider = "apify-openrouter"' \ 'approval_policy = "never"' \ 'sandbox_mode = "danger-full-access"' \ From f1534c4a269a8223d13972b8ae5ee92c37af6b07 Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Mon, 8 Jun 2026 17:39:45 +0200 Subject: [PATCH 45/56] Report sandbox lifecycle stages via Actor status message (#75) Surface each startup/shutdown phase in the Apify Console: installing Node.js/Python dependencies, running the setup script, sandbox live, and shutting down. The helper is best-effort and a no-op in local mode, so a failed status update never interrupts startup or shutdown. https://claude.ai/code/session_01KAhHPo8KQLi8ujAujwCqxB Co-authored-by: Claude --- sandbox/src/environment.ts | 3 +++ sandbox/src/main.ts | 4 ++++ sandbox/src/status.ts | 22 ++++++++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 sandbox/src/status.ts diff --git a/sandbox/src/environment.ts b/sandbox/src/environment.ts index f02fc52..beb6269 100644 --- a/sandbox/src/environment.ts +++ b/sandbox/src/environment.ts @@ -13,6 +13,7 @@ import { PYTHON_CODE_DIR, SANDBOX_DIR, } from './consts.js'; +import { setStatusMessage } from './status.js'; const execAsync = promisify(exec); @@ -169,6 +170,7 @@ export const installNodeLibraries = async ( const packageSpecs = Object.entries(dependencies).map(([pkg, version]) => `${pkg}@${version}`); log.info('Installing Node.js dependencies', { count: packageSpecs.length, packages: packageSpecs }); + await setStatusMessage('Installing Node.js dependencies'); const installed: string[] = []; const failed: { library: string; error: string }[] = []; @@ -232,6 +234,7 @@ export const installPythonLibraries = async ( } log.info('Installing Python requirements', { count: requirements.length, requirements }); + await setStatusMessage('Installing Python dependencies'); const installed: string[] = []; const failed: { library: string; error: string }[] = []; diff --git a/sandbox/src/main.ts b/sandbox/src/main.ts index 819d625..4ab5ee2 100644 --- a/sandbox/src/main.ts +++ b/sandbox/src/main.ts @@ -17,6 +17,7 @@ import { configureAgentMcpServers } from './mcp-agent-config.js'; import { writeMcpConfig } from './mcp-connections.js'; import { translateLaunchParam } from './shell-launch.js'; import { parseNodeDependencies } from './node-deps.js'; +import { setStatusMessage } from './status.js'; import { appendFile, createDirectory, @@ -151,6 +152,7 @@ if (!setupResult.success) { // Execute init script if provided and not empty if (input?.initBashScript && input.initBashScript.trim().length > 0) { log.info('Executing init script...'); + await setStatusMessage('Running setup script'); const initResult = await executeInitScript(input.initBashScript); if (initResult.exitCode !== 0) { // The output and failure summary were already streamed by executeInitScript; @@ -212,6 +214,7 @@ if (!isLocalMode) { initializationComplete = true; lastActivityAt = Date.now(); log.info('Actor startup complete - ready for requests'); +await setStatusMessage('Sandbox is live'); // Initialize bridges initializeBridges(input?.bridges); @@ -1382,6 +1385,7 @@ server.listen(port, () => { if (idleTimeMs > idleTimeoutSecs * 1000) { const message = `Sandbox shut down after ${Math.round(idleTimeoutSecs)} seconds of inactivity.`; log.warning(message); + await setStatusMessage('Sandbox is shutting down'); await Actor.exit({ statusMessage: message }); } }, 30000); // Check every 30 seconds diff --git a/sandbox/src/status.ts b/sandbox/src/status.ts new file mode 100644 index 0000000..c14119a --- /dev/null +++ b/sandbox/src/status.ts @@ -0,0 +1,22 @@ +// Run status message reporting for the Apify Console. +import { Actor, log } from 'apify'; + +const isLocalMode = process.env.MODE === 'local'; + +/** + * Update the Actor run's status message shown in the Apify Console so each + * lifecycle stage is visible at a glance (installing dependencies, running the + * setup script, live, shutting down). + * + * Best-effort by design: a failed status update — or running locally with no + * platform run to report to — must never interrupt startup or shutdown, so the + * call is swallowed and only logged. + */ +export const setStatusMessage = async (message: string): Promise => { + if (isLocalMode) return; + try { + await Actor.setStatusMessage(message); + } catch (err) { + log.warning('Failed to set Actor status message', { message, error: (err as Error).message }); + } +}; From 8f8e3257cf26f43fac8f64884ee54cf642b6b675 Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Mon, 8 Jun 2026 18:40:04 +0200 Subject: [PATCH 46/56] Rework landing page into an Interactive shell card (#76) - Replace the "Quick links" section with an "Interactive shell" (/shell) card: Plain shell / Claude Code / Codex CLI / OpenCode buttons plus a copy-able /shell URL and a "Launch a specific command" example. - Add a /llms.txt badge to the hero, ordered /llms.txt, idle-shutdown, Healthy. - Mention the /health endpoint in the hero so it stays documented in /llms.txt, where badges are stripped from the markdown projection. https://claude.ai/code/session_015x9rwaqDAgtgD3fYMtqHLL Co-authored-by: Claude --- sandbox/src/templates/landing.ejs | 37 +++++++++++++++++++------------ 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/sandbox/src/templates/landing.ejs b/sandbox/src/templates/landing.ejs index f8823be..61b6fac 100644 --- a/sandbox/src/templates/landing.ejs +++ b/sandbox/src/templates/landing.ejs @@ -13,16 +13,21 @@

      Apify AI Code Sandbox

      An Actor for secure execution of arbitrary code. Connect using MCP, REST API, or interactive shell.<% if (idleTimeoutSecs > 0) { %> The Actor shuts down automatically after <%= idleTimeoutLabel %> of inactivity.<% } %>

      +

      Live service status is reported by <%= serverUrl %>/health.

      - - - Checking... + + 📄 + /llms.txt + + + Checking... + <% if (isLocalMode) { %>
      <%= modeLabel %>
      <% } %> @@ -31,22 +36,26 @@
      -

      🔗 Quick links

      +

      🖥️ Interactive shell /shell

      - + +

      URL

      +
      + +
      <%= serverUrl %>/shell
      +
      + +

      Launch a specific command

      +
      + +
      <%= serverUrl %>/shell?launch=ls
      +
      From f55d8665e5408414c0cccdc3e213f1ecbdb3b6c6 Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Mon, 8 Jun 2026 18:47:52 +0200 Subject: [PATCH 47/56] Better copy --- sandbox/src/templates/landing.ejs | 4 ++-- sandbox/src/templates/shell.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sandbox/src/templates/landing.ejs b/sandbox/src/templates/landing.ejs index 61b6fac..56aaa9c 100644 --- a/sandbox/src/templates/landing.ejs +++ b/sandbox/src/templates/landing.ejs @@ -120,8 +120,8 @@
    • Shell commands: /sandbox (default)
    • JavaScript/TypeScript: /sandbox/js-ts (default)
    • Python: /sandbox/py (default)
    • -
    • Override with cwd parameter (must be within /sandbox)
    +

    Override with cwd parameter (must be within /sandbox).

    @@ -155,7 +155,7 @@ -d "New log entry"
    -

    Delete file or directory

    +

    Delete file or directory (to delete non-empty directory add recursive=1)

    curl -X DELETE <%= serverUrl %>/fs/temp?recursive=1
    diff --git a/sandbox/src/templates/shell.ts b/sandbox/src/templates/shell.ts index 58759f2..2814481 100644 --- a/sandbox/src/templates/shell.ts +++ b/sandbox/src/templates/shell.ts @@ -127,7 +127,7 @@ export const TERMINAL_DISCONNECT_MESSAGE = 'Connection lost — press ⏎ to rec * Message shown when a reconnect attempt fails — the Actor run has most likely * stopped (idle timeout, abort, run timeout, migration). Pressing ⏎ still retries. */ -export const TERMINAL_FINISHED_MESSAGE = 'Actor probably finished — press ⏎ to retry'; +export const TERMINAL_FINISHED_MESSAGE = 'Actor run probably finished — press ⏎ to retry'; /** * Browser script injected into ttyd's terminal page to explain *why* the session From 5d9b0de4eefb2de406623ae0c62720d0844a77ab Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Mon, 8 Jun 2026 18:48:17 +0200 Subject: [PATCH 48/56] Better copy --- sandbox/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sandbox/Dockerfile b/sandbox/Dockerfile index a038e4e..734ff6f 100644 --- a/sandbox/Dockerfile +++ b/sandbox/Dockerfile @@ -220,7 +220,7 @@ RUN mkdir -p /root/.codex && \ 'sandbox_mode = "danger-full-access"' \ '' \ '[model_providers.apify-openrouter]' \ - 'name = "apify/openrouter Actor"' \ + 'name = "apify/openrouter"' \ 'base_url = "https://openrouter.apify.actor/api/v1"' \ 'env_key = "APIFY_TOKEN"' \ 'requires_openai_auth = false' \ From 297391513ad45b408cedd3d7229bf493bf73d136 Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Mon, 8 Jun 2026 19:14:09 +0200 Subject: [PATCH 49/56] Widen homepage intro and drop redundant health-status line (#77) - Stack the hero vertically so the intro paragraph uses the full card width instead of being squeezed beside the badges. - Remove the "Live service status is reported by .../health" sentence; the health badge now carries the /health link into /llms.txt, so that info is still surfaced to LLMs without the extra on-page copy. https://claude.ai/code/session_01CtbYvcQy2ui4e4m8BjEQTs Co-authored-by: Claude --- sandbox/src/templates/landing.css | 23 ++++++++++++++----- sandbox/src/templates/landing.ejs | 37 +++++++++++++++---------------- sandbox/src/templates/landing.ts | 6 ++++- 3 files changed, 40 insertions(+), 26 deletions(-) diff --git a/sandbox/src/templates/landing.css b/sandbox/src/templates/landing.css index 3919bef..d6e6282 100644 --- a/sandbox/src/templates/landing.css +++ b/sandbox/src/templates/landing.css @@ -88,9 +88,8 @@ a:hover { .hero { display: flex; - justify-content: space-between; - align-items: flex-start; - gap: 20px; + flex-direction: column; + gap: 14px; padding: 30px; background: var(--hero-bg); border: 1px solid var(--border); @@ -105,16 +104,28 @@ a:hover { height: 3px; background: var(--accent-bar); } +.hero-bar { + display: flex; + justify-content: space-between; + align-items: center; + gap: 20px; + flex-wrap: wrap; +} +.hero-badges { + display: flex; + gap: 8px; + align-items: center; + flex-wrap: wrap; + flex-shrink: 0; +} h1 { font-size: 30px; font-weight: 700; letter-spacing: -0.02em; color: var(--text-strong); - margin-bottom: 8px; } .lead { color: var(--text-muted); - max-width: 64ch; font-size: 15px; } .badge { @@ -466,7 +477,7 @@ a.status-badge:hover { body { padding: 22px 14px 40px; } - .hero { + .hero-bar { flex-direction: column; align-items: flex-start; } diff --git a/sandbox/src/templates/landing.ejs b/sandbox/src/templates/landing.ejs index 56aaa9c..96b077e 100644 --- a/sandbox/src/templates/landing.ejs +++ b/sandbox/src/templates/landing.ejs @@ -10,28 +10,27 @@
    -
    +

    Apify AI Code Sandbox

    -

    An Actor for secure execution of arbitrary code. Connect using MCP, REST API, or interactive shell.<% if (idleTimeoutSecs > 0) { %> The Actor shuts down automatically after <%= idleTimeoutLabel %> of inactivity.<% } %>

    -

    Live service status is reported by <%= serverUrl %>/health.

    -
    -
    - - 📄 - /llms.txt - - +

    An Actor for secure execution of arbitrary code. Connect using MCP, REST API, or interactive shell.<% if (idleTimeoutSecs > 0) { %> The Actor shuts down automatically after <%= idleTimeoutLabel %> of inactivity.<% } %>

    diff --git a/sandbox/src/templates/landing.ts b/sandbox/src/templates/landing.ts index 010ac2f..8afe2c1 100644 --- a/sandbox/src/templates/landing.ts +++ b/sandbox/src/templates/landing.ts @@ -31,7 +31,11 @@ const landingTemplate = readFileSync(templatePath, 'utf8'); const stylesPath = join(dirname(fileURLToPath(import.meta.url)), 'landing.css'); const landingStyles = readFileSync(stylesPath, 'utf8'); -const STRIP_SELECTOR = 'script, style, [data-no-md], .copy-btn, .status-badge'; +// `.status-badge` is intentionally not stripped here: the health-status badge is +// kept in /llms.txt so the /health URL stays discoverable. Badges that should not +// appear in the Markdown (the /llms.txt link, the idle countdown) are tagged +// data-no-md in the template instead. +const STRIP_SELECTOR = 'script, style, [data-no-md], .copy-btn'; const nhm = new NodeHtmlMarkdown( { From d98cc966fee362435093f1b4a5d5b6488f64ee39 Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Mon, 8 Jun 2026 21:34:46 +0200 Subject: [PATCH 50/56] Rewrite sandbox README around home-page blocks and current features (#78) Restructure into the same blocks as the landing page (Interactive shell, Connect with MCP, Code execution API, Filesystem API, Bridges) and trim the verbose duplicated examples. Document features that had landed but weren't in the README: shell agent launchers, MCP client setup + tool names, /browse, /llms.txt, the envVars and mcpConnectors inputs, input renames, and cross-migration persistence. https://claude.ai/code/session_01G9qRHY5MwEmvbggrwbwVZ4 Co-authored-by: Claude --- sandbox/README.md | 457 +++++++++------------------------------------- 1 file changed, 88 insertions(+), 369 deletions(-) diff --git a/sandbox/README.md b/sandbox/README.md index ba133fc..a27fe26 100644 --- a/sandbox/README.md +++ b/sandbox/README.md @@ -1,429 +1,148 @@ # Apify AI Code Sandbox -Isolated sandbox for running AI coding operations in a containerized environment. 🚀 +Secure, isolated container for executing arbitrary code — built for AI coding agents and untrusted code. Connect over **MCP**, a **REST API**, or an **interactive browser shell**. Ships with **Claude Code**, **Codex CLI**, and **OpenCode** pre-configured and ready to launch. 🚀 + +> 💡 Open the Actor's **landing page** (the container URL, shown in the run log and Actor output) for live connection details, one-click shell/agent launches, a health badge, and the idle-shutdown countdown. The same docs are served as machine-readable Markdown at `/llms.txt`. ## Use cases -- **🔒 Execute untrusted code safely:** Run potentially unsafe code in an isolated container with controlled resources and security boundaries -- **🤖 AI agent development:** Provide isolated and managed development environments where AI agents can code, test, and execute operations securely -- **📦 Sandboxed operations:** Execute system commands, file operations, and custom scripts in a contained environment -- **🖥️ Interactive debugging:** Access the sandbox via browser-based shell terminal for real-time exploration and troubleshooting -- **🔀 Bridges:** Expose local services (web servers, APIs, dashboards) running inside the container to external URL paths - accessible from outside the container -- **🔗 Apify Actor orchestration:** Agents can access the limited permissions Apify token (available as `APIFY_TOKEN` env var) to run other [limited permissions Actors](https://docs.apify.com/platform/actors/development/permissions), process or analyze their output, and build complex data pipelines by combining results from multiple Actors +- **Run untrusted or AI-generated code safely** in an isolated container with controlled resources. +- **Give AI agents a managed workspace** to write, run, and test code — with state that survives container migrations. +- **Drop in over MCP** so any MCP client gains code-execution and filesystem tools, no glue code. +- **Pair with coding agents** (Claude Code, Codex CLI, OpenCode) right in the browser shell. +- **Expose internal services** (dev servers, dashboards, TUIs) at a public URL with bridges. +- **Orchestrate Apify Actors** using the limited-permission `APIFY_TOKEN` available inside the sandbox to run other [limited-permission Actors](https://docs.apify.com/platform/actors/development/permissions) and build data pipelines. ## Quickstart -### Start the Actor +1. Run the Actor on the [Apify platform](https://console.apify.com/) (Console or API). +2. Open the **landing page** — the container URL from the run log / Actor output — for live links and connection details. +3. Connect with an MCP client, call the REST API, or open the shell. -1. Run it on the Apify platform through the [Console](https://console.apify.com/) -2. Check the Actor run log console for connection details (host, port, MCP endpoint URL) -3. Open the landing page link from the run logs for connection details, quick links (shell + health), and endpoint URLs for the current run. +Examples below use `https://UNIQUE-ID.runs.apify.net` as the container URL — replace it with your run's URL. -## Ways to connect +## 🖥️ Interactive shell — `/shell` -Start the Actor (see Quickstart above), then choose how to interact: +Browser terminal (powered by ttyd) for hands-on work inside the sandbox. -- MCP client: Agent-driven access to run code or develop with LLM tooling. -- REST API: Endpoints to run code or shell commands. -- Interactive shell: Browser terminal for manual exploration. +- `…/shell` — plain Bash shell. +- `…/shell?launch=claude` — launch **Claude Code**. +- `…/shell?launch=codex` — launch **Codex CLI**. +- `…/shell?launch=opencode` — launch **OpenCode**. +- `…/shell?launch=` — run any command, then drop into a shell. -### MCP client +The coding agents are installed on first use and start pre-configured against the Apify OpenRouter proxy (billed as OpenRouter API usage). -Use a Model Context Protocol (MCP) client to interact with this sandbox. See [modelcontextprotocol.io/clients](https://modelcontextprotocol.io/clients). +## 📡 Connect with MCP — `/mcp` -**Connect with Claude Code:** +Streamable-HTTP MCP endpoint, no authentication required: -```bash -claude mcp add --transport http sandbox https://UNIQUE-ID.runs.apify.net/mcp +``` +https://UNIQUE-ID.runs.apify.net/mcp ``` -Replace `UNIQUE-ID` with the run ID from your Actor execution (URL is also in the landing page and logs). Then prompt your agent; it will use the sandbox tools automatically over MCP. - -### REST API - -Available endpoints (all URLs come from the run logs/landing page): - -#### Core endpoints - -- `POST /mcp` - - Body: JSON-RPC over HTTP per MCP client - - Returns: JSON-RPC response - -- `POST /exec` - - Execute shell commands OR code snippets (JavaScript, TypeScript, Python) - - Body: `{ command: string; language?: string; cwd?: string; timeoutSecs?: number }` - - Language options: `"js"`, `"javascript"`, `"ts"`, `"typescript"`, `"py"`, `"python"`, `"bash"`, `"sh"` (omit for shell) - - Returns (200 on success, 500 on error): `{ stdout: string; stderr: string; exitCode: number; language: string }` - - The `language` field in response is always present: `"shell"` for shell commands, `"js"`/`"ts"`/`"py"` for code - -- `GET /health` - - Health check endpoint - - Returns (200/503): `{ status: 'healthy' | 'initializing' | 'unhealthy'; message?: string }` - -- `GET /shell/` - - Interactive browser terminal - - Returns: Interactive terminal powered by ttyd - -- `GET /llms.txt` - - Markdown documentation for LLMs (same usage info as landing page) - - Returns (200): Plain text Markdown with all endpoint documentation - -#### Bridge endpoints - -Expose local services running inside the container to external URL paths. This allows you to start a web server (e.g., on port 3000) inside the sandbox and access it from outside via a bridge. - -- `GET /bridges` - - List current bridges - - Returns (200): `{ bridges: [{ path: string, target: string }] }` - -- `PUT /bridges` - - Replace all bridges - - Body: `{ bridges: [{ path: "/myapp", target: "http://127.0.0.1:3000/myapp" }] }` - - Returns (200): `{ success: true, bridges: [...] }` - -- `POST /bridges` - - Add a single bridge - - Body: `{ path: "/myapp", target: "http://127.0.0.1:3000/myapp" }` - - Returns (200): `{ success: true, bridges: [...] }` - -- `DELETE /bridges/{path}` - - Remove a bridge by path - - Returns (200): `{ success: true, removed: string, bridges: [...] }` - -Once configured, all HTTP requests and WebSocket connections to the bridge (e.g., `/myapp/*`) are transparently forwarded to the local service. Bridges can also be configured via Actor input or by writing to `/sandbox/.bridges.json`. - -**Health status:** - -- `status: "initializing"` (503) – dependencies/setup still running -- `status: "unhealthy"` (503) – init script failed; check logs -- `status: "healthy"` (200) – ready for requests - -#### RESTful filesystem endpoints - -Direct filesystem access using standard HTTP methods. All paths are relative to `/sandbox`. - -- `GET /fs/{path}` - - **Read file**: Returns raw file bytes with appropriate `Content-Type` header - - **List directory**: Returns JSON with directory contents (files and subdirectories with sizes) - - Query params: - - `?download=1`: Download file as attachment (or directory as ZIP) - - Returns (200): File content or directory JSON, (404): Path not found - -- `PUT /fs/{path}` - - **Write/replace file**: Create or replace file with request body content - - Accepts raw bytes or text in request body - - Automatically creates parent directories if they don't exist - - Returns (200): `{ success: true, path: string, size: number }` - -- `POST /fs/{path}?mkdir=1` - - **Create directory**: Create directory at specified path (recursive by default) - - Returns (201): `{ success: true, path: string, type: "directory" }` - -- `POST /fs/{path}?append=1` - - **Append to file**: Append request body to existing file (creates file if it doesn't exist) - - Returns (200): `{ success: true, path: string, size: number }` - -- `DELETE /fs/{path}` - - **Delete file or directory** - - Query params: - - `?recursive=1`: Enable recursive deletion for non-empty directories - - Returns (200): `{ success: true, path: string, deleted: true }`, (409): Directory not empty - -- `HEAD /fs/{path}` - - **Get metadata**: Returns file/directory metadata in response headers - - Headers: `Content-Type`, `Content-Length`, `X-File-Type`, `Last-Modified`, `X-Path` - - Returns (200): Headers only, (404): Path not found - -**Path Resolution**: All `/fs/*` paths are resolved relative to `/sandbox`: - -- `/fs/app/main.py` → `/sandbox/app/main.py` -- `/fs/tmp/test.txt` → `/sandbox/tmp/test.txt` - -**Security**: Paths are validated to prevent escaping the `/sandbox` directory. Symlinks are followed but validated to stay within `/sandbox`. - -**Filesystem examples (curl):** +Add it to a client: ```bash -# Read a file -curl https://UNIQUE-ID.runs.apify.net/fs/app/config.json - -# List directory contents -curl https://UNIQUE-ID.runs.apify.net/fs/app - -# Download directory as ZIP -curl https://UNIQUE-ID.runs.apify.net/fs/app?download=1 -o app.zip - -# Upload a file -curl -X PUT https://UNIQUE-ID.runs.apify.net/fs/app/config.json \ - -H "Content-Type: application/json" \ - -d '{"key": "value"}' - -# Create a directory -curl -X POST https://UNIQUE-ID.runs.apify.net/fs/app/data?mkdir=1 - -# Append to a log file -curl -X POST https://UNIQUE-ID.runs.apify.net/fs/app/log.txt?append=1 \ - -H "Content-Type: text/plain" \ - -d "New log entry" - -# Delete a file -curl -X DELETE https://UNIQUE-ID.runs.apify.net/fs/app/temp.txt - -# Delete directory recursively -curl -X DELETE https://UNIQUE-ID.runs.apify.net/fs/app/temp?recursive=1 - -# Get file metadata -curl -I https://UNIQUE-ID.runs.apify.net/fs/app/data.json -``` - -**Upload/download files (TypeScript):** - -```ts -const baseUrl = 'https://UNIQUE-ID.runs.apify.net'; - -// Upload a file -const uploadResponse = await fetch(`${baseUrl}/fs/app/document.pdf`, { - method: 'PUT', - headers: { 'Content-Type': 'application/pdf' }, - body: pdfBuffer, // File buffer or Blob -}); - -// Download a file -const downloadResponse = await fetch(`${baseUrl}/fs/app/document.pdf`); -const fileBlob = await downloadResponse.blob(); - -// Download directory as ZIP -const zipResponse = await fetch(`${baseUrl}/fs/app?download=1`); -const zipBlob = await zipResponse.blob(); - -// List directory -const listResponse = await fetch(`${baseUrl}/fs/app`); -const { entries } = await listResponse.json(); -console.log(entries); // [{ name, type, size }, ...] - -// Create project structure -await fetch(`${baseUrl}/fs/project/src?mkdir=1`, { method: 'POST' }); -await fetch(`${baseUrl}/fs/project/tests?mkdir=1`, { method: 'POST' }); -await fetch(`${baseUrl}/fs/project/README.md`, { - method: 'PUT', - body: '# My Project', -}); +claude mcp add --transport http sandbox https://UNIQUE-ID.runs.apify.net/mcp +codex mcp add sandbox --url https://UNIQUE-ID.runs.apify.net/mcp +mcpc connect https://UNIQUE-ID.runs.apify.net/mcp @sandbox ``` -**Upload/download files (Python):** - -```python -import requests - -base_url = "https://UNIQUE-ID.runs.apify.net" - -# Upload a file -with open('document.pdf', 'rb') as f: - resp = requests.put(f"{base_url}/fs/app/document.pdf", - data=f, - headers={'Content-Type': 'application/pdf'}) - resp.raise_for_status() +Tools exposed: `execute` (shell / JS / TS / Python), `read-file`, `write-file`, `list-files`. -# Download a file -resp = requests.get(f"{base_url}/fs/app/document.pdf") -with open('downloaded.pdf', 'wb') as f: - f.write(resp.content) +## ⚡ Code execution API — `/exec` -# Download directory as ZIP -resp = requests.get(f"{base_url}/fs/app?download=1") -with open('app.zip', 'wb') as f: - f.write(resp.content) +`POST /exec` runs a shell command or a code snippet. -# List directory -resp = requests.get(f"{base_url}/fs/app") -data = resp.json() -for entry in data['entries']: - print(f"{entry['name']} ({entry['type']}) - {entry.get('size', 'N/A')} bytes") +- Body: `{ command: string; language?: string; cwd?: string; timeoutSecs?: number }` +- `language`: `bash`/`sh` (or omit) for shell; `js`/`javascript`, `ts`/`typescript`, `py`/`python` for code. +- Returns `{ stdout, stderr, exitCode, language }` — `200` on success, `500` on a non-zero exit or error. -# Create project structure -requests.post(f"{base_url}/fs/project/src?mkdir=1") -requests.post(f"{base_url}/fs/project/tests?mkdir=1") -requests.put(f"{base_url}/fs/project/README.md", data=b"# My Project") -``` - -**Code execution examples (TypeScript/Node):** - -```ts -const baseUrl = 'https://UNIQUE-ID.runs.apify.net'; - -// Execute Python code -const codeRes = await fetch(`${baseUrl}/exec`, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - command: 'print("hello from python")', - language: 'py', - timeoutSecs: 10, - }), -}); -console.log(await codeRes.json()); - -// Execute shell command -const shellRes = await fetch(`${baseUrl}/exec`, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - command: 'ls -la', - cwd: '/sandbox', - timeoutSecs: 5, - }), -}); -console.log(await shellRes.json()); +```bash +curl -X POST https://UNIQUE-ID.runs.apify.net/exec \ + -H "Content-Type: application/json" \ + -d '{"command": "print(\"hi\")", "language": "py", "timeoutSecs": 10}' ``` -**Code execution examples (Python):** +Default working directories: shell → `/sandbox`, JS/TS → `/sandbox/js-ts`, Python → `/sandbox/py`. Override with `cwd` (must stay within `/sandbox`). -```python -import requests +## 📁 Filesystem API — `/fs` -base_url = "https://UNIQUE-ID.runs.apify.net" +Direct file operations over HTTP. All paths are relative to `/sandbox` and validated to stay inside it. -# Execute Python code -payload = {"command": "print('hello from python')", "language": "py", "timeoutSecs": 10} -resp = requests.post(f"{base_url}/exec", json=payload, timeout=15) -resp.raise_for_status() -print(resp.json()) +- `GET /fs/{path}` — read a file (raw bytes) or list a directory (JSON `{ path, entries }`). Add `?download=1` to get a file as an attachment or a directory as a ZIP. +- `PUT /fs/{path}` — write/replace a file (creates parent dirs; up to 500 MB). +- `POST /fs/{path}?mkdir=1` — create a directory; `?append=1` — append to a file. +- `DELETE /fs/{path}` — delete; add `?recursive=1` for non-empty directories. +- `HEAD /fs/{path}` — return metadata in the response headers. -# Execute shell command -payload = {"command": "ls -la", "cwd": "/sandbox", "timeoutSecs": 5} -resp = requests.post(f"{base_url}/exec", json=payload, timeout=15) -resp.raise_for_status() -print(resp.json()) +```bash +curl https://UNIQUE-ID.runs.apify.net/fs/app/log.txt # read +curl -X PUT https://UNIQUE-ID.runs.apify.net/fs/config.json -d '{"key":"value"}' # write +curl -X POST "https://UNIQUE-ID.runs.apify.net/fs/project/src?mkdir=1" # mkdir +curl -X DELETE "https://UNIQUE-ID.runs.apify.net/fs/temp?recursive=1" # delete ``` -### Interactive shell terminal +Prefer a UI? Browse the filesystem at `/browse`. -Open the interactive shell terminal URL from the run logs (also linked on the landing page) to work directly in the browser. +## 🔀 Bridges — `/bridges` -### Bridges +Expose a web server you start **inside** the sandbox at a public URL path on the container, reachable over HTTP and WebSocket. Each bridge forwards `…/{path}` → `http://127.0.0.1:{port}/…`. -Expose local services running inside the container to external URL paths. Start a web server inside the sandbox and access it from outside the container. - -**Example workflow:** +- `GET /bridges` — list current bridges. +- `POST /bridges` — add one: `{ "path": "/myapp", "target": "http://127.0.0.1:3000/myapp" }`. +- `PUT /bridges` — replace all: `{ "bridges": [ … ] }`. +- `DELETE /bridges/{path}` — remove one. ```bash -# 1. Start a local web server inside the sandbox -npx http-server /sandbox/myapp -p 8080 - -# 2. Add a bridge (via REST API) +# Start a server inside the sandbox, then expose it: curl -X POST https://UNIQUE-ID.runs.apify.net/bridges \ -H "Content-Type: application/json" \ -d '{"path": "/myapp", "target": "http://127.0.0.1:8080"}' - -# 3. Access the app from outside -# https://UNIQUE-ID.runs.apify.net/myapp/ +# Now reachable at https://UNIQUE-ID.runs.apify.net/myapp/ ``` -**Via Actor input:** - -Provide bridges at startup via the `bridges` input parameter: +Bridges can also be set via the `bridges` input or by writing `/sandbox/.bridges.json` (changes are picked up live). Longest-path matching and `Location`-header rewriting are automatic, and bridges persist across restarts. -```json -[{"path": "/myapp", "target": "http://127.0.0.1:3000/myapp"}] -``` +## Health & status — `/health` -**Via config file:** +`GET /health` reports the service state: -Bridges can also be modified by writing JSON to `/sandbox/.bridges.json`. Changes are detected automatically via file watching. +- `200 { status: "healthy", idleTimeoutSecs, remainingSecs? }` +- `503 { status: "initializing" }` — dependencies / setup script still running. +- `503 { status: "unhealthy", message }` — setup failed; check the run log. -**Features:** -- Supports both HTTP and WebSocket connections -- Longest-path matching for overlapping routes -- Automatic redirect rewriting (Location headers) -- Live reload on config file changes +`remainingSecs` counts down to idle shutdown and is present only while an idle timeout is active. ## Configuration -- **Memory & timeout:** Configure run options to set memory allocation and execution timeout -- **Idle timeout:** The container automatically shuts down after a period of inactivity (default: 15 minutes). Activity includes HTTP requests and shell interaction. You can adjust this via the `idleTimeoutSecs` input. -- **Recommendation:** For cost efficiency, set the standard Actor **Execution Timeout to 0 (infinite)** in the Apify Console. The internal idle logic will then manage the lifecycle based on your usage. -- **Request timeout:** All requests to the Actor have a 5-minute timeout ceiling. All operations (code execution, commands, file operations) must complete within this time limit. The `timeout` parameter in requests cannot exceed this 5-minute window -- **Check logs:** Open the Actor run log console to view connection details and operation output - -## Sandbox environment structure - -The sandbox runs on a **Debian Trixie** container image with **Node.js 24**, **Python 3**, and essential development tools pre-installed. - -The sandbox provides isolated execution environments for different code languages: - -### Code execution directories - -- **Python**: `/sandbox/py` - - Python code executes in this isolated directory - - Has access to Python virtual environment at `/sandbox/py/venv` - - All pip packages installed in the venv - -- **JavaScript/TypeScript**: `/sandbox/js-ts` - - JS/TS code executes in this isolated directory - - Has access to node_modules at `/sandbox/js-ts/node_modules` - - All npm packages installed in node_modules - -- **General Commands**: `/sandbox` (root) - - Shell commands via `/exec` endpoint run from sandbox root - - Can access all subdirectories - -### Dependency installation - -Specify dependencies to install via Actor input: +All inputs are optional. Set them in the Actor input form or via the API. -- **Node.js Dependencies**: npm packages for JS/TS code execution. Accepts either format: - - **One `package@version` per line** (npm CLI style): - ``` - zod@^3.0 - axios@latest - lodash - @types/node@^20 - ``` - Lines without `@version` default to `latest`. Blank lines and `#` comments are ignored. - - **JSON object** (package.json `dependencies` style): `{"zod": "^3.0", "axios": "latest"}` -- **Python Requirements**: pip packages for Python code execution in requirements.txt format - - Input as multi-line text: one package per line with optional version specifiers - - Example: - ``` - requests==2.31.0 - pandas>=2.0.0 - numpy - ``` - -Dependencies are installed during Actor startup before any code execution, allowing your code to immediately use them. - -### Customization with init script - -Provide a bash script via the "Initialization Script" input to customize the sandbox: - -- Runs **after** library installation -- Executes in `/sandbox` directory -- Can install system packages, create directories, set permissions, etc. -- Output is streamed live to the Actor log (tagged `[init]`), with a progress heartbeat for long steps, so you can follow along and debug failures -- Errors are logged but don't prevent Actor from starting -- **Note:** Init scripts have a 5-minute execution timeout - -**Example init scripts:** - -```bash -# Install system package -apt-get update && apt-get install -y curl - -# Create custom directory with permissions -mkdir -p /sandbox/custom-data && chmod 755 /sandbox/custom-data -``` +| Input | Description | +| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Agent skills** (`agentSkills`) | SKILLS.md packages for the coding agents — `owner/repo` or a repo URL per line, or a JSON array. Defaults to `apify/agent-skills`. See [skills.sh](https://skills.sh/). | +| **Node.js dependencies** (`nodeDependencies`) | npm packages for JS/TS execution. One `package@version` per line (npm-style), or a `package.json`-style JSON object. | +| **Python requirements** (`pythonRequirements`) | pip packages for Python execution, in `requirements.txt` format. | +| **MCP connectors** (`mcpConnectors`) | MCP connectors to pre-load into Claude Code, Codex, and OpenCode, and write to `/sandbox/mcp.json` for `mcpc`. | +| **Setup script** (`initBashScript`) | Bash script run on startup after dependencies install. Output streams to the log (tagged `[init]`) with a progress heartbeat; 5-minute timeout. | +| **Environment variables** (`envVars`) | Secret variables exposed **only to the setup script**, then removed before the shell, MCP server, and code execution start. dotenv or JSON; encrypted at rest. | +| **Idle timeout** (`idleTimeoutSecs`) | Seconds of inactivity before automatic shutdown (default `900`; `0` disables). Activity includes HTTP requests and shell interaction. | +| **Bridges** (`bridges`) | Bridges to create at startup (see above). | -### Skills support (SKILLS.md) +Dependencies install at startup before any code runs. For cost efficiency, set the Actor's **Execution Timeout to 0 (infinite)** and let the idle timeout manage the lifecycle. Note that every request to the Actor has a 5-minute ceiling, so each operation must finish within that window. -Install skill packages that provide specialized instructions for AI coding agents. Skills are SKILLS.md files that enhance agent capabilities. +## Sandbox environment -- Specify skills via the "Skills" input — one per line: a GitHub `owner/repo` (e.g. `anthropics/skills`) or repo URL (e.g. `https://github.com/anthropics/skills`), or a JSON array -- Example: `apify/agent-skills` -- Skills are installed globally during Actor startup -- For more info see [skills.sh](https://skills.sh/) +- **Base image:** Debian Trixie with **Node.js 24** and **Python 3** (`venv` at `/sandbox/py/venv`). +- **Pre-installed tools:** git, openssh-client, curl, wget, jq, build-essential, `tsx`, `apify-cli`, `mcpc`, and `ttyd`; `apify-client` is ready in both the Node and Python environments. +- **Coding agents:** Claude Code, Codex CLI, and OpenCode — installed on first launch and wired to the Apify OpenRouter proxy (authenticated with `APIFY_TOKEN`). +- **Working directories:** `/sandbox` (shell), `/sandbox/js-ts` (npm packages in `node_modules`), `/sandbox/py` (Python venv). +- **Persistence:** filesystem changes are backed up to the Actor's key-value store and restored after a container migration, so work survives restarts (dependency directories are excluded and reinstalled). +- **Agent context:** `AGENTS.md` and `CLAUDE.md` are placed in `/sandbox` to guide the coding agents. ## Learn more -- [Apify Actor documentation](https://docs.apify.com/platform/actors) +- [Apify Actors documentation](https://docs.apify.com/platform/actors) - [Model Context Protocol](https://modelcontextprotocol.io/) - [Apify SDK reference](https://docs.apify.com/sdk) From d2a9600f45c4c1a346e146eb629c7007ca170350 Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Mon, 8 Jun 2026 22:17:14 +0200 Subject: [PATCH 51/56] Better copy --- sandbox/README.md | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/sandbox/README.md b/sandbox/README.md index a27fe26..252f996 100644 --- a/sandbox/README.md +++ b/sandbox/README.md @@ -1,8 +1,10 @@ # Apify AI Code Sandbox -Secure, isolated container for executing arbitrary code — built for AI coding agents and untrusted code. Connect over **MCP**, a **REST API**, or an **interactive browser shell**. Ships with **Claude Code**, **Codex CLI**, and **OpenCode** pre-configured and ready to launch. 🚀 +Secure, isolated container for executing arbitrary code, built for AI coding agents and untrusted code. +Connect over **MCP**, a **REST API**, or an **interactive browser shell**. +Ships with **Claude Code**, **Codex CLI**, and **OpenCode** pre-configured and ready to launch. -> 💡 Open the Actor's **landing page** (the container URL, shown in the run log and Actor output) for live connection details, one-click shell/agent launches, a health badge, and the idle-shutdown countdown. The same docs are served as machine-readable Markdown at `/llms.txt`. +This Actor launches a web server on the Actor container URL that provides interface to the sandbox. ## Use cases @@ -13,25 +15,38 @@ Secure, isolated container for executing arbitrary code — built for AI coding - **Expose internal services** (dev servers, dashboards, TUIs) at a public URL with bridges. - **Orchestrate Apify Actors** using the limited-permission `APIFY_TOKEN` available inside the sandbox to run other [limited-permission Actors](https://docs.apify.com/platform/actors/development/permissions) and build data pipelines. + ## Quickstart 1. Run the Actor on the [Apify platform](https://console.apify.com/) (Console or API). -2. Open the **landing page** — the container URL from the run log / Actor output — for live links and connection details. +2. Open the sandbox **landing page** (the container URL shown in the Actor output) for live links and connection details. 3. Connect with an MCP client, call the REST API, or open the shell. Examples below use `https://UNIQUE-ID.runs.apify.net` as the container URL — replace it with your run's URL. + ## 🖥️ Interactive shell — `/shell` Browser terminal (powered by ttyd) for hands-on work inside the sandbox. -- `…/shell` — plain Bash shell. +- `https://UNIQUE-ID.runs.apify.net/shell` — plain Bash shell. - `…/shell?launch=claude` — launch **Claude Code**. - `…/shell?launch=codex` — launch **Codex CLI**. - `…/shell?launch=opencode` — launch **OpenCode**. - `…/shell?launch=` — run any command, then drop into a shell. -The coding agents are installed on first use and start pre-configured against the Apify OpenRouter proxy (billed as OpenRouter API usage). +The coding agents are installed on first use and start pre-configured against the [Apify OpenRouter proxy](https://apify.com/apify/openrouter), +billed to your Apify account. + + +## 🤖 AI agent instructions + +The sandbox landing page is also available as Markdown as the `/llms.txt` file: + +``` +https://UNIQUE-ID.runs.apify.net/llms.txt +``` + ## 📡 Connect with MCP — `/mcp` @@ -41,7 +56,7 @@ Streamable-HTTP MCP endpoint, no authentication required: https://UNIQUE-ID.runs.apify.net/mcp ``` -Add it to a client: +Add it to an MCP client: ```bash claude mcp add --transport http sandbox https://UNIQUE-ID.runs.apify.net/mcp @@ -67,6 +82,7 @@ curl -X POST https://UNIQUE-ID.runs.apify.net/exec \ Default working directories: shell → `/sandbox`, JS/TS → `/sandbox/js-ts`, Python → `/sandbox/py`. Override with `cwd` (must stay within `/sandbox`). + ## 📁 Filesystem API — `/fs` Direct file operations over HTTP. All paths are relative to `/sandbox` and validated to stay inside it. @@ -86,6 +102,7 @@ curl -X DELETE "https://UNIQUE-ID.runs.apify.net/fs/temp?recursive=1" Prefer a UI? Browse the filesystem at `/browse`. + ## 🔀 Bridges — `/bridges` Expose a web server you start **inside** the sandbox at a public URL path on the container, reachable over HTTP and WebSocket. Each bridge forwards `…/{path}` → `http://127.0.0.1:{port}/…`. @@ -105,6 +122,7 @@ curl -X POST https://UNIQUE-ID.runs.apify.net/bridges \ Bridges can also be set via the `bridges` input or by writing `/sandbox/.bridges.json` (changes are picked up live). Longest-path matching and `Location`-header rewriting are automatic, and bridges persist across restarts. + ## Health & status — `/health` `GET /health` reports the service state: From 73c9146490a3e79a451517287e715dee58e0ad7f Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Mon, 8 Jun 2026 22:34:37 +0200 Subject: [PATCH 52/56] Add emoji icons to use cases in README (#79) * Add emojis to Use cases bullets in sandbox README * Move Use cases emojis before the bullet text --------- Co-authored-by: Claude --- sandbox/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sandbox/README.md b/sandbox/README.md index 252f996..2970a0e 100644 --- a/sandbox/README.md +++ b/sandbox/README.md @@ -8,12 +8,12 @@ This Actor launches a web server on the Actor container URL that provides interf ## Use cases -- **Run untrusted or AI-generated code safely** in an isolated container with controlled resources. -- **Give AI agents a managed workspace** to write, run, and test code — with state that survives container migrations. -- **Drop in over MCP** so any MCP client gains code-execution and filesystem tools, no glue code. -- **Pair with coding agents** (Claude Code, Codex CLI, OpenCode) right in the browser shell. -- **Expose internal services** (dev servers, dashboards, TUIs) at a public URL with bridges. -- **Orchestrate Apify Actors** using the limited-permission `APIFY_TOKEN` available inside the sandbox to run other [limited-permission Actors](https://docs.apify.com/platform/actors/development/permissions) and build data pipelines. +- 🔒 **Run untrusted or AI-generated code safely** in an isolated container with controlled resources. +- 🤖 **Give AI agents a managed workspace** to write, run, and test code — with state that survives container migrations. +- 🔌 **Drop in over MCP** so any MCP client gains code-execution and filesystem tools, no glue code. +- 💻 **Pair with coding agents** (Claude Code, Codex CLI, OpenCode) right in the browser shell. +- 🌐 **Expose internal services** (dev servers, dashboards, TUIs) at a public URL with bridges. +- 🎭 **Orchestrate Apify Actors** using the limited-permission `APIFY_TOKEN` available inside the sandbox to run other [limited-permission Actors](https://docs.apify.com/platform/actors/development/permissions) and build data pipelines. ## Quickstart From 7e0385a65ecf700b1c61315b2b6a48d38da5908f Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Mon, 8 Jun 2026 22:52:17 +0200 Subject: [PATCH 53/56] Update npm dependencies to latest versions (#81) Bump dependencies across all four Actors, including major upgrades in sandbox: TypeScript 6, archiver 8, ejs 6, and @apify/eslint-config 2. Migration fixes required by the major bumps: - archiver 8 removed its default export; use `new ZipArchive(...)`. - TypeScript 6 requires an explicit rootDir when emitting; set it in each tsconfig.build.json (output layout unchanged). - Keep ESLint on the latest 9.x since @apify/eslint-config still peers eslint ^9. https://claude.ai/code/session_01E3CkuZSLHiB3qoP84b8Sin Co-authored-by: Claude --- claude-code/package-lock.json | 288 ++--- claude-code/package.json | 8 +- claude-code/tsconfig.build.json | 3 +- openclaw/package-lock.json | 283 ++--- openclaw/package.json | 8 +- openclaw/tsconfig.build.json | 3 +- opencode/package.json | 8 +- opencode/tsconfig.build.json | 3 +- sandbox/package-lock.json | 2061 +++++++++++++++---------------- sandbox/package.json | 30 +- sandbox/src/operations.ts | 4 +- sandbox/tsconfig.build.json | 3 +- 12 files changed, 1262 insertions(+), 1440 deletions(-) diff --git a/claude-code/package-lock.json b/claude-code/package-lock.json index 863bfe5..9461247 100644 --- a/claude-code/package-lock.json +++ b/claude-code/package-lock.json @@ -8,21 +8,21 @@ "name": "claude-code-sandbox", "version": "0.0.1", "dependencies": { - "apify": "^3.4.2" + "apify": "^3.7.2" }, "devDependencies": { - "@types/node": "^22.0.0", - "tsx": "^4.19.2", - "typescript": "^5.7.3" + "@types/node": "^25.9.2", + "tsx": "^4.22.4", + "typescript": "^6.0.3" }, "engines": { "node": ">=18" } }, "node_modules/@apify/consts": { - "version": "2.48.1", - "resolved": "https://registry.npmjs.org/@apify/consts/-/consts-2.48.1.tgz", - "integrity": "sha512-JPa8E10edotvuGDyVfqdVwRVvQl7wathOLNTL/UjO3ELorC+RLh95b+4dwn9ydCICjU7rDEzKbZ57xCQGXx6ZA==", + "version": "2.53.3", + "resolved": "https://registry.npmjs.org/@apify/consts/-/consts-2.53.3.tgz", + "integrity": "sha512-X26SC8d6kLMSWzBcYldKgzKFJXGnTAka/bRmvL+iYHJEabFzVYupj64kGhar6QTPeGKNt6WR/hNE+dToNqHZQA==", "license": "Apache-2.0" }, "node_modules/@apify/datastructures": { @@ -192,9 +192,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", "cpu": [ "ppc64" ], @@ -209,9 +209,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", "cpu": [ "arm" ], @@ -226,9 +226,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", "cpu": [ "arm64" ], @@ -243,9 +243,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", "cpu": [ "x64" ], @@ -260,9 +260,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", "cpu": [ "arm64" ], @@ -277,9 +277,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", "cpu": [ "x64" ], @@ -294,9 +294,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", "cpu": [ "arm64" ], @@ -311,9 +311,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", "cpu": [ "x64" ], @@ -328,9 +328,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", "cpu": [ "arm" ], @@ -345,9 +345,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", "cpu": [ "arm64" ], @@ -362,9 +362,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", "cpu": [ "ia32" ], @@ -379,9 +379,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", "cpu": [ "loong64" ], @@ -396,9 +396,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", "cpu": [ "mips64el" ], @@ -413,9 +413,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", "cpu": [ "ppc64" ], @@ -430,9 +430,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", "cpu": [ "riscv64" ], @@ -447,9 +447,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", "cpu": [ "s390x" ], @@ -464,9 +464,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", "cpu": [ "x64" ], @@ -481,9 +481,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", "cpu": [ "arm64" ], @@ -498,9 +498,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", "cpu": [ "x64" ], @@ -515,9 +515,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", "cpu": [ "arm64" ], @@ -532,9 +532,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", "cpu": [ "x64" ], @@ -549,9 +549,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", "cpu": [ "arm64" ], @@ -566,9 +566,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", "cpu": [ "x64" ], @@ -583,9 +583,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", "cpu": [ "arm64" ], @@ -600,9 +600,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", "cpu": [ "ia32" ], @@ -617,9 +617,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", "cpu": [ "x64" ], @@ -717,12 +717,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.19.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", - "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", + "version": "25.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz", + "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==", "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": ">=7.24.0 <7.24.7" } }, "node_modules/@types/sax": { @@ -772,12 +772,12 @@ } }, "node_modules/apify": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/apify/-/apify-3.5.3.tgz", - "integrity": "sha512-279q+c6Rz2RjQrDzhjrA8pyy40lQBoMnZBl3Ei+zP/gSh0WEVmTH5D/VbHH4TOQpOHv3CaP2Mwg/O2CaMNB4JQ==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/apify/-/apify-3.7.2.tgz", + "integrity": "sha512-I4R5Qd1X11vk5/1U0CxKSsMhNwER/3MntePEQb9Pbtf942OhQLh5IURrJZufqtpimkqRy44SlQMU6UhPdoFXhA==", "license": "Apache-2.0", "dependencies": { - "@apify/consts": "^2.47.1", + "@apify/consts": "^2.51.0", "@apify/input_secrets": "^1.2.0", "@apify/log": "^2.4.3", "@apify/timeout": "^0.3.0", @@ -1318,9 +1318,9 @@ } }, "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1331,32 +1331,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" } }, "node_modules/escalade": { @@ -1617,19 +1617,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, "node_modules/get-uri": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", @@ -2320,16 +2307,6 @@ "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", "license": "MIT" }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, "node_modules/responselike": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/responselike/-/responselike-4.0.2.tgz", @@ -2551,14 +2528,13 @@ "license": "0BSD" }, "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", + "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" + "esbuild": "~0.28.0" }, "bin": { "tsx": "dist/cli.mjs" @@ -2583,9 +2559,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2609,9 +2585,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "license": "MIT" }, "node_modules/universalify": { diff --git a/claude-code/package.json b/claude-code/package.json index 6d81acd..4ff364d 100644 --- a/claude-code/package.json +++ b/claude-code/package.json @@ -11,11 +11,11 @@ "build": "tsc -p tsconfig.build.json" }, "dependencies": { - "apify": "^3.4.2" + "apify": "^3.7.2" }, "devDependencies": { - "@types/node": "^22.0.0", - "tsx": "^4.19.2", - "typescript": "^5.7.3" + "@types/node": "^25.9.2", + "tsx": "^4.22.4", + "typescript": "^6.0.3" } } diff --git a/claude-code/tsconfig.build.json b/claude-code/tsconfig.build.json index 2dc9176..f78a908 100644 --- a/claude-code/tsconfig.build.json +++ b/claude-code/tsconfig.build.json @@ -1,6 +1,7 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "outDir": "dist" + "outDir": "dist", + "rootDir": "./src" } } diff --git a/openclaw/package-lock.json b/openclaw/package-lock.json index 8c59e6f..8342319 100644 --- a/openclaw/package-lock.json +++ b/openclaw/package-lock.json @@ -8,12 +8,12 @@ "name": "openclaw-sandbox", "version": "0.0.1", "dependencies": { - "apify": "^3.4.2" + "apify": "^3.7.2" }, "devDependencies": { - "@types/node": "^22.0.0", - "tsx": "^4.19.2", - "typescript": "^5.7.3" + "@types/node": "^25.9.2", + "tsx": "^4.22.4", + "typescript": "^6.0.3" }, "engines": { "node": ">=18" @@ -192,9 +192,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", "cpu": [ "ppc64" ], @@ -209,9 +209,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", "cpu": [ "arm" ], @@ -226,9 +226,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", "cpu": [ "arm64" ], @@ -243,9 +243,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", "cpu": [ "x64" ], @@ -260,9 +260,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", "cpu": [ "arm64" ], @@ -277,9 +277,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", "cpu": [ "x64" ], @@ -294,9 +294,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", "cpu": [ "arm64" ], @@ -311,9 +311,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", "cpu": [ "x64" ], @@ -328,9 +328,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", "cpu": [ "arm" ], @@ -345,9 +345,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", "cpu": [ "arm64" ], @@ -362,9 +362,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", "cpu": [ "ia32" ], @@ -379,9 +379,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", "cpu": [ "loong64" ], @@ -396,9 +396,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", "cpu": [ "mips64el" ], @@ -413,9 +413,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", "cpu": [ "ppc64" ], @@ -430,9 +430,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", "cpu": [ "riscv64" ], @@ -447,9 +447,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", "cpu": [ "s390x" ], @@ -464,9 +464,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", "cpu": [ "x64" ], @@ -481,9 +481,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", "cpu": [ "arm64" ], @@ -498,9 +498,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", "cpu": [ "x64" ], @@ -515,9 +515,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", "cpu": [ "arm64" ], @@ -532,9 +532,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", "cpu": [ "x64" ], @@ -549,9 +549,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", "cpu": [ "arm64" ], @@ -566,9 +566,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", "cpu": [ "x64" ], @@ -583,9 +583,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", "cpu": [ "arm64" ], @@ -600,9 +600,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", "cpu": [ "ia32" ], @@ -617,9 +617,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", "cpu": [ "x64" ], @@ -717,12 +717,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.19.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz", - "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==", + "version": "25.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz", + "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==", "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": ">=7.24.0 <7.24.7" } }, "node_modules/@types/sax": { @@ -772,12 +772,12 @@ } }, "node_modules/apify": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/apify/-/apify-3.5.3.tgz", - "integrity": "sha512-279q+c6Rz2RjQrDzhjrA8pyy40lQBoMnZBl3Ei+zP/gSh0WEVmTH5D/VbHH4TOQpOHv3CaP2Mwg/O2CaMNB4JQ==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/apify/-/apify-3.7.2.tgz", + "integrity": "sha512-I4R5Qd1X11vk5/1U0CxKSsMhNwER/3MntePEQb9Pbtf942OhQLh5IURrJZufqtpimkqRy44SlQMU6UhPdoFXhA==", "license": "Apache-2.0", "dependencies": { - "@apify/consts": "^2.47.1", + "@apify/consts": "^2.51.0", "@apify/input_secrets": "^1.2.0", "@apify/log": "^2.4.3", "@apify/timeout": "^0.3.0", @@ -912,7 +912,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -1319,9 +1318,9 @@ } }, "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1332,32 +1331,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" } }, "node_modules/escalade": { @@ -1618,19 +1617,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-tsconfig": { - "version": "4.13.6", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", - "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, "node_modules/get-uri": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", @@ -2321,16 +2307,6 @@ "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", "license": "MIT" }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, "node_modules/responselike": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/responselike/-/responselike-4.0.2.tgz", @@ -2552,14 +2528,13 @@ "license": "0BSD" }, "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", + "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" + "esbuild": "~0.28.0" }, "bin": { "tsx": "dist/cli.mjs" @@ -2584,9 +2559,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2610,9 +2585,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "license": "MIT" }, "node_modules/universalify": { diff --git a/openclaw/package.json b/openclaw/package.json index be214ee..2dd8148 100644 --- a/openclaw/package.json +++ b/openclaw/package.json @@ -11,11 +11,11 @@ "build": "tsc -p tsconfig.build.json" }, "dependencies": { - "apify": "^3.4.2" + "apify": "^3.7.2" }, "devDependencies": { - "@types/node": "^22.0.0", - "tsx": "^4.19.2", - "typescript": "^5.7.3" + "@types/node": "^25.9.2", + "tsx": "^4.22.4", + "typescript": "^6.0.3" } } diff --git a/openclaw/tsconfig.build.json b/openclaw/tsconfig.build.json index 2dc9176..f78a908 100644 --- a/openclaw/tsconfig.build.json +++ b/openclaw/tsconfig.build.json @@ -1,6 +1,7 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "outDir": "dist" + "outDir": "dist", + "rootDir": "./src" } } diff --git a/opencode/package.json b/opencode/package.json index 7abe3cb..c2e15e6 100644 --- a/opencode/package.json +++ b/opencode/package.json @@ -11,11 +11,11 @@ "build": "tsc -p tsconfig.build.json" }, "dependencies": { - "apify": "^3.4.2" + "apify": "^3.7.2" }, "devDependencies": { - "@types/node": "^22.0.0", - "tsx": "^4.19.2", - "typescript": "^5.7.3" + "@types/node": "^25.9.2", + "tsx": "^4.22.4", + "typescript": "^6.0.3" } } diff --git a/opencode/tsconfig.build.json b/opencode/tsconfig.build.json index 2dc9176..f78a908 100644 --- a/opencode/tsconfig.build.json +++ b/opencode/tsconfig.build.json @@ -1,6 +1,7 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "outDir": "dist" + "outDir": "dist", + "rootDir": "./src" } } diff --git a/sandbox/package-lock.json b/sandbox/package-lock.json index 097765d..9bcb29e 100644 --- a/sandbox/package-lock.json +++ b/sandbox/package-lock.json @@ -9,33 +9,33 @@ "version": "0.0.1", "license": "ISC", "dependencies": { - "@modelcontextprotocol/sdk": "^1.27.1", - "@types/archiver": "^7.0.0", + "@modelcontextprotocol/sdk": "^1.29.0", + "@types/archiver": "^8.0.0", "@types/mime-types": "^3.0.1", - "apify": "^3.7.0", - "archiver": "^7.0.1", - "ejs": "^5.0.1", + "apify": "^3.7.2", + "archiver": "^8.0.0", + "ejs": "^6.0.1", "express": "^5.2.1", "http-proxy": "^1.18.1", "mime-types": "^3.0.2", "node-html-markdown": "^2.0.0", "node-html-parser": "^7.1.0", - "zod": "^4.3.6" + "zod": "^4.4.3" }, "devDependencies": { - "@apify/eslint-config": "^1.1.0", - "@apify/tsconfig": "^0.1.1", + "@apify/eslint-config": "^2.0.6", + "@apify/tsconfig": "^0.1.2", "@types/ejs": "^3.1.5", "@types/express": "^5.0.6", "@types/http-proxy": "^1.17.17", - "@types/node": "^25.5.0", - "eslint": "^9.29.0", + "@types/node": "^25.9.2", + "eslint": "^9.39.4", "eslint-config-prettier": "^10.1.8", - "globals": "^17.4.0", - "prettier": "^3.8.1", - "tsx": "^4.21.0", - "typescript": "^5.9.3", - "typescript-eslint": "^8.57.0" + "globals": "^17.6.0", + "prettier": "^3.8.3", + "tsx": "^4.22.4", + "typescript": "^6.0.3", + "typescript-eslint": "^8.61.0" }, "engines": { "node": ">=20.0.0" @@ -54,24 +54,32 @@ "license": "Apache-2.0" }, "node_modules/@apify/eslint-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@apify/eslint-config/-/eslint-config-1.1.0.tgz", - "integrity": "sha512-3rVBzGcRP13aN8pcYz8qkbJ7ji4oJ/haabaiRiI43ytKBwasMA4rAcH0ZifVKm2qOAOj5Zd0e9YSuiqlTPDoDg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@apify/eslint-config/-/eslint-config-2.0.6.tgz", + "integrity": "sha512-8Ai9bHAB3wtVOupZO0TwrM3ZaEUdh//764DEw0uOxfp71xOApS3NdAYzy/6B22jnpd/cnOXqyz80kXLQiqicAQ==", "dev": true, "license": "ISC", "dependencies": { - "@eslint/compat": "^1.2.6", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-plugin-import": "^2.32.0", + "eslint-import-resolver-node": "^0.3.10", + "eslint-plugin-import-x": "^4.16.2", "eslint-plugin-simple-import-sort": "^12.1.1", "globals": "^15.14.0" }, "peerDependencies": { + "@eslint/js": "^9.19.0", + "@stylistic/eslint-plugin": "^5.0.0", + "@vitest/eslint-plugin": "^1.6.14", "eslint": "^9.19.0", "eslint-plugin-jest": "^28.11.0", "typescript-eslint": "^8.23.0" }, "peerDependenciesMeta": { + "@stylistic/eslint-plugin": { + "optional": true + }, + "@vitest/eslint-plugin": { + "optional": true + }, "eslint-plugin-jest": { "optional": true }, @@ -80,102 +88,6 @@ } } }, - "node_modules/@apify/eslint-config/node_modules/@eslint/compat": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.4.1.tgz", - "integrity": "sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "peerDependencies": { - "eslint": "^8.40 || 9" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/@apify/eslint-config/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@apify/eslint-config/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/@apify/eslint-config/node_modules/eslint-config-airbnb-base": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", - "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", - "dev": true, - "license": "MIT", - "dependencies": { - "confusing-browser-globals": "^1.0.10", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5", - "semver": "^6.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.2" - } - }, - "node_modules/@apify/eslint-config/node_modules/eslint-plugin-import": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", - "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.9", - "array.prototype.findlastindex": "^1.2.6", - "array.prototype.flat": "^1.3.3", - "array.prototype.flatmap": "^1.3.3", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.1", - "hasown": "^2.0.2", - "is-core-module": "^2.16.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "object.groupby": "^1.0.3", - "object.values": "^1.2.1", - "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.9", - "tsconfig-paths": "^3.15.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" - } - }, "node_modules/@apify/eslint-config/node_modules/globals": { "version": "15.15.0", "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", @@ -189,29 +101,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@apify/eslint-config/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@apify/eslint-config/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@apify/input_secrets": { "version": "1.2.19", "resolved": "https://registry.npmjs.org/@apify/input_secrets/-/input_secrets-1.2.19.tgz", @@ -264,9 +153,9 @@ "license": "Apache-2.0" }, "node_modules/@apify/tsconfig": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@apify/tsconfig/-/tsconfig-0.1.1.tgz", - "integrity": "sha512-cS7mwN2UW1UXcluGXRDHH0Vr2VsSLkw2DwLTwoSBkcJSe8fvCr3MPryTSq0uod4MashpMURxJ7CsLKxs82VmOQ==", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@apify/tsconfig/-/tsconfig-0.1.2.tgz", + "integrity": "sha512-9dzEI1ZQ5+iM0k0fmPJrpdSSPUolVdeI1nDGFZMjD9UabTmIvjQrzui+1a25uy913AUEBrKTojEPj87pU9/Ekg==", "dev": true, "license": "Apache-2.0" }, @@ -400,10 +289,44 @@ "node": ">=16.0.0" } }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz", - "integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", "cpu": [ "ppc64" ], @@ -418,9 +341,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz", - "integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", "cpu": [ "arm" ], @@ -435,9 +358,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz", - "integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", "cpu": [ "arm64" ], @@ -452,9 +375,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz", - "integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", "cpu": [ "x64" ], @@ -469,9 +392,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz", - "integrity": "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", "cpu": [ "arm64" ], @@ -486,9 +409,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz", - "integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", "cpu": [ "x64" ], @@ -503,9 +426,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz", - "integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", "cpu": [ "arm64" ], @@ -520,9 +443,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz", - "integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", "cpu": [ "x64" ], @@ -537,9 +460,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz", - "integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", "cpu": [ "arm" ], @@ -554,9 +477,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz", - "integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", "cpu": [ "arm64" ], @@ -571,9 +494,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz", - "integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", "cpu": [ "ia32" ], @@ -588,9 +511,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz", - "integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", "cpu": [ "loong64" ], @@ -605,9 +528,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz", - "integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", "cpu": [ "mips64el" ], @@ -622,9 +545,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz", - "integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", "cpu": [ "ppc64" ], @@ -639,9 +562,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz", - "integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", "cpu": [ "riscv64" ], @@ -656,9 +579,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz", - "integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", "cpu": [ "s390x" ], @@ -673,9 +596,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz", - "integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", "cpu": [ "x64" ], @@ -690,9 +613,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz", - "integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", "cpu": [ "arm64" ], @@ -707,9 +630,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz", - "integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", "cpu": [ "x64" ], @@ -724,9 +647,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz", - "integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", "cpu": [ "arm64" ], @@ -741,9 +664,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz", - "integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", "cpu": [ "x64" ], @@ -758,9 +681,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz", - "integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", "cpu": [ "arm64" ], @@ -775,9 +698,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz", - "integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", "cpu": [ "x64" ], @@ -792,9 +715,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz", - "integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", "cpu": [ "arm64" ], @@ -809,9 +732,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz", - "integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", "cpu": [ "ia32" ], @@ -826,9 +749,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz", - "integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", "cpu": [ "x64" ], @@ -1111,23 +1034,6 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@keyv/serialize": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", @@ -1135,9 +1041,9 @@ "license": "MIT" }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -1196,20 +1102,29 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, "license": "MIT", "optional": true, - "engines": { - "node": ">=14" + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "node_modules/@package-json/types": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@package-json/types/-/types-0.0.12.tgz", + "integrity": "sha512-uu43FGU34B5VM9mCNjXCwLaGHYjXdNincqKLaraaCW+7S2+SmiBg1Nv8bPnmschrIfZmfKNY9f3fC376MRrObw==", "dev": true, "license": "MIT" }, @@ -1278,12 +1193,24 @@ "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", "license": "MIT" }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/archiver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-7.0.0.tgz", - "integrity": "sha512-/3vwGwx9n+mCQdYZ2IKGGHEFL30I96UgBlk8EtRDDFQ9uxM1l4O5Ci6r00EMAkiDaTqD9DQ6nVrWRICnBPtzzg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-8.0.0.tgz", + "integrity": "sha512-YpXPbEuv9+eUIPPQWUPahj3cvs9isWRuF+J4z+KbdYVDO3rWorWQFxUVHnwPu2AgKwvgpki5F2VMX0Xx+mX45A==", "license": "MIT", "dependencies": { + "@types/node": "*", "@types/readdir-glob": "*" } }, @@ -1377,13 +1304,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/mime-types": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-3.0.1.tgz", @@ -1391,12 +1311,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", - "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "version": "25.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz", + "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==", "license": "MIT", "dependencies": { - "undici-types": "~7.18.0" + "undici-types": ">=7.24.0 <7.24.7" } }, "node_modules/@types/qs": { @@ -1453,20 +1373,20 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.0.tgz", - "integrity": "sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.0.tgz", + "integrity": "sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.57.0", - "@typescript-eslint/type-utils": "8.57.0", - "@typescript-eslint/utils": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0", + "@typescript-eslint/scope-manager": "8.61.0", + "@typescript-eslint/type-utils": "8.61.0", + "@typescript-eslint/utils": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1476,9 +1396,9 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.57.0", + "@typescript-eslint/parser": "^8.61.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { @@ -1492,16 +1412,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.0.tgz", - "integrity": "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.0.tgz", + "integrity": "sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.57.0", - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0", + "@typescript-eslint/scope-manager": "8.61.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0", "debug": "^4.4.3" }, "engines": { @@ -1513,18 +1433,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.0.tgz", - "integrity": "sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.0.tgz", + "integrity": "sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.57.0", - "@typescript-eslint/types": "^8.57.0", + "@typescript-eslint/tsconfig-utils": "^8.61.0", + "@typescript-eslint/types": "^8.61.0", "debug": "^4.4.3" }, "engines": { @@ -1535,18 +1455,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.0.tgz", - "integrity": "sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.0.tgz", + "integrity": "sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0" + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1557,9 +1477,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.0.tgz", - "integrity": "sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.0.tgz", + "integrity": "sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ==", "dev": true, "license": "MIT", "engines": { @@ -1570,21 +1490,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.0.tgz", - "integrity": "sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.0.tgz", + "integrity": "sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0", - "@typescript-eslint/utils": "8.57.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0", + "@typescript-eslint/utils": "8.61.0", "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1595,13 +1515,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.0.tgz", - "integrity": "sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.0.tgz", + "integrity": "sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==", "dev": true, "license": "MIT", "engines": { @@ -1613,21 +1533,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.0.tgz", - "integrity": "sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.0.tgz", + "integrity": "sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.57.0", - "@typescript-eslint/tsconfig-utils": "8.57.0", - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0", + "@typescript-eslint/project-service": "8.61.0", + "@typescript-eslint/tsconfig-utils": "8.61.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/visitor-keys": "8.61.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1637,7 +1557,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { @@ -1651,9 +1571,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { @@ -1664,13 +1584,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -1680,16 +1600,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.0.tgz", - "integrity": "sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.0.tgz", + "integrity": "sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.57.0", - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0" + "@typescript-eslint/scope-manager": "8.61.0", + "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1700,17 +1620,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.0.tgz", - "integrity": "sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.0.tgz", + "integrity": "sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/types": "8.61.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -1721,6 +1641,319 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@vladfrangu/async_event_emitter": { "version": "2.4.7", "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", @@ -1853,22 +2086,11 @@ "node": ">=6" } }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -1881,9 +2103,9 @@ } }, "node_modules/apify": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/apify/-/apify-3.7.0.tgz", - "integrity": "sha512-2WeMeI6BIH2ql5dtnsreKcWxE+OGqsj9LKEe1c0b7kaQpqlEHXN63OqPfDmX1EFjuGo5Z1I3uVYBx3BTQZ9+Ag==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/apify/-/apify-3.7.2.tgz", + "integrity": "sha512-I4R5Qd1X11vk5/1U0CxKSsMhNwER/3MntePEQb9Pbtf942OhQLh5IURrJZufqtpimkqRy44SlQMU6UhPdoFXhA==", "license": "Apache-2.0", "dependencies": { "@apify/consts": "^2.51.0", @@ -1925,133 +2147,41 @@ } }, "node_modules/archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-8.0.0.tgz", + "integrity": "sha512-fV1orZfsnPn9BaSByR/qE67rJCLJEy2Ox5bq7nJh+jquWaNh6Sfec75kJ2T6PtdGUbPQlrVoSVCEOa5SdiTQ1g==", "license": "MIT", "dependencies": { - "archiver-utils": "^5.0.2", "async": "^3.2.4", "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", - "license": "MIT", - "dependencies": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", + "is-stream": "^4.0.0", "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/archiver-utils/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-includes": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", - "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", - "get-intrinsic": "^1.3.0", - "is-string": "^1.1.1", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", - "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-shim-unscopables": "^1.1.0" + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^3.0.0", + "tar-stream": "^3.0.0", + "zip-stream": "^7.0.2" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/array.prototype.flat": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", - "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" }, "engines": { "node": ">= 0.4" @@ -2385,15 +2515,15 @@ } }, "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" }, "engines": { @@ -2520,6 +2650,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -2532,6 +2663,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -2546,32 +2678,30 @@ "node": ">= 0.8" } }, + "node_modules/comment-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.7.tgz", + "integrity": "sha512-0h+uSNtQGW3D98eQt3jJ8L06Fves8hncB4V/PKdw/Qb8Hnk19VaKuTr55UNRYiSoVa7WwrFls+rh3ux9agmkeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-7.0.1.tgz", + "integrity": "sha512-g0S8KAD8qf4+V//pr3BfB1aBnARLXNz2Gx+jmHU0LEriUuoQUOPOulVquHKTJ8+EAIIO7fhseNDr9wK5Q9FKBQ==", "license": "MIT", "dependencies": { "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", + "crc32-stream": "^7.0.1", + "is-stream": "^4.0.0", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 14" - } - }, - "node_modules/compress-commons/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, "node_modules/concat-map": { @@ -2581,13 +2711,6 @@ "dev": true, "license": "MIT" }, - "node_modules/confusing-browser-globals": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", - "dev": true, - "license": "MIT" - }, "node_modules/content-disposition": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", @@ -2660,16 +2783,16 @@ } }, "node_modules/crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-7.0.1.tgz", + "integrity": "sha512-IBWsY8xznyQrcHn8h4bC8/4ErNke5elzgG8GcqF4RFPw6aHkWWRc7Tgw6upjaTX/CT/yQgqYENkxYsTYN+hW2g==", "license": "MIT", "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 14" + "node": ">=18" } }, "node_modules/cross-spawn": { @@ -2867,19 +2990,6 @@ "node": ">= 0.8" } }, - "node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -2970,12 +3080,6 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "license": "MIT" }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -2983,9 +3087,9 @@ "license": "MIT" }, "node_modules/ejs": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-5.0.1.tgz", - "integrity": "sha512-COqBPFMxuPTPspXl2DkVYaDS3HtrD1GpzOGkNTJ1IYkifq/r9h8SVEFrjA3D9/VJGOEoMQcrlhpntcSUrM8k6A==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-6.0.1.tgz", + "integrity": "sha512-UaaM14yby8U3k02ihS1Bmj5Kz2d7CCQM1scxpgs4Mhkq8F1wR2gl3+Ts4h5Ne4Mnt7M9m4Dw7jsuMr3+xO4vZA==", "license": "Apache-2.0", "bin": { "ejs": "bin/cli.js" @@ -3000,12 +3104,6 @@ "integrity": "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg==", "license": "ISC" }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -3028,9 +3126,9 @@ } }, "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", "dev": true, "license": "MIT", "dependencies": { @@ -3115,9 +3213,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -3173,9 +3271,9 @@ } }, "node_modules/esbuild": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.1.tgz", - "integrity": "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3186,32 +3284,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.1", - "@esbuild/android-arm": "0.27.1", - "@esbuild/android-arm64": "0.27.1", - "@esbuild/android-x64": "0.27.1", - "@esbuild/darwin-arm64": "0.27.1", - "@esbuild/darwin-x64": "0.27.1", - "@esbuild/freebsd-arm64": "0.27.1", - "@esbuild/freebsd-x64": "0.27.1", - "@esbuild/linux-arm": "0.27.1", - "@esbuild/linux-arm64": "0.27.1", - "@esbuild/linux-ia32": "0.27.1", - "@esbuild/linux-loong64": "0.27.1", - "@esbuild/linux-mips64el": "0.27.1", - "@esbuild/linux-ppc64": "0.27.1", - "@esbuild/linux-riscv64": "0.27.1", - "@esbuild/linux-s390x": "0.27.1", - "@esbuild/linux-x64": "0.27.1", - "@esbuild/netbsd-arm64": "0.27.1", - "@esbuild/netbsd-x64": "0.27.1", - "@esbuild/openbsd-arm64": "0.27.1", - "@esbuild/openbsd-x64": "0.27.1", - "@esbuild/openharmony-arm64": "0.27.1", - "@esbuild/sunos-x64": "0.27.1", - "@esbuild/win32-arm64": "0.27.1", - "@esbuild/win32-ia32": "0.27.1", - "@esbuild/win32-x64": "0.27.1" + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" } }, "node_modules/escalade": { @@ -3318,16 +3416,41 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-import-context": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", + "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-tsconfig": "^4.10.1", + "stable-hash-x": "^0.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-context" + }, + "peerDependencies": { + "unrs-resolver": "^1.0.0" + }, + "peerDependenciesMeta": { + "unrs-resolver": { + "optional": true + } + } + }, "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", "dev": true, "license": "MIT", "dependencies": { "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" } }, "node_modules/eslint-import-resolver-node/node_modules/debug": { @@ -3340,34 +3463,44 @@ "ms": "^2.1.1" } }, - "node_modules/eslint-module-utils": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", - "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "node_modules/eslint-plugin-import-x": { + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.16.2.tgz", + "integrity": "sha512-rM9K8UBHcWKpzQzStn1YRN2T5NvdeIfSVoKu/lKF41znQXHAUcBbYXe5wd6GNjZjTrP7viQ49n1D83x/2gYgIw==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^3.2.7" + "@package-json/types": "^0.0.12", + "@typescript-eslint/types": "^8.56.0", + "comment-parser": "^1.4.1", + "debug": "^4.4.1", + "eslint-import-context": "^0.1.9", + "is-glob": "^4.0.3", + "minimatch": "^9.0.3 || ^10.1.2", + "semver": "^7.7.2", + "stable-hash-x": "^0.2.0", + "unrs-resolver": "^1.9.2" }, "engines": { - "node": ">=4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-import-x" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^8.56.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "eslint-import-resolver-node": "*" }, "peerDependenciesMeta": { - "eslint": { + "@typescript-eslint/utils": { + "optional": true + }, + "eslint-import-resolver-node": { "optional": true } } }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, "node_modules/eslint-plugin-simple-import-sort": { "version": "12.1.1", "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.1.1.tgz", @@ -3853,34 +3986,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -4112,9 +4217,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", "dev": true, "license": "MIT", "dependencies": { @@ -4124,26 +4229,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -4158,9 +4243,9 @@ } }, "node_modules/globals": { - "version": "17.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz", - "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==", + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", "dev": true, "license": "MIT", "engines": { @@ -4408,9 +4493,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -4733,13 +4818,13 @@ } }, "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "hasown": "^2.0.3" }, "engines": { "node": ">= 0.4" @@ -4809,15 +4894,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-generator-function": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", @@ -5079,21 +5155,6 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/jose": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", @@ -5287,12 +5348,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, "node_modules/map-stream": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", @@ -5380,31 +5435,28 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -5421,6 +5473,35 @@ "node": ">= 0.6" } }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/node-html-markdown": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/node-html-markdown/-/node-html-markdown-2.0.0.tgz", @@ -5523,94 +5604,41 @@ "node": ">= 0.4" } }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.entries": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", - "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.groupby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.values": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", - "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", - "call-bound": "^1.0.3", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "es-object-atoms": "^1.1.1" }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/on-finished": { @@ -5742,12 +5770,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -5833,22 +5855,6 @@ "dev": true, "license": "MIT" }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/pause-stream": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", @@ -5868,9 +5874,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -5910,9 +5916,9 @@ } }, "node_modules/prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", "bin": { @@ -6057,24 +6063,54 @@ } }, "node_modules/readdir-glob": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", - "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-3.0.0.tgz", + "integrity": "sha512-AhNB2KgKeVJr16nK9LLZbJNWnYoT23ZrumNKFDebHBdkC8KHSqWo871JAUhoWC/RtjEVdqNMFpM6qrwRbaUqpw==", "license": "Apache-2.0", "dependencies": { - "minimatch": "^5.1.0" + "minimatch": "^10.2.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/yqnn" + } + }, + "node_modules/readdir-glob/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/readdir-glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "license": "ISC", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=10" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/reflect.getprototypeof": { @@ -6137,13 +6173,16 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.1", + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -6243,15 +6282,15 @@ } }, "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, @@ -6552,6 +6591,16 @@ "node": "*" } }, + "node_modules/stable-hash-x": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", + "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -6619,79 +6668,21 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -6701,16 +6692,16 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "es-object-atoms": "^1.1.2" }, "engines": { "node": ">= 0.4" @@ -6737,53 +6728,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -6866,14 +6810,14 @@ "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -6940,9 +6884,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -6952,32 +6896,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -6985,14 +6903,13 @@ "license": "0BSD" }, "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", + "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" + "esbuild": "~0.28.0" }, "bin": { "tsx": "dist/cli.mjs" @@ -7101,18 +7018,18 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" }, "engines": { "node": ">= 0.4" @@ -7122,9 +7039,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -7136,16 +7053,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.0.tgz", - "integrity": "sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA==", + "version": "8.61.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.61.0.tgz", + "integrity": "sha512-8y31Rd0eGTrDKqhy6vT0HtzhN+YLjQizwX3aA3hPXP/ynSfnrBXcQY5IzsP9/DM7+klX4IUncZZjkchP0z+rUw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.57.0", - "@typescript-eslint/parser": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0", - "@typescript-eslint/utils": "8.57.0" + "@typescript-eslint/eslint-plugin": "8.61.0", + "@typescript-eslint/parser": "8.61.0", + "@typescript-eslint/typescript-estree": "8.61.0", + "@typescript-eslint/utils": "8.61.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7156,7 +7073,7 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/uint8array-extras": { @@ -7191,9 +7108,9 @@ } }, "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "license": "MIT" }, "node_modules/universalify": { @@ -7214,6 +7131,44 @@ "node": ">= 0.8" } }, + "node_modules/unrs-resolver": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", @@ -7370,14 +7325,14 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", - "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", + "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", @@ -7401,94 +7356,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -7530,23 +7397,23 @@ } }, "node_modules/zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-7.0.5.tgz", + "integrity": "sha512-dSvYKdvLsAHCDqPOhIwk/q5CvuWtTB3Dgpoe0uVEFjTzIOAmsQpprX25InCvrvJsirEbu1OHyy67n/kAj1Sw/w==", "license": "MIT", "dependencies": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", + "compress-commons": "^7.0.0", + "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 14" + "node": ">=18" } }, "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/sandbox/package.json b/sandbox/package.json index 7aae7ff..28268a8 100644 --- a/sandbox/package.json +++ b/sandbox/package.json @@ -7,33 +7,33 @@ "node": ">=20.0.0" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.27.1", - "@types/archiver": "^7.0.0", + "@modelcontextprotocol/sdk": "^1.29.0", + "@types/archiver": "^8.0.0", "@types/mime-types": "^3.0.1", - "apify": "^3.7.0", - "archiver": "^7.0.1", - "ejs": "^5.0.1", + "apify": "^3.7.2", + "archiver": "^8.0.0", + "ejs": "^6.0.1", "express": "^5.2.1", "http-proxy": "^1.18.1", "mime-types": "^3.0.2", "node-html-markdown": "^2.0.0", "node-html-parser": "^7.1.0", - "zod": "^4.3.6" + "zod": "^4.4.3" }, "devDependencies": { - "@apify/eslint-config": "^1.1.0", - "@apify/tsconfig": "^0.1.1", + "@apify/eslint-config": "^2.0.6", + "@apify/tsconfig": "^0.1.2", "@types/ejs": "^3.1.5", "@types/express": "^5.0.6", "@types/http-proxy": "^1.17.17", - "@types/node": "^25.5.0", - "eslint": "^9.29.0", + "@types/node": "^25.9.2", + "eslint": "^9.39.4", "eslint-config-prettier": "^10.1.8", - "globals": "^17.4.0", - "prettier": "^3.8.1", - "tsx": "^4.21.0", - "typescript": "^5.9.3", - "typescript-eslint": "^8.57.0" + "globals": "^17.6.0", + "prettier": "^3.8.3", + "tsx": "^4.22.4", + "typescript": "^6.0.3", + "typescript-eslint": "^8.61.0" }, "scripts": { "start": "npm run start:dev", diff --git a/sandbox/src/operations.ts b/sandbox/src/operations.ts index eb5282a..3d9324b 100644 --- a/sandbox/src/operations.ts +++ b/sandbox/src/operations.ts @@ -7,7 +7,7 @@ import type { Readable } from 'node:stream'; import { promisify } from 'node:util'; import { log } from 'apify'; -import archiver from 'archiver'; +import { ZipArchive } from 'archiver'; import mime from 'mime-types'; import { JS_TS_CODE_DIR, PYTHON_CODE_DIR, SANDBOX_DIR } from './consts.js'; @@ -709,7 +709,7 @@ export const createZipArchive = async ( } // Create archive - const archive = archiver('zip', { + const archive = new ZipArchive({ zlib: { level: 6 }, // Compression level }); diff --git a/sandbox/tsconfig.build.json b/sandbox/tsconfig.build.json index 29d9617..c3e6909 100644 --- a/sandbox/tsconfig.build.json +++ b/sandbox/tsconfig.build.json @@ -1,7 +1,8 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "outDir": "dist" + "outDir": "dist", + "rootDir": "./src" }, "exclude": ["./tests/**/*"] } From 7075425312f69ff1218b6f04e02dba5a14a6baa4 Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Mon, 8 Jun 2026 22:53:17 +0200 Subject: [PATCH 54/56] Relabel /llms.txt health link and link the Actor page in the lead (#80) - /llms.txt now shows a "Health check" link to /health instead of the live page's transient "Checking..." text (kept page-only via data-no-md) - Link "Actor" in the hero lead to https://apify.com/apify/ai-code-sandbox - Document that /llms.txt fetches already reset the idle-shutdown timer https://claude.ai/code/session_01N3HNsUofRw1jgDG6Xq6Z7G Co-authored-by: Claude --- sandbox/src/main.ts | 3 +++ sandbox/src/templates/landing.css | 5 +++++ sandbox/src/templates/landing.ejs | 11 ++++++----- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/sandbox/src/main.ts b/sandbox/src/main.ts index 4ab5ee2..7e8f4e1 100644 --- a/sandbox/src/main.ts +++ b/sandbox/src/main.ts @@ -236,6 +236,9 @@ app.use((req, _res, next) => { }); } + // Any non-health request — including doc fetches like /llms.txt — counts as + // activity and pushes back the idle-shutdown timer. Only /health and the + // readiness probe are excluded, since they fire automatically. if (!isHealth && !isProbe) { lastActivityAt = Date.now(); } diff --git a/sandbox/src/templates/landing.css b/sandbox/src/templates/landing.css index d6e6282..ea6a9c2 100644 --- a/sandbox/src/templates/landing.css +++ b/sandbox/src/templates/landing.css @@ -379,6 +379,11 @@ a.status-badge:hover { font-size: 12px; line-height: 1; } +/* Rendered only in the generated /llms.txt Markdown (where CSS doesn't apply); + hidden in the live page, which shows the JS-driven status text instead. */ +.md-only { + display: none; +} /* Keep countdown digits fixed-width so the badge doesn't jiggle as it ticks */ .countdown { font-variant-numeric: tabular-nums; diff --git a/sandbox/src/templates/landing.ejs b/sandbox/src/templates/landing.ejs index 96b077e..fa188cc 100644 --- a/sandbox/src/templates/landing.ejs +++ b/sandbox/src/templates/landing.ejs @@ -21,16 +21,17 @@
    - <%# The status badge is deliberately NOT marked data-no-md: it is kept in %> - <%# /llms.txt purely so the /health URL is still surfaced to LLMs now that %> - <%# the visible "service status" sentence has been removed. %> - Checking... + <%# The status-badge link is kept in /llms.txt so the /health URL stays %> + <%# discoverable by LLMs. The live "Checking..." text is page-only %> + <%# (data-no-md, swapped out by JS); the Markdown shows a static %> + <%# "Health check" label via the .md-only span instead. %> + Checking...Health check <% if (isLocalMode) { %>
    <%= modeLabel %>
    <% } %> -

    An Actor for secure execution of arbitrary code. Connect using MCP, REST API, or interactive shell.<% if (idleTimeoutSecs > 0) { %> The Actor shuts down automatically after <%= idleTimeoutLabel %> of inactivity.<% } %>

    +

    An Actor for secure execution of arbitrary code. Connect using MCP, REST API, or interactive shell.<% if (idleTimeoutSecs > 0) { %> The Actor shuts down automatically after <%= idleTimeoutLabel %> of inactivity.<% } %>

    From 373839b2b6c3f97e3c81d8e60fae54bdca2111fd Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Mon, 8 Jun 2026 23:37:34 +0200 Subject: [PATCH 55/56] Better copy --- sandbox/Dockerfile | 2 +- sandbox/artifacts/AGENTS.md | 6 +++--- sandbox/scripts/capture-versions.sh | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/sandbox/Dockerfile b/sandbox/Dockerfile index 734ff6f..eed2798 100644 --- a/sandbox/Dockerfile +++ b/sandbox/Dockerfile @@ -91,7 +91,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # - tsx: TypeScript execution in the execute-code endpoint # - apify-cli: --ignore-scripts works around apify-client's `only-allow pnpm` # preinstall hook -# - @apify/mcpc: Apify MCP CLI (binary: mcpc) +# - @apify/mcpc: MCP CLI (binary: mcpc) RUN npm install -g tsx \ && npm install -g --ignore-scripts apify-cli \ && npm install -g @apify/mcpc \ diff --git a/sandbox/artifacts/AGENTS.md b/sandbox/artifacts/AGENTS.md index 1439ca8..f73cad8 100644 --- a/sandbox/artifacts/AGENTS.md +++ b/sandbox/artifacts/AGENTS.md @@ -6,7 +6,7 @@ This document contains instructions for AI coding agents working inside the Apif ### 🚨 CRITICAL: Always Generate Signed Public URLs -When sharing data with users, **NEVER return just storage IDs or raw API URLs** — they require authentication. +When sharing data with users, **NEVER return just storage IDs or raw API URLs** — they require authentication. **ALWAYS generate signed public URLs** that work without authentication. ### Key-Value Stores (Files & Binary Data) @@ -557,7 +557,7 @@ cat /sandbox/py/report.pdf | apify actor set-value report.pdf --content-type app ## Resources -- [Apify CLI Documentation](https://docs.apify.com/cli) -- [Apify MCP CLI Repository](https://github.com/apify/mcp-cli) +- [Apify CLI documentation](https://docs.apify.com/cli) +- [mcpc MCP CLI repository](https://github.com/apify/mcpc) - [Key-Value Store API](https://docs.apify.com/api/v2#/reference/key-value-stores) - [Dataset API](https://docs.apify.com/api/v2#/reference/datasets) diff --git a/sandbox/scripts/capture-versions.sh b/sandbox/scripts/capture-versions.sh index 499821f..b9a2592 100644 --- a/sandbox/scripts/capture-versions.sh +++ b/sandbox/scripts/capture-versions.sh @@ -34,12 +34,12 @@ else echo "⚠️ Apify CLI: not installed" fi -# Capture MCP CLI version (optional) +# Capture mcpc version (optional) if mcpc --version > "$VERSION_DIR/mcpc.txt" 2>/dev/null; then - echo "✅ MCP CLI: $(cat "$VERSION_DIR/mcpc.txt")" + echo "✅ mcpc: $(cat "$VERSION_DIR/mcpc.txt")" else echo "not installed" > "$VERSION_DIR/mcpc.txt" - echo "⚠️ MCP CLI: not installed" + echo "⚠️ mcpc: not installed" fi # Claude Code, OpenCode, and Codex are NOT captured here: they are installed From 4e1283bd2594d612071fa8816ff1ce060cb71e02 Mon Sep 17 00:00:00 2001 From: Jan Curn Date: Mon, 8 Jun 2026 23:51:15 +0200 Subject: [PATCH 56/56] docs(sandbox): link pre-installed and pre-configured software (#83) Add links to the runtimes, CLIs, libraries, and coding agents in the "Sandbox environment" section, and relabel the agents bullet as pre-configured so the inventory is clearer. https://claude.ai/code/session_01194YUUpiWPJHDWizv3iDiT Co-authored-by: Claude --- sandbox/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sandbox/README.md b/sandbox/README.md index 2970a0e..d146891 100644 --- a/sandbox/README.md +++ b/sandbox/README.md @@ -152,12 +152,12 @@ Dependencies install at startup before any code runs. For cost efficiency, set t ## Sandbox environment -- **Base image:** Debian Trixie with **Node.js 24** and **Python 3** (`venv` at `/sandbox/py/venv`). -- **Pre-installed tools:** git, openssh-client, curl, wget, jq, build-essential, `tsx`, `apify-cli`, `mcpc`, and `ttyd`; `apify-client` is ready in both the Node and Python environments. -- **Coding agents:** Claude Code, Codex CLI, and OpenCode — installed on first launch and wired to the Apify OpenRouter proxy (authenticated with `APIFY_TOKEN`). +- **Base image:** [Debian Trixie](https://www.debian.org/releases/trixie/) with [Node.js 24](https://nodejs.org/) and [Python 3](https://www.python.org/) (`venv` at `/sandbox/py/venv`). +- **Pre-installed tools:** [git](https://git-scm.com/), [openssh-client](https://www.openssh.com/), [curl](https://curl.se/), [wget](https://www.gnu.org/software/wget/), [jq](https://jqlang.org/), [build-essential](https://packages.debian.org/trixie/build-essential), [`tsx`](https://tsx.is/), [`apify-cli`](https://docs.apify.com/cli/), [`mcpc`](https://github.com/apify/mcpc), and [`ttyd`](https://github.com/tsl0922/ttyd). The [`apify-client`](https://docs.apify.com/api/client/js/) library is preinstalled in the Node environment, and the [Python `apify-client`](https://docs.apify.com/api/client/python/) in the venv. +- **Pre-configured coding agents:** [Claude Code](https://code.claude.com/), [Codex CLI](https://github.com/openai/codex), and [OpenCode](https://opencode.ai/) — installed on first launch and wired to the [Apify OpenRouter proxy](https://apify.com/apify/openrouter) (authenticated with `APIFY_TOKEN`), with confirmation prompts auto-approved (safe inside the sandbox). - **Working directories:** `/sandbox` (shell), `/sandbox/js-ts` (npm packages in `node_modules`), `/sandbox/py` (Python venv). - **Persistence:** filesystem changes are backed up to the Actor's key-value store and restored after a container migration, so work survives restarts (dependency directories are excluded and reinstalled). -- **Agent context:** `AGENTS.md` and `CLAUDE.md` are placed in `/sandbox` to guide the coding agents. +- **Agent context:** [`AGENTS.md`](https://agents.md/) and `CLAUDE.md` are placed in `/sandbox` to guide the coding agents. ## Learn more