diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
index c383265d..c2049aac 100644
--- a/.github/workflows/check.yml
+++ b/.github/workflows/check.yml
@@ -5,6 +5,10 @@ on:
branches:
- main
+concurrency:
+ group: check-${{ github.ref }}
+ cancel-in-progress: true
+
jobs:
check:
name: Lint, typecheck, format
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index aa2c9845..fb182495 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -6,6 +6,10 @@ on:
branches:
- main
+concurrency:
+ group: deploy
+ cancel-in-progress: false
+
jobs:
build-site:
name: Build & push image
diff --git a/.github/workflows/generator.yml b/.github/workflows/generator.yml
index 12026063..4709e231 100644
--- a/.github/workflows/generator.yml
+++ b/.github/workflows/generator.yml
@@ -18,19 +18,15 @@ on:
concurrency:
group: generator-${{ github.ref }}
- cancel-in-progress: true
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
snapshot-tests:
name: Snapshot tests
runs-on: ubuntu-latest
- outputs:
- changed: ${{ steps.check.outputs.changed }}
steps:
- name: Checkout repo
uses: actions/checkout@v4
- with:
- fetch-depth: 0
- name: Setup Bun
uses: oven-sh/setup-bun@v2
@@ -48,15 +44,8 @@ jobs:
working-directory: packages/generator
run: bun test
- - name: Check for generator changes
- if: github.event_name == 'push'
- id: check
- run: |
- CHANGED=$(git diff --name-only HEAD~1 HEAD -- packages/generator/ packages/openapi-parser/ | grep -q . && echo true || echo false)
- echo "changed=$CHANGED" >> $GITHUB_OUTPUT
-
publish-generator:
- if: github.event_name == 'push' && needs.snapshot-tests.outputs.changed == 'true'
+ if: github.event_name == 'push'
needs: snapshot-tests
runs-on: ubuntu-latest
concurrency:
@@ -84,14 +73,40 @@ jobs:
working-directory: packages/generator
run: bun run build
+ - name: Set version (auto-increment patch)
+ id: version
+ working-directory: packages/generator
+ run: |
+ VERSION=$(node -e "
+ const { execSync } = require('child_process');
+ const base = require('./package.json').version.split('.').slice(0, 2).join('.');
+ let versions = [];
+ try {
+ const raw = execSync('npm view @pachca/generator versions --json', { stdio: ['pipe', 'pipe', 'pipe'] }).toString().trim();
+ const parsed = JSON.parse(raw);
+ versions = Array.isArray(parsed) ? parsed : [parsed];
+ } catch {}
+ const matching = versions.filter(v => v.startsWith(base + '.'));
+ const lastPatch = matching.length > 0
+ ? Math.max(...matching.map(v => parseInt(v.split('.')[2])))
+ : -1;
+ console.log(base + '.' + (lastPatch + 1));
+ ")
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
+ node -e "const fs=require('fs'),p=JSON.parse(fs.readFileSync('package.json'));p.version='$VERSION';fs.writeFileSync('package.json',JSON.stringify(p,null,2)+'\n')"
+ echo "Publishing @pachca/generator@$VERSION"
+
- name: Publish to npm
working-directory: packages/generator
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
- VERSION=$(node -e "console.log(require('./package.json').version)")
- if npm view "@pachca/generator@${VERSION}" version 2>/dev/null; then
- echo "Version ${VERSION} already published, skipping"
- else
- npm publish --access public --provenance
- fi
+ OUTPUT=$(npm publish --access public --provenance 2>&1) || {
+ CODE=$?
+ if echo "$OUTPUT" | grep -q "E409\|already exists"; then
+ echo "Version ${{ steps.version.outputs.version }} already published, skipping"
+ else
+ echo "$OUTPUT"
+ exit $CODE
+ fi
+ }
diff --git a/.github/workflows/gitlab.yml b/.github/workflows/gitlab.yml
index e289dc45..ee9de7f9 100644
--- a/.github/workflows/gitlab.yml
+++ b/.github/workflows/gitlab.yml
@@ -4,6 +4,10 @@ on:
branches:
- main
+concurrency:
+ group: gitlab-sync
+ cancel-in-progress: false
+
jobs:
sync_gitlab:
name: push github/main to gitlab/main
diff --git a/.github/workflows/n8n.yml b/.github/workflows/n8n.yml
new file mode 100644
index 00000000..e498e519
--- /dev/null
+++ b/.github/workflows/n8n.yml
@@ -0,0 +1,204 @@
+name: n8n Node
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - 'integrations/n8n/**'
+ - 'packages/spec/openapi.yaml'
+ - 'packages/spec/workflows.ts'
+ - 'packages/spec/examples.ts'
+ - 'packages/openapi-parser/src/**'
+ - 'packages/generator/src/naming.ts'
+ - 'apps/docs/lib/openapi/mapper.ts'
+ - 'apps/docs/lib/openapi/example-generator.ts'
+ - 'packages/spec/overlay.en.yaml'
+ - 'packages/spec/scripts/**'
+ pull_request:
+ branches: [main]
+ paths:
+ - 'integrations/n8n/**'
+ - 'packages/spec/openapi.yaml'
+ - 'packages/spec/workflows.ts'
+ - 'packages/spec/examples.ts'
+ - 'packages/openapi-parser/src/**'
+ - 'packages/generator/src/naming.ts'
+ - 'apps/docs/lib/openapi/mapper.ts'
+ - 'apps/docs/lib/openapi/example-generator.ts'
+ - 'packages/spec/overlay.en.yaml'
+ - 'packages/spec/scripts/**'
+ workflow_dispatch:
+
+concurrency:
+ group: n8n-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
+jobs:
+ generate-and-build:
+ name: Generate & Build
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ steps:
+ - uses: actions/checkout@v4
+ - uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: 1.3.4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 22
+
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+
+ - name: Build parser
+ working-directory: packages/openapi-parser
+ run: bun run build
+
+ - name: Apply English overlay
+ working-directory: packages/spec
+ run: bun run scripts/apply-overlay.ts
+
+ - name: Generate n8n node
+ run: bun run integrations/n8n/scripts/generate-n8n.ts
+
+ - name: Type check
+ working-directory: integrations/n8n
+ run: bun run tsc --noEmit
+
+ - name: Lint
+ working-directory: integrations/n8n
+ run: bun run lint
+
+ - name: Test
+ working-directory: integrations/n8n
+ run: bun run test
+
+ - name: Build n8n node
+ working-directory: integrations/n8n
+ run: |
+ bun run tsc
+ find nodes icons credentials \( -name '*.png' -o -name '*.svg' \) | while read f; do mkdir -p "dist/$(dirname "$f")" && cp "$f" "dist/$f"; done
+
+ publish:
+ if: github.event_name == 'push'
+ needs: generate-and-build
+ runs-on: ubuntu-latest
+ concurrency:
+ group: publish-n8n
+ cancel-in-progress: false
+ permissions:
+ contents: write
+ id-token: write
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: 1.3.4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 22
+ registry-url: "https://registry.npmjs.org"
+
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+
+ - name: Build parser
+ working-directory: packages/openapi-parser
+ run: bun run build
+
+ - name: Apply English overlay
+ working-directory: packages/spec
+ run: bun run scripts/apply-overlay.ts
+
+ - name: Generate n8n node
+ run: bun run integrations/n8n/scripts/generate-n8n.ts
+
+ - name: Check for publishable changes
+ id: changes
+ run: |
+ # Check committed source changes (exclude tests, scripts, docs, config)
+ COMMITTED=$(git diff --name-only HEAD~1 HEAD -- integrations/n8n/ \
+ | grep -v -E '^integrations/n8n/(tests/|scripts/|docs/|e2e/|eslint|tsconfig|\.gitignore|\.npmrc|vitest)' \
+ | grep -q . && echo true || echo false)
+ # Check if generation produced different files (spec/generator changed)
+ GENERATED=$(git diff --name-only -- integrations/n8n/nodes/ integrations/n8n/credentials/ \
+ | grep -q . && echo true || echo false)
+ if [ "$COMMITTED" = "true" ] || [ "$GENERATED" = "true" ]; then
+ echo "changed=true" >> $GITHUB_OUTPUT
+ echo "Publishable changes detected (committed=$COMMITTED, generated=$GENERATED)"
+ else
+ echo "changed=false" >> $GITHUB_OUTPUT
+ echo "No publishable changes, skipping publish"
+ fi
+
+ - name: Set version (auto-increment patch)
+ if: steps.changes.outputs.changed == 'true'
+ id: version
+ working-directory: integrations/n8n
+ run: |
+ VERSION=$(node -e "
+ const { execSync } = require('child_process');
+ const base = require('./package.json').version.split('.').slice(0, 2).join('.');
+ let versions = [];
+ try {
+ const raw = execSync('npm view n8n-nodes-pachca versions --json', { stdio: ['pipe', 'pipe', 'pipe'] }).toString().trim();
+ const parsed = JSON.parse(raw);
+ versions = Array.isArray(parsed) ? parsed : [parsed];
+ } catch {}
+ const matching = versions.filter(v => v.startsWith(base + '.'));
+ const lastPatch = matching.length > 0
+ ? Math.max(...matching.map(v => parseInt(v.split('.')[2])))
+ : -1;
+ console.log(base + '.' + (lastPatch + 1));
+ ")
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
+ node -e "const fs=require('fs'),p=JSON.parse(fs.readFileSync('package.json'));p.version='$VERSION';fs.writeFileSync('package.json',JSON.stringify(p,null,2)+'\n')"
+ echo "Publishing n8n-nodes-pachca@$VERSION"
+
+ - name: Build n8n node
+ if: steps.changes.outputs.changed == 'true'
+ working-directory: integrations/n8n
+ run: |
+ bun run tsc
+ find nodes icons credentials \( -name '*.png' -o -name '*.svg' \) | while read f; do mkdir -p "dist/$(dirname "$f")" && cp "$f" "dist/$f"; done
+
+ - name: Scan community package
+ if: steps.changes.outputs.changed == 'true'
+ working-directory: integrations/n8n
+ run: bun x @n8n/scan-community-package .
+
+ - name: Publish to npm
+ if: steps.changes.outputs.changed == 'true'
+ working-directory: integrations/n8n
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+ run: |
+ OUTPUT=$(npm publish --access public --provenance 2>&1) || {
+ CODE=$?
+ if echo "$OUTPUT" | grep -q "E409\|already exists"; then
+ echo "Version ${{ steps.version.outputs.version }} already published, skipping"
+ else
+ echo "$OUTPUT"
+ exit $CODE
+ fi
+ }
+
+ - name: Create GitHub Release
+ if: steps.changes.outputs.changed == 'true'
+ working-directory: integrations/n8n
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ npm pack
+ mkdir n8n-nodes-pachca
+ tar -xzf n8n-nodes-pachca-*.tgz --strip-components=1 -C n8n-nodes-pachca
+ rm n8n-nodes-pachca-*.tgz
+ tar -czf n8n-nodes-pachca.tgz n8n-nodes-pachca
+ gh release create "n8n-v${{ steps.version.outputs.version }}" \
+ n8n-nodes-pachca.tgz \
+ --title "n8n-nodes-pachca v${{ steps.version.outputs.version }}" \
+ --notes "See [CHANGELOG](integrations/n8n/CHANGELOG.md) for details." \
+ --latest
diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml
index 3be08aa7..67a64fcd 100644
--- a/.github/workflows/sdk.yml
+++ b/.github/workflows/sdk.yml
@@ -7,6 +7,10 @@ on:
branches: [main]
workflow_dispatch:
+concurrency:
+ group: sdk-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
jobs:
generate-and-build:
name: Generate and build
@@ -15,12 +19,21 @@ jobs:
contents: write
outputs:
version: ${{ steps.version.outputs.version }}
+ any_sdk_changed: ${{ steps.sdk_changes.outputs.any_changed }}
+ ts_changed: ${{ steps.sdk_changes.outputs.ts_changed }}
+ go_changed: ${{ steps.sdk_changes.outputs.go_changed }}
+ kotlin_changed: ${{ steps.sdk_changes.outputs.kotlin_changed }}
+ python_changed: ${{ steps.sdk_changes.outputs.python_changed }}
+ csharp_changed: ${{ steps.sdk_changes.outputs.csharp_changed }}
+ swift_changed: ${{ steps.sdk_changes.outputs.swift_changed }}
cli_changed: ${{ steps.cli_check.outputs.changed }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: 1.3.4
- uses: actions/setup-node@v4
with:
node-version: 20
@@ -36,12 +49,32 @@ jobs:
- name: Check for SDK changes
id: sdk_changes
run: |
- echo "ts_changed=$(git diff --name-only -- sdk/typescript/ | grep -q . && echo true || echo false)" >> $GITHUB_OUTPUT
- echo "go_changed=$(git diff --name-only -- sdk/go/ | grep -q . && echo true || echo false)" >> $GITHUB_OUTPUT
- echo "kotlin_changed=$(git diff --name-only -- sdk/kotlin/ | grep -q . && echo true || echo false)" >> $GITHUB_OUTPUT
- echo "python_changed=$(git diff --name-only -- sdk/python/ | grep -q . && echo true || echo false)" >> $GITHUB_OUTPUT
- echo "csharp_changed=$(git diff --name-only -- sdk/csharp/ | grep -q . && echo true || echo false)" >> $GITHUB_OUTPUT
- echo "swift_changed=$(git diff --name-only -- sdk/swift/ | grep -q . && echo true || echo false)" >> $GITHUB_OUTPUT
+ PREV_TAG=$(git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo "")
+ check_sdk() {
+ local dir=$1
+ if [ -n "$PREV_TAG" ]; then
+ git diff --name-only "$PREV_TAG" HEAD -- "$dir" | grep -q . && echo true || echo false
+ else
+ git diff --name-only HEAD~1 HEAD -- "$dir" | grep -q . && echo true || echo false
+ fi
+ }
+ TS=$(check_sdk sdk/typescript/)
+ GO=$(check_sdk sdk/go/)
+ KT=$(check_sdk sdk/kotlin/)
+ PY=$(check_sdk sdk/python/)
+ CS=$(check_sdk sdk/csharp/)
+ SW=$(check_sdk sdk/swift/)
+ echo "ts_changed=$TS" >> $GITHUB_OUTPUT
+ echo "go_changed=$GO" >> $GITHUB_OUTPUT
+ echo "kotlin_changed=$KT" >> $GITHUB_OUTPUT
+ echo "python_changed=$PY" >> $GITHUB_OUTPUT
+ echo "csharp_changed=$CS" >> $GITHUB_OUTPUT
+ echo "swift_changed=$SW" >> $GITHUB_OUTPUT
+ if [ "$TS" = "true" ] || [ "$GO" = "true" ] || [ "$KT" = "true" ] || [ "$PY" = "true" ] || [ "$CS" = "true" ] || [ "$SW" = "true" ]; then
+ echo "any_changed=true" >> $GITHUB_OUTPUT
+ else
+ echo "any_changed=false" >> $GITHUB_OUTPUT
+ fi
- name: Check for CLI changes
if: github.event_name == 'push'
@@ -98,11 +131,11 @@ jobs:
run: swift build
- name: Copy Swift Package.swift to root
- if: github.event_name == 'push'
+ if: github.event_name == 'push' && steps.sdk_changes.outputs.swift_changed == 'true'
run: cp sdk/swift/Package.swift Package.swift
- name: Extract version
- if: github.event_name == 'push'
+ if: github.event_name == 'push' && steps.sdk_changes.outputs.any_changed == 'true'
id: version
run: |
SPEC_VERSION=$(grep -oP 'version:\s*"\K[^"]+' packages/spec/typespec.tsp)
@@ -121,7 +154,7 @@ jobs:
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Commit
- if: github.event_name == 'push'
+ if: github.event_name == 'push' && steps.sdk_changes.outputs.any_changed == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
@@ -129,17 +162,18 @@ jobs:
git diff --staged --quiet || git commit -m "chore: regenerate SDK v${{ steps.version.outputs.version }}"
- name: Tag
- if: github.event_name == 'push'
- run: |
- git tag -f "v${{ steps.version.outputs.version }}"
- git tag -f "sdk/go/generated/v${{ steps.version.outputs.version }}"
+ if: github.event_name == 'push' && steps.sdk_changes.outputs.any_changed == 'true'
+ run: git tag -f "v${{ steps.version.outputs.version }}"
+ - name: Tag Go SDK
+ if: github.event_name == 'push' && steps.sdk_changes.outputs.go_changed == 'true'
+ run: git tag -f "sdk/go/generated/v${{ steps.version.outputs.version }}"
- name: Push
- if: github.event_name == 'push'
+ if: github.event_name == 'push' && steps.sdk_changes.outputs.any_changed == 'true'
run: git push && git push --tags --force
publish-ts:
- if: github.event_name == 'push'
+ if: github.event_name == 'push' && needs.generate-and-build.outputs.ts_changed == 'true'
needs: generate-and-build
runs-on: ubuntu-latest
permissions:
@@ -150,6 +184,8 @@ jobs:
with:
ref: v${{ needs.generate-and-build.outputs.version }}
- uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: 1.3.4
- uses: actions/setup-node@v4
with:
node-version: 20
@@ -175,7 +211,7 @@ jobs:
if npm view "@pachca/sdk@${VERSION}" version 2>/dev/null; then
echo "Version ${VERSION} already published, skipping"
else
- npm publish --access public
+ npm publish --access public --provenance
fi
publish-cli:
@@ -193,6 +229,8 @@ jobs:
with:
fetch-depth: 0
- uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: 1.3.4
- uses: actions/setup-node@v4
with:
node-version: 20
@@ -240,7 +278,7 @@ jobs:
}
publish-py:
- if: github.event_name == 'push'
+ if: github.event_name == 'push' && needs.generate-and-build.outputs.python_changed == 'true'
needs: generate-and-build
runs-on: ubuntu-latest
steps:
@@ -265,7 +303,7 @@ jobs:
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
publish-kt:
- if: github.event_name == 'push'
+ if: github.event_name == 'push' && needs.generate-and-build.outputs.kotlin_changed == 'true'
needs: generate-and-build
runs-on: ubuntu-latest
steps:
@@ -274,7 +312,7 @@ jobs:
version: v${{ needs.generate-and-build.outputs.version }}
publish-cs:
- if: github.event_name == 'push'
+ if: github.event_name == 'push' && needs.generate-and-build.outputs.csharp_changed == 'true'
needs: generate-and-build
runs-on: ubuntu-latest
steps:
diff --git a/.gitignore b/.gitignore
index b75ada9c..1baae5e7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -64,9 +64,11 @@ Thumbs.db
# Evals
evals/results/
-# Agent-specific local skills (installed via `npx skills add`)
-.claude/skills/
+# Agent / AI tooling
+.claude/
.cursor/
+.mcp.json
+.playwright-mcp/
# C# / .NET
bin/
diff --git a/AGENTS.md b/AGENTS.md
index cac8a157..106f9881 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -80,17 +80,3 @@ npx skills add pachca/openapi
```
More info: [API Docs](https://dev.pachca.com) · [Full reference](https://dev.pachca.com/llms-full.txt) · [OpenAPI spec](https://dev.pachca.com/openapi.yaml) · CLI help: `pachca --help`
-
-## Generator Development
-
-When modifying the SDK generator (`packages/generator/src/lang/*.ts`):
-
-1. Run tests: `cd packages/generator && npm test`
-2. **Regenerate test snapshots**: Ask the user to run `bun bin/regen-snapshots.ts` in `packages/generator`
-3. Regenerate SDKs: Run `npm run generate` in each `sdk/*` directory
-
-## Agent Restrictions
-
-**NEVER run these commands directly** — ask the user to run them:
-- `bun bin/regen-snapshots.ts` — regenerates test snapshots
-- Any command that bulk-modifies test fixtures or snapshots
diff --git a/README.md b/README.md
index e0fc08b5..8b64534b 100644
--- a/README.md
+++ b/README.md
@@ -4,10 +4,11 @@
[](https://www.npmjs.com/package/@pachca/sdk)
[](https://www.npmjs.com/package/@pachca/cli)
[](https://www.npmjs.com/package/@pachca/generator)
+[](https://www.npmjs.com/package/n8n-nodes-pachca)
[](https://pypi.org/project/pachca-sdk/)
[](LICENSE)
-Unified Developer Experience Platform для [Pachca API](https://dev.pachca.com) — API корпоративного мессенджера Пачка. Один источник (TypeSpec + workflows.ts) генерирует артефакты для всех каналов: web docs, CLI, SDK, agent skills, LLM context.
+Unified Developer Experience Platform для [Pachca API](https://dev.pachca.com) — API корпоративного мессенджера Пачка. Один источник (TypeSpec + workflows.ts) генерирует артефакты для всех каналов: web docs, CLI, SDK, n8n node, agent skills, LLM context.
**Документация**: https://dev.pachca.com · **OpenAPI**: https://dev.pachca.com/openapi.yaml · **Авторизация**: https://dev.pachca.com/guides/authorization · **Changelog**: https://dev.pachca.com/guides/updates · **Postman/Bruno**: https://dev.pachca.com/pachca.postman_collection.json
@@ -86,6 +87,19 @@ npx skills add pachca/openapi
Скиллы генерируются автоматически из OpenAPI-спеки при `bun turbo build`. Устанавливайте только из официального репозитория — скиллы содержат исключительно инструкции (нет исполняемого кода).
+## n8n
+
+Community node для [n8n](https://n8n.io/) — 18 ресурсов, 60+ операций, Pachca Trigger с авторегистрацией вебхука.
+
+```bash
+# В n8n: Settings > Community Nodes > n8n-nodes-pachca
+npm install n8n-nodes-pachca
+```
+
+Автоматически генерируется из OpenAPI-спецификации, полная обратная совместимость с v1.
+
+**Документация**: [dev.pachca.com/guides/n8n](https://dev.pachca.com/guides/n8n/overview) · **[README](integrations/n8n/README.md)**
+
## SDK
| Язык | Пакет | Реестр |
@@ -149,7 +163,6 @@ npx @pachca/generator --output ./generated --lang typescript,python,go,kotlin,sw
| [`/llms.txt`](https://dev.pachca.com/llms.txt) | Краткий индекс: все endpoint'ы со ссылками |
| [`/llms-full.txt`](https://dev.pachca.com/llms-full.txt) | Полная документация: гайды + endpoint'ы с параметрами |
| [`/skill.md`](https://dev.pachca.com/skill.md) | AI-agent skill: workflows, capabilities, ссылки |
-| [`/scenarios.json`](https://dev.pachca.com/scenarios.json) | Машиночитаемые сценарии для no-code платформ (n8n, Albato) |
| `/{section}/{action}.md` | Отдельный .md для каждого endpoint'а и гайда |
[Context7](https://context7.com/pachca/openapi) — AI-native document discovery.
@@ -173,6 +186,7 @@ bun turbo generate # TypeSpec → openapi.yaml + SDK
|----------|---------|------------|
| `check.yml` | PR в `main` | `bun turbo check` |
| `sdk.yml` | Push в `main` | Генерация SDK → коммит → теги → публикация |
+| `n8n.yml` | Push/PR в `main` | Генерация n8n node → тест → npm publish → GitHub Release |
| `deploy.yml` | Push в `main` | Docker build → GitLab registry → SSH deploy |
| `gitlab.yml` | Push в `main` | Зеркало в GitLab |
@@ -203,8 +217,10 @@ bun turbo generate # TypeSpec → openapi.yaml + SDK
│ ├── kotlin/ # JitPack
│ ├── swift/ # SPM
│ └── csharp/ # NuGet
+├── integrations/
+│ └── n8n/ # n8n community node (генерируется из OpenAPI)
├── skills/ # Agent Skills (генерируются → apps/docs/public/.well-known/skills/)
-├── .github/workflows/ # CI/CD (check, sdk, deploy, gitlab)
+├── .github/workflows/ # CI/CD (check, sdk, n8n, deploy, gitlab)
├── Package.swift # Корневой Swift Package (копируется из sdk/swift при CI)
├── jitpack.yml # JitPack конфиг для Kotlin (JDK 17)
├── Dockerfile # Multi-stage Docker-сборка docs
@@ -231,7 +247,6 @@ apps/docs sdk/* (6 языков)
Сайт + llms.txt + llms-full.txt
+ skill.md + per-endpoint .md
+ Agent Skills (skills/, AGENTS.md, .well-known/)
- + scenarios.json (n8n, no-code)
+ CLI examples (10-й код-генератор)
+ pachca.postman_collection.json
+ OG-изображения + sitemap + RSS
@@ -240,8 +255,7 @@ workflows.ts (packages/spec — единый источник сценариев
│
├──→ Web (страница сценариев с поиском)
├──→ CLI (pachca guide)
- ├──→ Skills (CLI-сценарии в SKILL.md)
- └──→ scenarios.json (no-code: n8n, Albato)
+ └──→ Skills (CLI-сценарии в SKILL.md)
```
## Turborepo пайплайн
@@ -381,6 +395,6 @@ Badge «Новое» показывается < 7 дней. Попадает в
### Безопасность (next.config.ts)
-HSTS (2 года, preload), X-Frame-Options: DENY, nosniff, Permissions-Policy. CORS разрешён для `llms.txt`, `llms-full.txt`, `skill.md`, `*.md`, `/.well-known/skills/*`, `openapi.yaml`, `pachca.postman_collection.json`, `scenarios.json`.
+HSTS (2 года, preload), X-Frame-Options: DENY, nosniff, Permissions-Policy. CORS разрешён для `llms.txt`, `llms-full.txt`, `skill.md`, `*.md`, `/.well-known/skills/*`, `openapi.yaml`, `pachca.postman_collection.json`.
diff --git a/apps/docs/app/globals.css b/apps/docs/app/globals.css
index 66a519b1..c68fd74f 100644
--- a/apps/docs/app/globals.css
+++ b/apps/docs/app/globals.css
@@ -5,6 +5,7 @@
/* Primary — warm orange (Pachca brand), AA-compliant white text (4.82:1) */
--color-primary: oklch(48% 0.17 50);
--color-primary-dark: oklch(42% 0.17 50);
+ --color-primary-inverted: oklch(72% 0.15 50);
/* Text — 4-level hierarchy, warm stone neutrals */
--color-text-primary: oklch(20% 0.01 70);
@@ -38,6 +39,7 @@
--color-callout-tip-text: var(--color-accent-green);
--color-method-get: oklch(60.2% 0.193 263);
+ --color-method-get-inverted: oklch(66% 0.193 263);
--color-method-post: oklch(63.8% 0.142 143.5);
--color-method-put: oklch(55.4% 0.116 77);
--color-method-delete: oklch(63.9% 0.179 28);
@@ -85,6 +87,7 @@ html.dark {
/* Primary — lighter warm orange for dark mode, desaturated */
--color-primary: oklch(72% 0.15 50);
--color-primary-dark: oklch(78% 0.13 50);
+ --color-primary-inverted: oklch(48% 0.17 50);
/* Text — warm off-white, clear 3-level separation */
--color-text-primary: oklch(93% 0.007 70);
@@ -128,6 +131,7 @@ html.dark {
/* HTTP methods — lighter for dark mode readability */
--color-method-get: oklch(66% 0.193 263);
+ --color-method-get-inverted: oklch(60.2% 0.193 263);
--color-method-post: oklch(68% 0.142 143.5);
--color-method-put: oklch(68% 0.116 77);
--color-method-delete: oklch(67.1% 0.145 17);
@@ -149,6 +153,7 @@ html.dark {
html:not(.light):not(.dark) {
--color-primary: oklch(72% 0.15 50);
--color-primary-dark: oklch(78% 0.13 50);
+ --color-primary-inverted: oklch(48% 0.17 50);
--color-text-primary: oklch(93% 0.007 70);
--color-text-secondary: oklch(70% 0.012 70);
@@ -187,6 +192,7 @@ html.dark {
--color-glass-active: oklch(100% 0 0 / 12%);
--color-method-get: oklch(66% 0.193 263);
+ --color-method-get-inverted: oklch(60.2% 0.193 263);
--color-method-post: oklch(68% 0.142 143.5);
--color-method-put: oklch(68% 0.116 77);
--color-method-delete: oklch(67.1% 0.145 17);
diff --git a/apps/docs/components/api/copied-tooltip.tsx b/apps/docs/components/api/copied-tooltip.tsx
index 18721a81..8f2d2347 100644
--- a/apps/docs/components/api/copied-tooltip.tsx
+++ b/apps/docs/components/api/copied-tooltip.tsx
@@ -18,10 +18,9 @@ export function CopiedTooltip({ children, open }: CopiedTooltipProps) {
align="center"
sideOffset={2}
collisionPadding={8}
- className="z-50 pointer-events-none animate-tooltip bg-text-primary text-background text-[12px] font-semibold rounded-md px-2.5 py-1.5 whitespace-nowrap shadow-xl"
+ className="z-50 pointer-events-none animate-tooltip rounded-lg px-2.5 py-1.5 shadow-xl border border-glass-heavy-border bg-glass-heavy backdrop-blur-md text-text-primary text-[12px] font-semibold whitespace-nowrap"
>
Скопировано
-
diff --git a/apps/docs/components/api/endpoint-header.tsx b/apps/docs/components/api/endpoint-header.tsx
index 071008fe..16013596 100644
--- a/apps/docs/components/api/endpoint-header.tsx
+++ b/apps/docs/components/api/endpoint-header.tsx
@@ -98,7 +98,7 @@ export function EndpointHeader({
align="center"
sideOffset={4}
collisionPadding={8}
- className="z-50 animate-tooltip bg-text-primary text-[12px] font-semibold rounded-md px-2.5 py-1.5 shadow-xl whitespace-nowrap flex items-center gap-1.5"
+ className="z-50 animate-tooltip rounded-lg px-2.5 py-1.5 shadow-xl border border-glass-heavy-border bg-glass-heavy backdrop-blur-md text-[12px] font-semibold whitespace-nowrap flex items-center gap-1.5"
>
{requirements.scopeRoles ? (
ROLES.map((r, i) => (
@@ -106,18 +106,17 @@ export function EndpointHeader({
key={r}
className={
requirements.scopeRoles!.includes(r)
- ? 'text-background'
- : 'text-background/40'
+ ? 'text-text-primary'
+ : 'text-text-tertiary'
}
>
- {i > 0 && ·}
+ {i > 0 && ·}
{ROLE_LABELS[r]}
))
) : (
- Все роли
+ Все роли
)}
-
diff --git a/apps/docs/components/api/endpoint-link.tsx b/apps/docs/components/api/endpoint-link.tsx
index e2541618..7cbf9e68 100644
--- a/apps/docs/components/api/endpoint-link.tsx
+++ b/apps/docs/components/api/endpoint-link.tsx
@@ -1,4 +1,7 @@
+'use client';
+
import Link from 'next/link';
+import * as Tooltip from '@radix-ui/react-tooltip';
const ENDPOINT_METHOD_COLORS: Record = {
GET: 'bg-method-get/10 text-method-get',
@@ -8,13 +11,40 @@ const ENDPOINT_METHOD_COLORS: Record = {
PATCH: 'bg-method-patch/10 text-method-patch',
};
+const ROLES = ['owner', 'admin', 'user', 'bot'] as const;
+const ROLE_LABELS: Record = {
+ owner: 'Владелец',
+ admin: 'Администратор',
+ user: 'Сотрудник',
+ bot: 'Бот',
+};
+
+const PLAN_NAMES: Record = {
+ corporation: 'Корпорация',
+};
+
interface EndpointLinkProps {
method?: string;
href?: string;
+ scope?: string;
+ scopeRoles?: string;
+ plan?: string;
+ noAuth?: boolean;
children: React.ReactNode;
}
-export function EndpointLink({ method, href, children }: EndpointLinkProps) {
+export function EndpointLink({
+ method,
+ href,
+ scope,
+ scopeRoles: scopeRolesStr,
+ plan,
+ noAuth,
+ children,
+}: EndpointLinkProps) {
+ const scopeRoles = scopeRolesStr ? scopeRolesStr.split(',') : undefined;
+ const hasBadges = scope || plan || noAuth;
+
const badge = method ? (
) : null;
- if (href) {
+ const linkContent = (
+ <>
+ {badge}
+
+ {children}
+
+ >
+ );
+
+ if (!href) {
return (
-
+
{badge}
-
- {children}
-
-
+ {children}
+
);
}
+ const link = (
+
+ {linkContent}
+
+ );
+
+ if (!hasBadges) {
+ return link;
+ }
+
return (
-
- {badge}
- {children}
-
+
+ {link}
+
+
+ {noAuth && (
+
+ Без авторизации
+
+ )}
+ {scope && (
+
+ {scope}
+ {scopeRoles && (
+
+ {ROLES.map((r) => (
+
+ ))}
+
+ )}
+
+ )}
+ {plan && (
+
+ {PLAN_NAMES[plan] ?? plan}
+
+ )}
+
+
+
);
}
diff --git a/apps/docs/components/api/scope-roles-table.tsx b/apps/docs/components/api/scope-roles-table.tsx
index fdad2b80..aea6908f 100644
--- a/apps/docs/components/api/scope-roles-table.tsx
+++ b/apps/docs/components/api/scope-roles-table.tsx
@@ -129,22 +129,21 @@ export function ScopeRolesTable({ schema }: ScopeRolesTableProps) {
align="end"
sideOffset={4}
collisionPadding={8}
- className="z-50 animate-tooltip bg-text-primary text-[12px] font-semibold rounded-md px-2.5 py-1.5 shadow-xl whitespace-nowrap flex items-center gap-1.5"
+ className="z-50 animate-tooltip rounded-lg px-2.5 py-1.5 shadow-xl border border-glass-heavy-border bg-glass-heavy backdrop-blur-md text-[12px] font-semibold whitespace-nowrap flex items-center gap-1.5"
>
{ROLES.map((r, i) => (
- {i > 0 && ·}
+ {i > 0 && ·}
{ROLE_LABELS[r]}
))}
-
diff --git a/apps/docs/components/layout/footer.tsx b/apps/docs/components/layout/footer.tsx
index adb7fd8e..3c1d3f99 100644
--- a/apps/docs/components/layout/footer.tsx
+++ b/apps/docs/components/layout/footer.tsx
@@ -92,7 +92,7 @@ function PrevButton({ item }: { item: NavigationItem }) {
Назад
- {item.title}
+ {item.sectionTitle ? `${item.sectionTitle}: ${item.title}` : item.title}
);
@@ -112,7 +112,7 @@ function NextButton({ item }: { item: NavigationItem }) {
{isLoading ? : }
- {item.title}
+ {item.sectionTitle ? `${item.sectionTitle}: ${item.title}` : item.title}
);
diff --git a/apps/docs/components/mdx/cli-commands.tsx b/apps/docs/components/mdx/cli-commands.tsx
index b1769cd8..55874954 100644
--- a/apps/docs/components/mdx/cli-commands.tsx
+++ b/apps/docs/components/mdx/cli-commands.tsx
@@ -1,14 +1,25 @@
import { generateNavigation } from '@/lib/navigation';
+import { parseOpenAPI } from '@/lib/openapi/parser';
+import { generateUrlFromOperation } from '@/lib/openapi/mapper';
import { CopyableInlineCode } from '@/components/api/copyable-inline-code';
import { EndpointLink } from '@/components/api/endpoint-link';
+import type { EndpointRequirements } from '@/lib/openapi/types';
export async function CliCommands() {
- const sections = await generateNavigation();
+ const [sections, api] = await Promise.all([generateNavigation(), parseOpenAPI()]);
const methodsSection = sections.find((s) => s.title === 'Методы API');
const allCommands = methodsSection
? methodsSection.items.flatMap((group) => group.children ?? [])
: [];
+ // Build a map from URL to requirements for tooltip data
+ const requirementsMap = new Map();
+ for (const ep of api.endpoints) {
+ if (ep.requirements) {
+ requirementsMap.set(generateUrlFromOperation(ep), ep.requirements);
+ }
+ }
+
return (
@@ -25,13 +36,21 @@ export async function CliCommands() {
{allCommands.map((item) => {
const command = `pachca ${item.href.replace(/^\/api\//, '').replace(/\//g, ' ')}`;
+ const req = requirementsMap.get(item.href);
return (
|
{command}
|
-
+
{item.title}
|
diff --git a/apps/docs/components/mdx/sdk-commands.tsx b/apps/docs/components/mdx/sdk-commands.tsx
index 59516142..55a819cb 100644
--- a/apps/docs/components/mdx/sdk-commands.tsx
+++ b/apps/docs/components/mdx/sdk-commands.tsx
@@ -3,6 +3,7 @@ import { groupEndpointsByTag, generateUrlFromOperation, generateTitle } from '@/
import { sortTagsByOrder } from '@/lib/guides-config';
import { CopyableInlineCode } from '@/components/api/copyable-inline-code';
import { EndpointLink } from '@/components/api/endpoint-link';
+import type { EndpointRequirements } from '@/lib/openapi/types';
const METHOD_ORDER: Record = { POST: 0, GET: 1, PUT: 2, PATCH: 3, DELETE: 4 };
@@ -62,7 +63,13 @@ export async function SdkCommands({ lang }: SdkCommandsProps) {
const grouped = groupEndpointsByTag(api.endpoints);
const sortedTags = sortTagsByOrder(Array.from(grouped.keys()));
- const rows: Array<{ sdkCall: string; method: string; href: string; title: string }> = [];
+ const rows: Array<{
+ sdkCall: string;
+ method: string;
+ href: string;
+ title: string;
+ requirements?: EndpointRequirements;
+ }> = [];
for (const tag of sortedTags) {
const endpoints = grouped.get(tag)!;
@@ -74,6 +81,7 @@ export async function SdkCommands({ lang }: SdkCommandsProps) {
method: endpoint.method,
href: generateUrlFromOperation(endpoint),
title: generateTitle(endpoint),
+ requirements: endpoint.requirements,
});
}
}
@@ -98,7 +106,14 @@ export async function SdkCommands({ lang }: SdkCommandsProps) {
{row.sdkCall}
-
+
{row.title}
|
diff --git a/apps/docs/content/api/models.mdx b/apps/docs/content/api/models.mdx
index 223164b2..b90064f1 100644
--- a/apps/docs/content/api/models.mdx
+++ b/apps/docs/content/api/models.mdx
@@ -8,7 +8,7 @@ hideTableOfContents: true
Все модели данных, возвращаемые в ответах API. Каждая модель содержит связанные методы и таблицу свойств.
-Методы [Получение подписи](POST /uploads) и [Загрузка файла](POST /direct_url) не возвращают модели данных.
+Методы [Получение подписи](POST /uploads), [Загрузка файла](POST /direct_url), [Загрузка аватара](PUT /profile/avatar), [Удаление аватара](DELETE /profile/avatar), [Загрузка аватара сотрудника](PUT /users/{user_id}/avatar) и [Удаление аватара сотрудника](DELETE /users/{user_id}/avatar) не возвращают модели данных.
diff --git a/apps/docs/content/api/pagination.mdx b/apps/docs/content/api/pagination.mdx
index edbeb627..5034827d 100644
--- a/apps/docs/content/api/pagination.mdx
+++ b/apps/docs/content/api/pagination.mdx
@@ -33,7 +33,7 @@ API Пачки использует **cursor-based** пагинацию для
Для перехода на следующую страницу передайте значение `next_page` в параметр `cursor` следующего запроса. Последняя страница достигнута, когда массив `data` вернулся пустым.
-Не определяйте последнюю страницу по количеству записей в ответе — оно может быть меньше `limit` и на промежуточных страницах. Проверяйте пустой массив `data`. Курсор — непрозрачный токен: не парсите и не сохраняйте его между сессиями. Всегда явно указывайте `limit` — не полагайтесь на значение по умолчанию.
+Поле `next_page` **всегда присутствует** в ответе и никогда не бывает `null` — даже на последней странице. Не используйте `next_page == null` как признак конца данных. Единственный надёжный способ — проверять, что массив `data` пуст. Количество записей в ответе может быть меньше `limit` и на промежуточных страницах — не полагайтесь на него. Курсор — непрозрачный токен: не парсите и не сохраняйте его между сессиями. Всегда явно указывайте `limit` — не полагайтесь на значение по умолчанию.
### Методы поиска
diff --git a/apps/docs/content/guides/cli.mdx b/apps/docs/content/guides/cli.mdx
index 95350fe9..4b5631d4 100644
--- a/apps/docs/content/guides/cli.mdx
+++ b/apps/docs/content/guides/cli.mdx
@@ -6,7 +6,7 @@ description: Управление Пачкой из терминала — вс
# CLI
- 2026.3.10 · 21 марта 2026
+ 2026.4.0 · 7 апреля 2026
Официальный CLI для работы с Pachca API из терминала. Каждый API-метод доступен как команда с типизированными флагами, валидацией и интерактивными подсказками. Требуется Node.js 20 или новее.
diff --git a/apps/docs/content/guides/forms/handling.mdx b/apps/docs/content/guides/forms/handling.mdx
index 63c182e4..0034ddc2 100644
--- a/apps/docs/content/guides/forms/handling.mdx
+++ b/apps/docs/content/guides/forms/handling.mdx
@@ -28,10 +28,7 @@ description: Открытие представлений, получение р
Каждый исходящий вебхук защищён с помощью подписи, основанной на хешировании содержимого. Подробнее об этом — в разделе [Безопасность](/guides/webhook#bezopasnost).
-
+
```json title="Пример вебхука о заполнении формы"
{
diff --git a/apps/docs/content/guides/n8n.mdx b/apps/docs/content/guides/n8n.mdx
deleted file mode 100644
index 3966ed78..00000000
--- a/apps/docs/content/guides/n8n.mdx
+++ /dev/null
@@ -1,181 +0,0 @@
----
-title: n8n
-description: Автоматизации в Пачке через платформу n8n без программирования
----
-
-# n8n
-
-## Что такое n8n
-
-n8n — платформа для автоматизации рабочих процессов с открытым исходным кодом. Можно развернуть на собственном сервере или использовать веб-версию. Платформа позволяет создавать интеграции с сервисами без написания кода, используя визуальный редактор с узлами (nodes).
-
-В n8n встроено более 400 готовых узлов для популярных сервисов. Узлы бывают двух типов:
-
-- **Узел триггера** — событие, запускающее рабочий процесс: новое сообщение, нажатие кнопки, обновление статуса и др.
-- **Узел действия** — логика после триггера: отправка сообщения в чат, создание задачи, добавление записи в БД и т.д.
-
-n8n автоматически выполняет каждое действие по триггеру в указанном порядке.
-
-
-
-## Настройка
-
-
-
- Расширение Пачки доступно пока только в Beta. Его нет в веб-версии n8n — для использования нужно развернуть коробочную версию на собственном сервере.
-
- Два способа установки:
-
- **С помощью команды** (требуется Node.js):
-
- ```bash
- npx n8n
- ```
-
- **С помощью Docker:**
-
- ```bash
- docker volume create n8n_data
-
- docker run -it --rm \
- --name n8n \
- -p 5678:5678 \
- -e GENERIC_TIMEZONE="" \
- -e TZ="" \
- -e N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true \
- -e N8N_RUNNERS_ENABLED=true \
- -v n8n_data:/home/node/.n8n \
- docker.n8n.io/n8nio/n8n
- ```
-
- Подробные инструкции — в [официальной документации n8n](https://docs.n8n.io/hosting/) и на [GitHub](https://github.com/n8n-io/n8n).
-
- После запуска настройте аккаунт владельца (Owner Account), указав почту, имя и пароль.
-
-
-
-
- Расширение доступно на [GitHub](https://github.com/pachca/n8n-nodes-pachca) и [npmjs](https://www.npmjs.com/package/n8n-nodes-pachca).
-
- Три способа установки:
-
- 1. Зайти в **Settings** > **Community nodes** и добавить `n8n-nodes-pachca` (рекомендуется)
- 2. Выполнить команду `npm i n8n-nodes-pachca` в директории n8n
- 3. Следовать README-инструкции на [GitHub](https://github.com/pachca/n8n-nodes-pachca)
-
-
-
-
- Credentials — данные для авторизации.
-
- Нажмите **«Add Credential»**, найдите **Pachca API** в списке и заполните поля:
-
- - **Base URL:** `https://api.pachca.com/api/shared/v1`
- - **Access Token:** Токен доступа к API. В Пачке доступны два типа токенов:
- - Персональный токен — доступен в разделе **Автоматизации** > **Интеграции** > **API**
- - Токен бота — доступен в настройках бота на вкладке **API**
-
- Подробнее о токенах — в разделе [Авторизация](/api/authorization). Credentials можно создать несколько — для разных операций и токенов.
-
-
-
-
- Workflow — визуальный редактор, в котором выстраиваются цепочки триггеров и действий.
-
-
-
- Пример: отправка сообщения от лица бота. Триггер — нажатие кнопки **«Execute Workflow»**, действие — **Send a message** в Пачке:
-
- - **Credential** — от чьего лица будет отправлено сообщение
- - **Entity ID** — ID чата
- - **Content** — содержание сообщения
-
- Не забудьте добавить бота в чат.
-
-
-
- В платформу встроено более 400 узлов для популярных сервисов, а дополнительные Community nodes можно установить из интерфейса. При необходимости доступны HTTP-запросы к любому API, условия, кастомный JavaScript- или Python-код.
-
-
-
-## Методы API в nodes Pachca (Beta)
-
-Nodes (узлы) в расширении Пачки для n8n совпадают с методами API. Вот список доступных:
-
-### Действия с сообщениями
-
-- **Send a message** — [Новое сообщение](POST /messages)
-- **Get a message** — [Информация о сообщении](GET /messages/{id})
-- **Get messages from the chat** — [Список сообщений чата](GET /messages)
-- **Update a message** — [Редактирование сообщения](PUT /messages/{id})
-- **Delete a message** — [Удаление сообщения](DELETE /messages/{id})
-- **Pin a message** — [Закрепление сообщения](POST /messages/{id}/pin)
-- **Unpin a message** — [Открепление сообщения](DELETE /messages/{id}/pin)
-- **Get message readers** — [Список прочитавших сообщение](GET /messages/{id}/read_member_ids)
-- **Unfurl message links** — [Unfurl (разворачивание ссылок)](POST /messages/{id}/link_previews)
-
-### Действия с тредами
-
-- **Create a thread** — [Новый тред](POST /messages/{id}/thread)
-- **Get a thread** — [Информация о треде](GET /threads/{id})
-
-### Действия с реакциями
-
-- **Add a reaction** — [Новая реакция](POST /messages/{id}/reactions)
-- **Remove a reaction** — [Удаление реакции](DELETE /messages/{id}/reactions)
-- **Get message reactions** — [Список реакций на сообщение](GET /messages/{id}/reactions)
-
-### Действия с чатом
-
-- **Get all chats** — [Список чатов](GET /chats)
-- **Get a chat** — [Информация о чате](GET /chats/{id})
-- **Create a chat** — [Новый чат](POST /chats)
-- **Update a chat** — [Обновление чата](PUT /chats/{id})
-- **Archive a chat** — [Архивация чата](PUT /chats/{id}/archive)
-- **Unarchive a chat** — [Разархивация чата](PUT /chats/{id}/unarchive)
-- **Get chat members** — [Список участников чата](GET /chats/{id}/members)
-- **Add users to chat** — [Добавление пользователей](POST /chats/{id}/members)
-- **Remove user from chat** — [Исключение пользователя](DELETE /chats/{id}/members/{user_id})
-- **Update user role in chat** — [Редактирование роли](PUT /chats/{id}/members/{user_id})
-- **Leave a chat** — [Выход из чата](DELETE /chats/{id}/leave)
-
-### Действия с пользователями
-
-- **Get all users** — [Список сотрудников](GET /users)
-- **Get a user** — [Информация о сотруднике](GET /users/{id})
-- **Create a user** — [Новый сотрудник](POST /users)
-- **Update a user** — [Редактирование сотрудника](PUT /users/{id})
-- **Delete a user** — [Удаление сотрудника](DELETE /users/{id})
-
-### Действия с тегами пользователей
-
-- **Get all group tags** — [Список тегов сотрудников](GET /group_tags)
-- **Get a group tag** — [Информация о теге](GET /group_tags/{id})
-- **Create a group tag** — [Новый тег](POST /group_tags)
-- **Update a group tag** — [Редактирование тега](PUT /group_tags/{id})
-- **Delete a group tag** — [Удаление тега](DELETE /group_tags/{id})
-- **Get users in group tag** — [Список сотрудников тега](GET /group_tags/{id}/users)
-- **Add tags to chat** — [Добавление тегов](POST /chats/{id}/group_tags)
-- **Remove tag from chat** — [Исключение тега](DELETE /chats/{id}/group_tags/{tag_id})
-
-### Действия со статусом и профилем
-
-- **Get my profile** — [Информация о профиле](GET /profile)
-- **Get my status** — [Текущий статус](GET /profile/status)
-- **Set my status** — [Новый статус](PUT /profile/status)
-- **Clear my status** — [Удаление статуса](DELETE /profile/status)
-
-### Действия с формами
-
-- **Create a form** — [Открытие представления](POST /views/open)
-- **Process form submission** — закрытие и отображение ошибок
-- **Get form templates**
-
-### Другие действия
-
-- **Get custom properties** — [Список дополнительных полей](GET /custom_properties)
-- **Create a task** — [Новое напоминание](POST /tasks)
-- **Update a bot** — [Редактирование бота](PUT /bots/{id})
-- **Upload a file** — [Загрузка файла](POST /direct_url)
-
-Некоторые действия доступны только с персональным токеном (владельца или участника с ролью «Администратор»).
diff --git a/apps/docs/content/guides/n8n/advanced.mdx b/apps/docs/content/guides/n8n/advanced.mdx
new file mode 100644
index 00000000..b681e3c9
--- /dev/null
+++ b/apps/docs/content/guides/n8n/advanced.mdx
@@ -0,0 +1,207 @@
+---
+title: Продвинутые функции
+description: "Экспорт сообщений, загрузка файлов, кнопки, формы, AI-агент, разворачивание ссылок, журнал безопасности"
+---
+
+# Продвинутые функции
+
+## Загрузка файлов
+
+
+
+Ресурс **File** позволяет загружать файлы через двухшаговый S3 upload. n8n автоматически выполняет оба этапа: запрашивает presigned URL через API и загружает файл на S3.
+
+**Два источника файлов:**
+
+| Источник | Описание |
+|----------|----------|
+| **URL** | Файл загружается по ссылке. Укажите `fileUrl`, `fileName` и `contentType` |
+| **Binary Data** | Файл из предыдущего узла workflow (например, из HTTP Request). Укажите `binaryProperty` |
+
+**Пример workflow: загрузка PDF и отправка в чат**
+
+1. **HTTP Request** — скачать файл по URL
+2. **Pachca** (File > Create) — загрузить файл, получить `key`
+3. **Pachca** (Message > Create) — отправить сообщение с прикреплённым файлом, указав `key` в поле `files`
+
+Загруженные файлы привязываются к сообщениям через массив `files` при создании или обновлении сообщения. Каждый файл описывается объектом с полями: `key`, `name`, `file_type`, `size`.
+
+Подробнее — в [документации загрузки файлов](/api/file-uploads).
+
+---
+
+## Загрузка аватара
+
+Операции **Update Avatar** для ресурсов **Profile** и **User** позволяют загружать аватар через `multipart/form-data`.
+
+**Как использовать:**
+
+1. **HTTP Request** или **Read Binary File** — загрузите изображение в бинарное свойство (по умолчанию `data`)
+2. **Pachca** (Profile > Update Avatar или User > Update Avatar) — в поле **Input Binary Field** укажите имя бинарного свойства
+
+Для удаления аватара используйте операцию **Delete Avatar** — она не требует параметров (для User — только `userId`).
+
+Операции с аватарами сотрудников (User > Update/Delete Avatar) требуют прав администратора.
+
+---
+
+## Экспорт сообщений
+
+Ресурс **Chat Export** позволяет выгружать сообщения из чатов. Экспорт выполняется асинхронно: вы запрашиваете экспорт, а Пачка присылает уведомление на вебхук, когда архив готов.
+
+**Пример workflow:**
+
+
+
+ Создайте отдельный workflow с узлом **Webhook** (встроенный в n8n). Он создаст URL, на который Пачка отправит уведомление о готовности экспорта. Скопируйте этот URL — он понадобится на следующем шаге.
+
+
+ В основном workflow добавьте узел **Pachca** с ресурсом **Chat Export** и операцией **Create**. Укажите период (`startAt`, `endAt`) и вставьте URL вебхука из первого шага в поле `webhookUrl`.
+
+
+ Когда экспорт будет готов, Пачка отправит JSON на ваш вебхук:
+ ```json
+ {
+ "type": "export",
+ "event": "ready",
+ "export_id": 22322,
+ "created_at": "2025-03-20T12:33:58Z"
+ }
+ ```
+
+
+ В workflow с Webhook-узлом добавьте узел **Pachca** с ресурсом **Chat Export** и операцией **Get**. Передайте `export_id` из данных вебхука в поле **ID**. Архив будет скачан автоматически.
+
+
+
+**Ограничения:**
+- Максимальный период одной выгрузки: 45 дней (366 дней при указании конкретных чатов)
+- Максимум 50 чатов при фильтрации по `chatIds`
+- Новый запрос можно сделать только после завершения текущего
+
+Подробнее — в [документации экспорта](/guides/export).
+
+---
+
+## Кнопки в сообщениях
+
+
+
+При создании или обновлении сообщения можно добавить интерактивные кнопки через поле **Buttons**. Кнопки передаются как JSON-строка.
+
+Два типа кнопок:
+
+| Тип | Описание |
+|-----|----------|
+| **URL-кнопка** | Открывает ссылку в браузере |
+| **Data-кнопка** | Отправляет вебхук с `button_pressed` событием |
+
+Пример JSON для одной строки кнопок:
+
+```json
+[
+ [
+ { "text": "Открыть сайт", "url": "https://example.com" },
+ { "text": "Подтвердить", "data": "confirm_action" }
+ ]
+]
+```
+
+Максимум 100 кнопок на сообщение, до 8 в одной строке.
+
+Подробнее о кнопках, их внешнем виде в чате и обработке нажатий — в [документации кнопок](/guides/buttons).
+
+---
+
+## Формы
+
+Ресурс **Form** позволяет открывать модальные формы (представления) для пользователей.
+
+**Как это работает:**
+
+1. Пользователь нажимает Data-кнопку в сообщении бота
+2. Бот получает вебхук с `trigger_id`
+3. Бот вызывает [Открытие представления](POST /views/open) с `trigger_id` и описанием формы
+4. Пользователь видит модальное окно с полями
+
+**Ключевые параметры:**
+
+| Параметр | Описание |
+|----------|----------|
+| `triggerId` | Уникальный ID из вебхука кнопки (действителен 3 секунды) |
+| `title` | Заголовок модального окна |
+| `type` | Тип представления (по умолчанию `modal`) |
+| `blocks` | JSON-массив блоков формы |
+| `submitText` | Текст кнопки отправки |
+| `closeText` | Текст кнопки закрытия |
+
+**Пример workflow:**
+
+1. **Pachca Trigger** — событие `Button Pressed`
+2. **Pachca** (Form > Create) — открыть форму с `trigger_id` из триггера
+3. **Pachca Trigger** — событие `Form Submitted` (в отдельном workflow)
+4. Обработка данных формы
+
+Подробнее о формах, типах полей и внешнем виде модального окна в интерфейсе Пачки — в [документации форм](/guides/forms/overview).
+
+---
+
+## AI-агент
+
+
+
+Оба узла (Pachca и Pachca Trigger) поддерживают `usableAsTool: true` — их можно использовать как инструменты для **AI Agent** в n8n.
+
+**Что это значит:**
+
+- AI Agent может вызывать операции Pachca для выполнения задач
+- Примеры: поиск сообщений, отправка ответов, создание задач
+- Agent автоматически выбирает подходящую операцию на основе запроса
+
+**Пример: AI-помощник для команды**
+
+
+
+ Создайте новый workflow. Добавьте узел **AI Agent** и подключите LLM-модель (OpenAI, Anthropic и др.) через соответствующие Credentials.
+
+
+ Нажмите **+** на входе **Tool** узла AI Agent и добавьте узел **Pachca**. Выберите нужную операцию — например, **Search > Get Many Messages** для поиска по сообщениям. Добавьте ещё один узел Pachca для **Message > Create** — отправки ответов.
+
+
+ Добавьте **Pachca Trigger** с событием `New Message` на вход workflow. AI Agent будет автоматически отвечать на сообщения пользователей, используя поиск по истории чатов.
+
+
+
+AI Agent самостоятельно выбирает подходящий инструмент на основе запроса пользователя — ищет информацию, создаёт задачи или отправляет сообщения.
+
+Для использования AI Agent необходимо настроить LLM-провайдер (OpenAI, Anthropic и др.) в Credentials n8n.
+
+---
+
+## Разворачивание ссылок
+
+Ресурс **Link Preview** позволяет формировать кастомные превью для ссылок в сообщениях бота.
+
+Когда бот отправляет сообщение со ссылкой, Пачка может запросить у бота данные для превью. Бот может ответить через [Создание превью ссылки](POST /messages/{id}/link_previews) с заголовком, описанием и изображением.
+
+Подробнее — в [документации разворачивания ссылок](/guides/link-previews).
+
+---
+
+## Журнал безопасности
+
+Ресурс **Security** предоставляет доступ к журналу аудита — списку событий безопасности в пространстве.
+
+**Доступные фильтры:**
+
+| Фильтр | Описание |
+|--------|----------|
+| `eventKey` | Тип события (login, message_created, user_deleted и др.) |
+| `actorId` | ID пользователя, совершившего действие |
+| `actorType` | Тип актора (user или bot) |
+| `entityId` | ID сущности, над которой совершено действие |
+| `entityType` | Тип сущности |
+| `startTime` | Начало временного диапазона |
+| `endTime` | Конец временного диапазона |
+
+Подробнее — в [документации журнала аудита](/guides/audit-events).
diff --git a/apps/docs/content/guides/n8n/migration.mdx b/apps/docs/content/guides/n8n/migration.mdx
new file mode 100644
index 00000000..3844b442
--- /dev/null
+++ b/apps/docs/content/guides/n8n/migration.mdx
@@ -0,0 +1,111 @@
+---
+title: Миграция с v1
+description: "Обновление с v1 на v2: таблицы переименований, новые ресурсы, полная обратная совместимость"
+---
+
+# Миграция с v1
+
+Обновление необязательно. Все существующие workflow на v1 продолжают работать без изменений.
+
+## Полная обратная совместимость
+
+Версия 2.0 на 100% совместима с v1. При обновлении расширения:
+
+- Существующие workflow остаются на v1 и работают как прежде
+- Новые workflow по умолчанию создаются на v2 с обновлёнными именами
+- Переход с v1 на v2 — опциональный
+
+## Переименованные ресурсы
+
+| v1 | v2 | Изменение |
+|----|-----|-----------|
+| `reactions` | `reaction` | Единственное число |
+| `status` | `profile` | Более точное имя |
+| `customFields` | `customProperty` | Более точное имя |
+
+## Переименованные операции
+
+| Ресурс | v1 | v2 |
+|--------|-----|-----|
+| Message | `send` | `create` |
+| Message | `getById` | `get` |
+| Chat | `getById` | `get` |
+| User | `getById` | `get` |
+| Group Tag | `getById` | `get` |
+| Group Tag | `getUsers` | `getAllUsers` |
+| Reaction | `addReaction` | `create` |
+| Reaction | `deleteReaction` | `delete` |
+| Reaction | `getReactions` | `getAll` |
+| Custom Property | `getCustomProperties` | `get` |
+| Profile | `getProfile` | `get` |
+| Thread | `createThread` | `create` |
+| Thread | `getThread` | `get` |
+| Form | `createView` | `create` |
+| File | `upload` | `create` |
+
+## Перенесённые операции
+
+Некоторые операции из v1 ресурсов были перенесены в новые v2 ресурсы:
+
+| v1 ресурс | v1 операция | v2 ресурс | v2 операция |
+|-----------|-------------|-----------|-------------|
+| Chat | `getMembers` | Chat Member | `getAll` |
+| Chat | `addUsers` | Chat Member | `create` |
+| Chat | `removeUser` | Chat Member | `delete` |
+| Chat | `updateRole` | Chat Member | `update` |
+| Chat | `leaveChat` | Chat Member | `leave` |
+| Group Tag | `addTags` | Chat Member | `addGroupTags` |
+| Group Tag | `removeTag` | Chat Member | `removeGroupTags` |
+| Message | `getReadMembers` | Read Member | `getAll` |
+| Message | `unfurl` | Link Preview | `create` |
+
+Все перенесённые операции продолжают работать в v1 workflow без изменений. Маршрутизатор автоматически транслирует v1 имена в v2.
+
+## Новые ресурсы (только v2)
+
+| Ресурс | Описание |
+|--------|----------|
+| **Chat Member** | Управление участниками чата: добавление, удаление, роли, теги |
+| **Custom Property** | Дополнительные поля пространства |
+| **Read Member** | Список прочитавших сообщение |
+| **Link Preview** | Разворачивание ссылок в сообщениях |
+| **Search** | Полнотекстовый поиск по чатам, сообщениям, пользователям |
+| **Chat Export** | Экспорт сообщений из чатов |
+| **Security** | Журнал безопасности |
+
+## Новые функции
+
+| Функция | Описание |
+|---------|----------|
+| **Return All / Limit** | Курсорная автопагинация вместо ручного `per`/`page` |
+| **Simplify** | Переключатель для получения только ключевых полей из ответа API ([подробнее](/guides/n8n/resources#simplify)) |
+| **Pachca Trigger** | Webhook-нода с авторегистрацией вебхука через Bot ID, 16 типов событий |
+| **AI Tool Use** | Использование узлов как инструментов AI Agent |
+| **Searchable Dropdowns** | Поиск по чатам и пользователям в выпадающих списках |
+| **File Upload** | Загрузка файлов через S3 с поддержкой URL и Binary Data |
+| **Task CRUD** | Полный CRUD для задач (было только создание) |
+
+## Как работает совместимость
+
+Расширение использует паттерн **VersionedNodeType** с `defaultVersion: 2`:
+
+- **V1** и **V2** — отдельные классы с собственными описаниями ресурсов и операций
+- Общий **SharedRouter** обрабатывает запросы обеих версий, транслируя v1 имена ресурсов и операций в v2 на лету
+- При обновлении расширения существующие ноды сохраняют `typeVersion: 1` и используют V1 класс — все параметры и поведение остаются прежними
+- Новые ноды по умолчанию создаются с `typeVersion: 2` и используют чистый V2 класс
+- В Node Creator отображаются только v2 операции — без дубликатов
+- V1 ноды в существующих workflow показывают жёлтый баннер «New node version available» с предложением обновиться
+
+## Как обновить workflow (необязательно)
+
+При открытии существующего workflow вы увидите жёлтый баннер в настройках v1 нод — это информационное уведомление, менять ничего не нужно.
+
+Если вы хотите перевести ноду на v2:
+
+1. Откройте workflow в n8n
+2. Удалите v1 ноду Pachca
+3. Добавьте новую ноду Pachca (по умолчанию v2)
+4. Перенастройте с v2 именами ресурсов и операций
+5. API-вызовы идентичны — изменились только имена в UI
+
+При удалении ноды и добавлении новой вы получите v2 с обновлёнными именами и новыми ресурсами. Все параметры и API endpoint-ы остались прежними.
diff --git a/apps/docs/content/guides/n8n/overview.mdx b/apps/docs/content/guides/n8n/overview.mdx
new file mode 100644
index 00000000..f248b53f
--- /dev/null
+++ b/apps/docs/content/guides/n8n/overview.mdx
@@ -0,0 +1,66 @@
+---
+title: Обзор
+description: Автоматизации в Пачке через платформу n8n — 18 ресурсов, триггер, AI-агент
+---
+
+# n8n
+
+## Что такое n8n
+
+[n8n](https://n8n.io) — платформа для автоматизации рабочих процессов с открытым исходным кодом. Можно развернуть на собственном сервере или использовать веб-версию. Платформа позволяет создавать интеграции с сервисами без написания кода, используя визуальный редактор с узлами (nodes).
+
+В n8n встроено более 400 готовых узлов для популярных сервисов. Узлы бывают двух типов:
+
+- **Узел триггера** — событие, запускающее рабочий процесс: новое сообщение, нажатие кнопки, обновление статуса и др.
+- **Узел действия** — логика после триггера: отправка сообщения в чат, создание задачи, добавление записи в БД и т.д.
+
+
+
+## Что можно автоматизировать
+
+- **Уведомления из внешних систем** — пересылайте алерты из мониторинга, CI/CD, CRM и других сервисов в чаты Пачки
+- **Бот-ассистент** — подключите AI-агент, который ищет по сообщениям и отвечает на вопросы команды
+- **Задачи по сообщениям** — автоматически создавайте задачи из сообщений с определённым тегом или реакцией
+- **Согласование** — отправляйте кнопки «Одобрить / Отклонить» и обрабатывайте ответ в workflow
+- **Онбординг** — приветствуйте новых сотрудников личным сообщением от бота
+- **Синхронизация данных** — экспортируйте сообщения, задачи и события в Google Sheets, Notion или другие сервисы
+- **Мониторинг и алерты** — проверяйте состояние сервисов по расписанию и отправляйте алерт в чат при сбое
+
+## Расширение Пачки
+
+Расширение Пачки для n8n предоставляет два узла:
+
+| Узел | Тип | Описание |
+|------|-----|----------|
+| **Pachca** | Действие | 18 ресурсов и более 60 операций для работы с API |
+| **Pachca Trigger** | Триггер | 16 типов событий через вебхуки |
+
+
+
+ Сообщения, чаты, пользователи, задачи, формы, поиск и другие
+
+
+ 16 типов событий с авторегистрацией вебхука
+
+
+ Используйте Pachca как инструмент AI Agent
+
+
+ Cursor-based пагинация с Return All / Limit
+
+
+
+## Что нового в v2.0
+
+Версия 2.0 — полностью автогенерируемая из OpenAPI-спецификации с сохранением 100% обратной совместимости с v1.
+
+- **7 новых ресурсов:** Chat Member, Custom Property, Read Member, Link Preview, Search, Chat Export, Security
+- **Курсорная пагинация** с Return All / Limit вместо ручного `per`/`page`
+- **AI Tool Use** — `usableAsTool: true` для использования с AI Agent
+- **Trigger-нода** с авторегистрацией вебхука через Bot ID и 16 типами событий
+- **Поисковые выпадающие списки** для Chat ID и User ID
+- **Загрузка файлов** через S3 с поддержкой URL и Binary Data
+- **Полнотекстовый поиск** по сообщениям, чатам и пользователям
+- **Журнал безопасности** — аудит действий пользователей
+
+Все существующие workflow на v1 продолжают работать без изменений. Подробнее — в разделе [Миграция с v1](/guides/n8n/migration).
diff --git a/apps/docs/content/guides/n8n/resources.mdx b/apps/docs/content/guides/n8n/resources.mdx
new file mode 100644
index 00000000..90a57a5a
--- /dev/null
+++ b/apps/docs/content/guides/n8n/resources.mdx
@@ -0,0 +1,348 @@
+---
+title: Ресурсы и операции
+description: Все 18 ресурсов и более 60 операций расширения Пачки для n8n
+---
+
+# Ресурсы и операции
+
+В расширении Пачки каждый узел **Pachca** работает по модели **Resource → Operation**: вы выбираете ресурс (например, Message) и операцию над ним (например, Create).
+
+
+
+Для каждого ресурса доступен свой набор операций.
+
+
+
+## Список ресурсов
+
+| # | Ресурс | Операций | Описание | Только v2 |
+|---|--------|:---:|----------|:---:|
+| 1 | [Message](#message) | 7 | Сообщения: создание, редактирование, удаление, закрепление | |
+| 2 | [Chat](#chat) | 6 | Чаты: создание, обновление, архивация | |
+| 3 | [Chat Member](#chat-member) | 7 | Участники чата: добавление, удаление, роли, теги | да |
+| 4 | [User](#user) | 10 | Сотрудники: CRUD, аватар, статус | |
+| 5 | [Group Tag](#group-tag) | 6 | Теги сотрудников: CRUD, список пользователей | |
+| 6 | [Thread](#thread) | 2 | Треды: создание, получение | |
+| 7 | [Reaction](#reaction) | 3 | Реакции: создание, удаление, список | |
+| 8 | [Profile](#profile) | 7 | Мой профиль: информация, аватар, статус | |
+| 9 | [Task](#task) | 5 | Задачи: полный CRUD | |
+| 10 | [Bot](#bot) | 3 | Боты: обновление, события, удаление событий | |
+| 11 | [File](#file) | 1 | Загрузка файлов через S3 | |
+| 12 | [Form](#form) | 1 | Модальные формы | |
+| 13 | [Custom Property](#custom-property) | 1 | Дополнительные поля | да |
+| 14 | [Read Member](#read-member) | 1 | Список прочитавших сообщение | да |
+| 15 | [Link Preview](#link-preview) | 1 | Разворачивание ссылок | да |
+| 16 | [Search](#search) | 3 | Полнотекстовый поиск | да |
+| 17 | [Chat Export](#chat-export) | 2 | Экспорт сообщений из чатов | да |
+| 18 | [Security](#security) | 1 | Журнал безопасности | да |
+
+Для некоторых операций требуются скоупы, которые доступны только определённым ролям (администратор, владелец). При создании персонального токена отображаются только скоупы, доступные вашей роли. Подробнее — в разделе [Авторизация](/api/authorization).
+
+---
+
+## Message
+
+Сообщения: создание, получение, редактирование, удаление, закрепление и открепление.
+
+| Операция | API |
+|----------|-----|
+| Create | [Создание сообщения](POST /messages) |
+| Get Many | [Список сообщений чата](GET /messages) |
+| Get | [Информация о сообщении](GET /messages/{id}) |
+| Update | [Редактирование сообщения](PUT /messages/{id}) |
+| Delete | [Удаление сообщения](DELETE /messages/{id}) |
+| Pin | [Закрепление сообщения](POST /messages/{id}/pin) |
+| Unpin | [Открепление сообщения](DELETE /messages/{id}/pin) |
+
+**Ключевые параметры Create:** `entityId` (ID чата или пользователя), `content` (текст, Markdown), `entityType` (discussion, user, thread), `files`, `buttons`, `parentMessageId`.
+
+**Сортировка в Get Many:** параметры `sort` (по умолчанию `id`) и `order` (`asc` / `desc`) определяют порядок выдачи сообщений.
+
+
+
+---
+
+## Chat
+
+Чаты: создание, получение, обновление, архивация и разархивация.
+
+| Операция | API |
+|----------|-----|
+| Create | [Создание чата](POST /chats) |
+| Get Many | [Список чатов](GET /chats) |
+| Get | [Информация о чате](GET /chats/{id}) |
+| Update | [Обновление чата](PUT /chats/{id}) |
+| Archive | [Архивация чата](PUT /chats/{id}/archive) |
+| Unarchive | [Разархивация чата](PUT /chats/{id}/unarchive) |
+
+**Сортировка в Get Many:** параметры `sort` (`id` или `last_message_at`) и `order` (`asc` / `desc`). Также доступны фильтры `availability`, `lastMessageAtAfter`, `lastMessageAtBefore`.
+
+---
+
+## Chat Member
+
+Управление участниками чата: добавление, удаление, изменение ролей, управление тегами.
+
+В v1 эти операции были частью ресурса Chat. В v2 они выделены в отдельный ресурс Chat Member.
+
+| Операция | API |
+|----------|-----|
+| Get Many | [Список участников чата](GET /chats/{id}/members) |
+| Create | [Добавление пользователей в чат](POST /chats/{id}/members) |
+| Delete | [Удаление пользователя из чата](DELETE /chats/{id}/members/{user_id}) |
+| Update | [Изменение роли участника](PUT /chats/{id}/members/{user_id}) |
+| Leave | [Выход из чата](DELETE /chats/{id}/leave) |
+| Add Group Tags | [Добавление тегов к чату](POST /chats/{id}/group_tags) |
+| Remove Group Tags | [Удаление тегов из чата](DELETE /chats/{id}/group_tags/{tag_id}) |
+
+---
+
+## User
+
+Сотрудники: полный CRUD, получение и управление статусом.
+
+| Операция | API |
+|----------|-----|
+| Create | [Создание сотрудника](POST /users) |
+| Get Many | [Список сотрудников](GET /users) |
+| Get | [Информация о сотруднике](GET /users/{id}) |
+| Update | [Обновление сотрудника](PUT /users/{id}) |
+| Delete | [Удаление сотрудника](DELETE /users/{id}) |
+| Update Avatar | [Обновление аватара](PUT /users/{user_id}/avatar) |
+| Delete Avatar | [Удаление аватара](DELETE /users/{user_id}/avatar) |
+| Get Status | [Получение статуса](GET /users/{user_id}/status) |
+| Update Status | [Обновление статуса](PUT /users/{user_id}/status) |
+| Delete Status | [Удаление статуса](DELETE /users/{user_id}/status) |
+
+---
+
+## Group Tag
+
+Теги (группы) сотрудников: создание, обновление, удаление, список пользователей.
+
+| Операция | API |
+|----------|-----|
+| Create | [Создание тега](POST /group_tags) |
+| Get Many | [Список тегов](GET /group_tags) |
+| Get | [Информация о теге](GET /group_tags/{id}) |
+| Update | [Обновление тега](PUT /group_tags/{id}) |
+| Delete | [Удаление тега](DELETE /group_tags/{id}) |
+| Get Many Users | [Список пользователей тега](GET /group_tags/{id}/users) |
+
+---
+
+## Thread
+
+Треды (комментарии к сообщениям): создание и получение.
+
+| Операция | API |
+|----------|-----|
+| Create | [Создание треда](POST /messages/{id}/thread) |
+| Get | [Информация о треде](GET /threads/{id}) |
+
+---
+
+## Reaction
+
+Реакции на сообщения: создание, удаление, список.
+
+| Операция | API |
+|----------|-----|
+| Create | [Добавление реакции](POST /messages/{id}/reactions) |
+| Delete | [Удаление реакции](DELETE /messages/{id}/reactions) |
+| Get Many | [Список реакций](GET /messages/{id}/reactions) |
+
+---
+
+## Profile
+
+Профиль текущего пользователя: информация, статус, информация о токене.
+
+| Операция | API |
+|----------|-----|
+| Get | [Информация о профиле](GET /profile) |
+| Get Info | [Информация о токене](GET /oauth/token/info) |
+| Update Avatar | [Обновление аватара](PUT /profile/avatar) |
+| Delete Avatar | [Удаление аватара](DELETE /profile/avatar) |
+| Get Status | [Получение статуса](GET /profile/status) |
+| Update Status | [Обновление статуса](PUT /profile/status) |
+| Delete Status | [Удаление статуса](DELETE /profile/status) |
+
+**Загрузка аватара:** операция Update Avatar принимает бинарные данные из предыдущего узла (например, HTTP Request или Read Binary File). В поле **Input Binary Field** укажите имя бинарного свойства (по умолчанию `data`).
+
+---
+
+## Task
+
+Задачи (напоминания): полный CRUD.
+
+| Операция | API |
+|----------|-----|
+| Create | [Создание задачи](POST /tasks) |
+| Get Many | [Список задач](GET /tasks) |
+| Get | [Информация о задаче](GET /tasks/{id}) |
+| Update | [Обновление задачи](PUT /tasks/{id}) |
+| Delete | [Удаление задачи](DELETE /tasks/{id}) |
+
+**Типы задач:** `call`, `email`, `event`, `meeting`, `reminder`.
+
+---
+
+## Bot
+
+Управление ботами: обновление настроек, получение и удаление событий.
+
+| Операция | API |
+|----------|-----|
+| Update | [Обновление бота](PUT /bots/{id}) |
+| Get Many Events | [Список событий бота](GET /webhooks/events) |
+| Remove Events | [Удаление событий](DELETE /webhooks/events/{id}) |
+
+---
+
+## File
+
+Загрузка файлов через двухшаговый S3 upload.
+
+| Операция | API |
+|----------|-----|
+| Create | [Загрузка файла](POST /uploads) |
+
+Подробнее — в разделе [Продвинутые функции](/guides/n8n/advanced#zagruzka-fajlov).
+
+---
+
+## Form
+
+Модальные формы (представления).
+
+| Операция | API |
+|----------|-----|
+| Create | [Открытие представления](POST /views/open) |
+
+Подробнее — в разделе [Продвинутые функции](/guides/n8n/advanced#formy) и в [документации форм](/guides/forms/overview).
+
+---
+
+## Custom Property
+
+Дополнительные поля пространства.
+
+| Операция | API |
+|----------|-----|
+| Get | [Список дополнительных полей](GET /custom_properties) |
+
+---
+
+## Read Member
+
+Список пользователей, прочитавших сообщение.
+
+| Операция | API |
+|----------|-----|
+| Get Many | [Список прочитавших](GET /messages/{id}/read_member_ids) |
+
+---
+
+## Link Preview
+
+Разворачивание ссылок в сообщениях.
+
+| Операция | API |
+|----------|-----|
+| Create | [Создание превью ссылки](POST /messages/{id}/link_previews) |
+
+Подробнее — в [документации разворачивания ссылок](/guides/link-previews).
+
+---
+
+## Search
+
+Полнотекстовый поиск по сообщениям, чатам и пользователям.
+
+| Операция | API |
+|----------|-----|
+| Get Many Chats | [Поиск чатов](GET /search/chats) |
+| Get Many Messages | [Поиск сообщений](GET /search/messages) |
+| Get Many Users | [Поиск пользователей](GET /search/users) |
+
+**Обязательный параметр:** `query` — строка поиска.
+
+---
+
+## Chat Export
+
+Экспорт сообщений из чатов: запрос экспорта и скачивание архива.
+
+| Операция | API |
+|----------|-----|
+| Create | [Запрос экспорта](POST /chats/exports) |
+| Get | [Скачивание архива](GET /chats/exports/{id}) |
+
+**Ключевые параметры Create:** `startAt` (дата начала, YYYY-MM-DD), `endAt` (дата окончания), `webhookUrl` (URL для уведомления о готовности).
+
+**Дополнительные параметры:** `chatIds` (экспорт конкретных чатов, до 50), `skipChatsFile` (не создавать chats.json).
+
+Экспорт выполняется асинхронно. После завершения Пачка отправит вебхук на указанный `webhookUrl` с `export_id`. Используйте операцию **Get** для скачивания готового архива.
+
+Подробнее — в разделе [Продвинутые функции](/guides/n8n/advanced#eksport-soobshhenij) и в [документации экспорта](/guides/export).
+
+---
+
+## Security
+
+Журнал безопасности: отслеживание действий пользователей.
+
+| Операция | API |
+|----------|-----|
+| Get Many | [Список событий аудита](GET /audit_events) |
+
+**Фильтры:** `eventKey`, `actorId`, `actorType`, `entityId`, `entityType`, `startTime`, `endTime`.
+
+Подробнее — в [документации журнала аудита](/guides/audit-events).
+
+---
+
+## Пагинация
+
+Все операции Get Many поддерживают автоматическую курсорную пагинацию:
+
+- **Return All** = `true` — получить все результаты автоматически, переключаясь между страницами
+- **Return All** = `false` — получить не более **Limit** результатов (по умолчанию 50)
+
+
+
+n8n автоматически отправляет повторные запросы с курсором до получения всех данных.
+
+Для операций со списками (Get Many) рекомендуется использовать **Return All = false** с разумным **Limit**, чтобы избежать долгих запросов при большом объёме данных.
+
+---
+
+## Simplify
+
+Операции получения данных (Get, Get Many) поддерживают переключатель **Simplify** (включён по умолчанию). Когда Simplify включён, из ответа API возвращаются только ключевые поля — остальные отбрасываются.
+
+| Ресурс | Ключевые поля |
+|--------|---------------|
+| Message | `id`, `entity_id`, `chat_id`, `content`, `user_id`, `created_at` |
+| Chat | `id`, `name`, `channel`, `public`, `members_count`, `created_at` |
+| User | `id`, `first_name`, `last_name`, `nickname`, `email`, `role`, `suspended` |
+| Task | `id`, `content`, `kind`, `status`, `priority`, `due_at`, `created_at` |
+| Bot | `id`, `name`, `created_at` |
+| Group Tag | `id`, `name`, `users_count` |
+| Reaction | `id`, `code`, `user_id`, `created_at` |
+| Chat Export | `id`, `status`, `created_at` |
+
+Чтобы получить все поля ответа — выключите **Simplify**.
+
+Simplify доступен только в v2. В v1 workflow всегда возвращают полный ответ API.
+
+---
+
+## Поисковые выпадающие списки
+
+
+
+Для поля **Chat ID** доступен поиск по имени: начните вводить текст, и n8n покажет подходящие результаты из вашего пространства Пачки.
+
+Поиск вызывает API-эндпоинт [Поиск чатов](GET /search/chats) и работает только с валидным `Access Token` в Credentials.
diff --git a/apps/docs/content/guides/n8n/setup.mdx b/apps/docs/content/guides/n8n/setup.mdx
new file mode 100644
index 00000000..8377b703
--- /dev/null
+++ b/apps/docs/content/guides/n8n/setup.mdx
@@ -0,0 +1,132 @@
+---
+title: Начало работы
+description: Установка n8n, расширения Пачки, настройка Credentials и первый workflow
+---
+
+# Начало работы
+
+## Установка
+
+
+
+ Два способа установки:
+
+ **С помощью команды** (требуется Node.js 22+):
+
+ ```bash
+ npx n8n
+ ```
+
+ **С помощью Docker:**
+
+ ```bash
+ docker volume create n8n_data
+
+ docker run -it --rm \
+ --name n8n \
+ -p 5678:5678 \
+ -e GENERIC_TIMEZONE="Europe/Moscow" \
+ -e TZ="Europe/Moscow" \
+ -e N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true \
+ -e N8N_RUNNERS_ENABLED=true \
+ -v n8n_data:/home/node/.n8n \
+ docker.n8n.io/n8nio/n8n
+ ```
+
+ Подробные инструкции — в [официальной документации n8n](https://docs.n8n.io/hosting/) и на [GitHub](https://github.com/n8n-io/n8n).
+
+ После запуска откройте `http://localhost:5678` и настройте аккаунт владельца (Owner Account), указав почту, имя и пароль.
+
+
+
+ После входа вы увидите главный экран n8n с пустым списком workflow.
+
+
+
+
+ Расширение доступно на [npm](https://www.npmjs.com/package/n8n-nodes-pachca) и [GitHub](https://github.com/pachca/openapi/tree/main/integrations/n8n).
+
+ Три способа установки:
+
+ 1. Зайти в **Settings** > **Community nodes** и добавить `n8n-nodes-pachca` (рекомендуется)
+ 2. Выполнить команду `npm i n8n-nodes-pachca` в директории n8n
+ 3. Следовать README-инструкции на [GitHub](https://github.com/pachca/openapi/tree/main/integrations/n8n)
+
+
+
+## Настройка Credentials
+
+
+
+ Credentials — данные для авторизации. Перейдите в **Credentials** и нажмите **Add Credential**.
+
+
+
+ Найдите **Pachca API** в списке и заполните поля:
+
+
+
+ | Поле | Обязательное | Описание |
+ |------|:---:|----------|
+ | **Base URL** | нет | Базовый URL API. По умолчанию `https://api.pachca.com/api/shared/v1`. Менять только для on-premise |
+ | **Access Token** | да | Токен доступа к API |
+ | **Bot ID** | нет | ID бота — нужен для авторегистрации вебхука в [Pachca Trigger](/guides/n8n/trigger). Автоопределяется из токена бота |
+ | **Signing Secret** | нет | Секрет для верификации входящих webhook-запросов (HMAC-SHA256) |
+ | **Webhook Allowed IPs** | нет | Список IP-адресов через запятую, с которых разрешены входящие вебхуки. Пачка отправляет с `37.200.70.177`. Пустое поле — разрешить все |
+
+
+
+
+ В Пачке доступны два типа токенов:
+
+ - **Персональный токен** — доступен в разделе **Автоматизации** > **Интеграции** > **API**
+ - **Токен бота** — доступен в настройках бота на вкладке **API**
+
+ Доступные операции зависят от [скоупов](/api/authorization#skoupy) токена, а не от его типа. Подробнее — в разделе [Авторизация](/api/authorization).
+
+ После заполнения полей нажмите **Test** — n8n проверит подключение вызовом [Информация о токене](GET /oauth/token/info). При успехе вы увидите подтверждение.
+
+
+
+ Если тест не проходит — проверьте правильность токена и доступность API. Подробнее — в разделе [Устранение ошибок](/guides/n8n/troubleshooting).
+
+
+
+## Первый workflow
+
+
+
+ Workflow — визуальный редактор, в котором выстраиваются цепочки триггеров и действий. Создайте новый workflow и добавьте триггер **Manual Trigger** для ручного запуска.
+
+ Нажмите **+** на выходе триггера и найдите **Pachca** в списке узлов.
+
+
+
+ После добавления узла на канвасе появится цепочка: Manual Trigger → Pachca.
+
+
+
+
+ Дважды кликните на узел **Pachca** и настройте:
+
+ - **Credential:** выберите созданный Pachca API
+ - **Resource:** Message
+ - **Operation:** Create
+ - **Entity ID:** ID чата (число)
+ - **Content:** текст сообщения
+
+
+
+ Перед отправкой сообщения [добавьте бота в чат](/guides/bots#dostupy-bota-k-chatam-i-soobscheniyam).
+
+ Закройте панель настроек и нажмите **Execute Workflow**. При успехе узлы подсветятся зелёным и покажут количество обработанных элементов.
+
+
+
+ Откройте узел Pachca, чтобы увидеть ответ API с данными созданного сообщения.
+
+
+
+ Больше примеров — в разделе [Примеры workflow](/guides/n8n/workflows).
+
+
diff --git a/apps/docs/content/guides/n8n/trigger.mdx b/apps/docs/content/guides/n8n/trigger.mdx
new file mode 100644
index 00000000..66e43ce2
--- /dev/null
+++ b/apps/docs/content/guides/n8n/trigger.mdx
@@ -0,0 +1,150 @@
+---
+title: Триггер
+description: "Pachca Trigger: 16 типов событий, авторегистрация вебхука, проверка подписи"
+---
+
+# Триггер
+
+Узел **Pachca Trigger** запускает workflow при наступлении события в Пачке — новое сообщение, нажатие кнопки, отправка формы, изменение состава команды и др.
+
+## Поддерживаемые события
+
+### Сообщения и чаты
+
+| Событие | Значение | Описание |
+|---------|----------|----------|
+| Новое сообщение | `new_message` | Создание сообщения в чате |
+| Сообщение изменено | `message_updated` | Редактирование существующего сообщения |
+| Сообщение удалено | `message_deleted` | Удаление сообщения |
+| Новая реакция | `new_reaction` | Добавление реакции к сообщению |
+| Реакция удалена | `reaction_deleted` | Удаление реакции с сообщения |
+| Участник добавлен | `chat_member_added` | Добавление участника в чат |
+| Участник удалён | `chat_member_removed` | Удаление участника из чата |
+
+### Интерактивные элементы
+
+| Событие | Значение | Описание |
+|---------|----------|----------|
+| Нажатие кнопки | `button_pressed` | Клик по Data-кнопке в сообщении |
+| Отправка формы | `form_submitted` | Отправка модальной формы |
+| Ссылка отправлена | `link_shared` | Бот может развернуть превью ссылки |
+
+### Сотрудники
+
+| Событие | Значение | Описание |
+|---------|----------|----------|
+| Приглашение сотрудника | `company_member_invite` | Отправлено приглашение новому сотруднику |
+| Подтверждение регистрации | `company_member_confirm` | Сотрудник подтвердил регистрацию |
+| Активация сотрудника | `company_member_activate` | Сотрудник активирован |
+| Обновление сотрудника | `company_member_update` | Изменение данных сотрудника |
+| Приостановка сотрудника | `company_member_suspend` | Сотрудник приостановлен |
+| Удаление сотрудника | `company_member_delete` | Сотрудник удалён |
+
+### Wildcard
+
+| Событие | Значение | Описание |
+|---------|----------|----------|
+| Все события | `*` | Получать все типы событий |
+
+
+
+Бот получает события только из чатов, в которых он состоит. Убедитесь, что бот добавлен в нужные чаты.
+
+## Настройка
+
+Добавьте узел **Pachca Trigger** в workflow — найдите его через поиск в панели узлов.
+
+
+
+### Автоматический режим (рекомендуется)
+
+
+
+При наличии **Bot ID** в [Credentials](/guides/n8n/setup#sozdanie-credentials) вебхук регистрируется автоматически:
+
+
+
+ Откройте Pachca API Credentials и заполните поле **Bot ID** — это ID вашего бота в Пачке.
+
+
+ Создайте новый workflow и добавьте узел **Pachca Trigger**. Выберите нужный тип события.
+
+
+ Нажмите **Activate**. n8n автоматически вызовет [Обновление бота](PUT /bots/{id}) и зарегистрирует webhook URL в настройках бота.
+
+
+
+При деактивации workflow вебхук автоматически удаляется.
+
+>Pachca: PUT /bots/{id}
webhook_url=n8n_url
+ Pachca->>Bot: Webhook зарегистрирован
+
+ Note over Bot: Событие в чате
+ Bot->>Pachca: Новое сообщение
+ Pachca->>n8n: POST webhook_url
+ payload + signature
+ Note over n8n: Проверка подписи
+ n8n->>n8n: Запуск workflow
+
+ Note over n8n: Деактивация workflow
+ n8n->>Pachca: PUT /bots/{id}
webhook_url=null
+ Pachca->>Bot: Webhook удалён`}
+/>
+
+### Ручной режим
+
+Если **Bot ID** не указан в Credentials:
+
+1. Добавьте узел **Pachca Trigger** в workflow
+2. Скопируйте сгенерированный **Webhook URL** из настроек узла
+3. Вставьте URL в настройки бота в Пачке (раздел **Webhook URL**)
+4. Активируйте workflow
+
+## Безопасность
+
+### Проверка подписи
+
+Для защиты от поддельных запросов добавьте **Signing Secret** бота в [Credentials](/guides/n8n/setup#sozdanie-credentials). Trigger автоматически проверяет HMAC-SHA256 подпись каждого входящего запроса через заголовок `pachca-signature` и отклоняет невалидные.
+
+Подробнее о механизме подписи — в разделе [Исходящие вебхуки](/guides/webhook#bezopasnost).
+
+Рекомендуется всегда использовать Signing Secret в продакшене для защиты от несанкционированных запросов.
+
+### Ограничение по IP
+
+Укажите **Webhook Allowed IPs** в [Credentials](/guides/n8n/setup#sozdanie-credentials) — через запятую список IP-адресов, с которых принимаются вебхуки. Пачка отправляет вебхуки с IP `37.200.70.177`.
+
+Если поле пустое — проверка IP отключена и запросы принимаются с любого адреса.
+
+Ограничение по IP — дополнительная мера. Заголовок `x-forwarded-for` может быть подменён, если n8n не стоит за доверенным reverse proxy. Используйте вместе с Signing Secret.
+
+### Защита от повторов
+
+Trigger автоматически отклоняет события старше **5 минут** (по полю `webhook_timestamp` в теле запроса). Это защищает от replay-атак — повторной отправки перехваченного запроса.
+
+## Фильтрация событий
+
+Выберите конкретный тип события для фильтрации — workflow будет запускаться только при совпадении. Можно выбрать только один тип события на один узел Trigger.
+
+Используйте **All Events** (`*`) и фильтруйте в последующих узлах (например, через IF или Switch), если нужна сложная логика маршрутизации по типу события или обработка нескольких типов в одном workflow.
+
+## Пример: бот-эхо
+
+
+
+Простой workflow, который отвечает на каждое новое сообщение:
+
+1. **Pachca Trigger** — событие `New Message`
+2. **IF** — условие: `message.user_id` не равен ID бота (чтобы бот не отвечал сам себе)
+3. **Pachca** — операция `Message > Create`, `entityId` = ID чата из триггера, `content` = текст ответа
+
+ID бота — это `user_id` из ответа [Информация о профиле](GET /profile) при авторизации токеном бота.
+
+Больше готовых сценариев — в разделе [Примеры workflow](/guides/n8n/workflows).
diff --git a/apps/docs/content/guides/n8n/troubleshooting.mdx b/apps/docs/content/guides/n8n/troubleshooting.mdx
new file mode 100644
index 00000000..85ab6057
--- /dev/null
+++ b/apps/docs/content/guides/n8n/troubleshooting.mdx
@@ -0,0 +1,140 @@
+---
+title: Устранение ошибок
+description: "Частые ошибки при работе с Пачкой в n8n: неверный токен, 403, 429, вебхук не приходит"
+---
+
+# Устранение ошибок
+
+## Ошибки авторизации
+
+### 401 Unauthorized — неверный токен
+
+
+
+**Причина:** указан некорректный или просроченный Access Token.
+
+**Решение:**
+
+1. Откройте **Credentials** и проверьте значение **Access Token**
+2. Убедитесь, что токен скопирован целиком, без лишних пробелов
+3. Нажмите **Test** — при ошибке создайте новый токен в Пачке
+4. Для бот-токена: **Настройки бота** → вкладка **API** → скопируйте токен
+5. Для персонального токена: **Автоматизации** → **Интеграции** → **API**
+
+### 403 Forbidden — недостаточно прав
+
+**Причина:** операция требует более высокий уровень доступа, чем предоставляет текущий токен.
+
+**Решение:**
+
+Доступ к операциям определяется [скоупами](/api/authorization#skoupy) токена, а не его типом. Убедитесь, что ваш токен включает нужные скоупы:
+
+| Операция | Требуемые скоупы |
+|----------|-----------------|
+| Управление сотрудниками (User > Create/Update/Delete) | `users:write` (доступен администраторам и владельцам) |
+| Журнал безопасности (Security > Get Many) | `audit_events:read` (доступен администраторам и владельцам) |
+| Управление тегами (Group Tag > Create/Update/Delete) | `group_tags:write` (доступен администраторам и владельцам) |
+| Отправка сообщений, чаты, задачи | `messages:write`, `chats:write`, `tasks:write` |
+
+Подробнее — в разделе [Авторизация](/api/authorization).
+
+---
+
+## Ошибки лимитов
+
+### 429 Too Many Requests — превышение лимита
+
+**Причина:** слишком много запросов за единицу времени.
+
+**Лимиты API:**
+
+| Тип операции | Лимит |
+|-------------|-------|
+| Отправка сообщений | ~4 запроса/сек на чат |
+| Остальные операции | ~50 запросов/сек |
+
+**Решение:**
+
+1. Добавьте узел **Wait** между операциями для замедления
+2. Используйте **Batching** в настройках узла Pachca (Additional Fields → Request Options → Batching)
+3. Для массовых операций используйте **Return All** = `false` с ограниченным **Limit**
+
+При получении 429 или 5xx расширение автоматически повторяет запросы с экспоненциальной задержкой и jitter (до 5 попыток). Учитывается заголовок `Retry-After` из ответа API.
+
+---
+
+## Ошибки триггера
+
+### Вебхук не приходит
+
+**Возможные причины и решения:**
+
+1. **Бот не добавлен в чат** — бот получает события только из чатов, в которых он состоит. Добавьте бота в нужный канал
+2. **Workflow не активирован** — нажмите **Activate** в правом верхнем углу. Неактивные workflow не принимают вебхуки
+3. **Bot ID не указан** — при отсутствии Bot ID в Credentials авторегистрация не работает. Укажите Bot ID или настройте вебхук вручную
+4. **n8n недоступен извне** — при локальной установке Пачка не может отправить вебхук на `localhost`. Используйте туннель (ngrok, Cloudflare Tunnel) или разверните n8n на сервере с публичным IP
+
+### Ошибка подписи (Signature Mismatch)
+
+**Причина:** Signing Secret в Credentials не совпадает с секретом бота в Пачке.
+
+**Решение:** скопируйте Signing Secret из настроек бота в Пачке (вкладка **API**) и вставьте в Credentials.
+
+---
+
+## Ошибки данных
+
+### Entity ID не найден
+
+**Причина:** указан несуществующий ID чата, пользователя или сообщения.
+
+**Решение:**
+
+- Для чатов: используйте **Search** (Search > Get Many Chats) для поиска по имени
+- Для пользователей: используйте **Search** (Search > Get Many Users) для поиска
+- Для сообщений: убедитесь, что сообщение не было удалено
+
+### Бот не может отправить сообщение
+
+**Причина:** бот не является участником целевого чата.
+
+**Решение:** добавьте бота в чат. Бот может отправлять сообщения только в те чаты, в которых он состоит.
+
+---
+
+## Ошибки форм
+
+### Форма не открывается
+
+**Причина:** `trigger_id` из события `button_pressed` действителен только **3 секунды**. Если между получением вебхука и вызовом [Открытие представления](POST /views/open) проходит больше времени — форма не откроется.
+
+**Решение:**
+
+1. Убедитесь, что узел **Form > Create** стоит сразу после триггера, без долгих операций между ними
+2. Не используйте узел **Wait** между получением `trigger_id` и открытием формы
+3. Если нужна дополнительная логика — выполняйте её после отправки формы, а не до
+
+Подробнее о формах — в [документации форм](/guides/forms/overview).
+
+---
+
+## Ошибки пагинации
+
+### Возвращаются не все данные
+
+**Причина:** API возвращает данные постранично. Если **Return All** выключен, вы получаете только первую страницу (по умолчанию до 50 записей).
+
+**Решение:**
+
+1. Включите **Return All** = `true` в настройках узла — n8n автоматически пройдёт по всем страницам через cursor-based пагинацию
+2. Если нужен ограниченный набор — используйте **Return All** = `false` и укажите нужное число в поле **Limit**
+3. Для больших объёмов данных учитывайте [лимиты API](#oshibki-limitov) — при Return All n8n делает несколько запросов последовательно
+
+---
+
+## Общие рекомендации
+
+1. **Включите Retry On Fail** в настройках узла (Settings → Retry On Fail) для автоматического повтора при временных ошибках
+2. **Используйте Error Trigger** для обработки ошибок в отдельном workflow — отправляйте уведомление об ошибке в специальный канал
+3. **Проверяйте Credentials** кнопкой **Test** перед запуском workflow
+4. **Используйте Execute Step** для отладки узлов по одному, не запуская весь workflow
diff --git a/apps/docs/content/guides/n8n/workflows.mdx b/apps/docs/content/guides/n8n/workflows.mdx
new file mode 100644
index 00000000..b7b06471
--- /dev/null
+++ b/apps/docs/content/guides/n8n/workflows.mdx
@@ -0,0 +1,193 @@
+---
+title: Примеры workflow
+description: "Готовые сценарии автоматизации Пачки в n8n: приветствие, пересылка, задачи, согласование, мониторинг, заявки на отпуск"
+---
+
+# Примеры workflow
+
+Ниже — готовые сценарии, которые можно воспроизвести в n8n. Каждый пример использует узлы **Pachca** и **Pachca Trigger** из расширения.
+
+## Приветствие нового сотрудника
+
+Автоматическое приветственное сообщение при добавлении сотрудника в канал.
+
+
+
+**Как работает:**
+
+1. **Pachca Trigger** — событие `New Message`
+2. **IF** — проверка: сообщение системное (тип `user_joined`)
+3. **Pachca** (Message > Create) — отправка приветствия в тот же чат
+
+**Что можно добавить:** отправка личного сообщения с полезными ссылками, добавление сотрудника в рабочие каналы.
+
+
+ Готовый workflow для импорта в n8n
+
+
+После импорта замените Credentials на свои во всех узлах Pachca.
+
+---
+
+## Пересылка сообщений между каналами
+
+Автоматическая пересылка сообщений из одного чата в другой.
+
+
+
+**Как работает:**
+
+1. **Pachca Trigger** — событие `New Message` (в исходном канале)
+2. **IF** — фильтрация: пропускать сервисные сообщения, пересылать только пользовательские
+3. **Pachca** (Message > Create) — отправка в целевой канал с указанием автора
+
+**Когда полезно:** дублирование важных новостей в общие каналы, агрегация обсуждений.
+
+
+ Готовый workflow для импорта в n8n
+
+
+После импорта замените Credentials, `CHAT_ID_ИСТОЧНИКА` и `CHAT_ID_ЦЕЛЕВОГО` на свои значения.
+
+---
+
+## Напоминание о задачах
+
+Ежедневная проверка просроченных задач с уведомлением в чат.
+
+
+
+**Как работает:**
+
+1. **Schedule Trigger** — ежедневный запуск (например, в 10:00)
+2. **Pachca** (Task > Get Many) — получение списка задач
+3. **IF** — фильтр: только просроченные (дедлайн < сегодня)
+4. **Pachca** (Message > Create) — уведомление в чат со списком задач
+
+**Что можно добавить:** личные уведомления ответственным, группировка по проектам.
+
+
+ Готовый workflow для импорта в n8n
+
+
+После импорта замените Credentials и `CHAT_ID` во всех узлах Pachca.
+
+---
+
+## Согласование с кнопками
+
+Запрос на согласование через сообщение с кнопками и обработка ответа.
+
+
+
+**Как работает:**
+
+1. **Manual Trigger** или **Webhook** — инициация запроса
+2. **Pachca** (Message > Create) — отправка сообщения с кнопками «Согласовать» / «Отклонить»
+3. **Pachca Trigger** (в отдельном workflow) — событие `Button Pressed`
+4. **Switch** — маршрутизация по `data` из нажатой кнопки
+5. **Pachca** (Message > Update) — обновление исходного сообщения с результатом
+
+**Пример кнопок:**
+
+```json
+[
+ [
+ { "text": "Согласовать", "data": "approve" },
+ { "text": "Отклонить", "data": "reject" }
+ ]
+]
+```
+
+Подробнее о кнопках — в разделе [Продвинутые функции](/guides/n8n/advanced#knopki-v-soobshheniyax).
+
+
+
+ Отправка запроса с кнопками
+
+
+ Обработка нажатий кнопок
+
+
+
+После импорта замените Credentials и `CHAT_ID` во всех узлах Pachca.
+
+---
+
+## Мониторинг и алерты
+
+Периодическая проверка состояния и отправка алерта при аномалиях.
+
+
+
+**Как работает:**
+
+1. **Schedule Trigger** — проверка каждые 5 минут
+2. **HTTP Request** — запрос к внешнему API или сервису
+3. **IF** — проверка: статус не 200 или метрика выше порога
+4. **Pachca** (Message > Create) — отправка алерта в канал мониторинга
+
+**Что можно добавить:** проверка нескольких сервисов, графики через разворачивание ссылок, эскалация в личные сообщения.
+
+
+ Готовый workflow для импорта в n8n
+
+
+После импорта замените Credentials, `CHAT_ID_МОНИТОРИНГА` и URL сервиса во всех узлах Pachca.
+
+---
+
+## Заявка на отпуск
+
+Полноценный сценарий с кнопками, формой и согласованием в треде — бот принимает заявку через модальную форму и отправляет на согласование руководителю.
+
+
+
+**Как работает (два workflow):**
+
+**Workflow 1 — Приём заявки:**
+
+1. **Pachca Trigger** — событие `New Message`, фильтр по команде `/отпуск`
+2. **Pachca** (Message > Create) — отправка сообщения с Data-кнопкой «Создать заявку»
+3. **Pachca Trigger** (событие `Button Pressed`) — пользователь нажимает кнопку
+4. **Pachca** (Form > Create) — открытие модальной формы с полями «Дата начала», «Дата окончания», «Комментарий»
+
+**Workflow 2 — Обработка и согласование:**
+
+1. **Pachca Trigger** — событие `Form Submitted`
+2. **Pachca** (Thread > Create) — создание треда с деталями заявки
+3. **Pachca** (Message > Create) — отправка в тред кнопок «Согласовать» / «Отклонить»
+4. **Pachca Trigger** (событие `Button Pressed`) — руководитель нажимает кнопку
+5. **Pachca** (Message > Create) — уведомление сотрудника о результате
+
+**Что задействовано:** триггер, кнопки, формы, треды, условная логика.
+
+Подробнее о кнопках — в разделе [Кнопки в сообщениях](/guides/n8n/advanced#knopki-v-soobshheniyax), о формах — в разделе [Формы](/guides/n8n/advanced#formy).
+
+
+
+ Приём команды и кнопка заявки
+
+
+ Форма, тред и согласование
+
+
+
+После импорта замените Credentials и `CHAT_ID_HR` во всех узлах Pachca.
+
+---
+
+## AI-ассистент
+
+Бот, использующий AI для ответов на вопросы на основе истории чата.
+
+
+
+**Как работает:**
+
+1. **Pachca Trigger** — событие `New Message`
+2. **AI Agent** — обработка запроса с LLM
+3. **Pachca** (Search > Get Many Messages) — как Tool для поиска информации
+4. **Pachca** (Message > Create) — как Tool для отправки ответа
+
+Для использования AI Agent необходимо настроить LLM-провайдер (OpenAI, Anthropic и др.) в n8n. Подробнее — в разделе [Продвинутые функции](/guides/n8n/advanced#ai-agent).
diff --git a/apps/docs/content/guides/sdk/csharp.mdx b/apps/docs/content/guides/sdk/csharp.mdx
index 0bf2158e..cc148ed8 100644
--- a/apps/docs/content/guides/sdk/csharp.mdx
+++ b/apps/docs/content/guides/sdk/csharp.mdx
@@ -1,9 +1,9 @@
---
-title: CSharp
+title: "C#"
description: Типизированный клиент для Pachca API на C# с async/await и автопагинацией
---
-# CSharp
+# C#
NuGet
@@ -84,7 +84,7 @@ using var client = new PachcaClient("YOUR_TOKEN", "https://custom-api.example.co
## Пагинация
-Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит поле `Meta.Paginate.NextPage` — курсор для следующей страницы.
+Методы, возвращающие списки, используют cursor-based пагинацию. Ответ всегда содержит поле `Meta.Paginate.NextPage` — курсор для следующей страницы. Курсор никогда не бывает `null` — конец данных определяется по пустому массиву `Data`.
### Ручная пагинация
@@ -92,12 +92,13 @@ using var client = new PachcaClient("YOUR_TOKEN", "https://custom-api.example.co
var chats = new List();
string? cursor = null;
-do
+while (true)
{
var response = await client.Chats.ListChatsAsync(cursor: cursor);
+ if (response.Data.Count == 0) break;
chats.AddRange(response.Data);
- cursor = response.Meta?.Paginate?.NextPage;
-} while (cursor != null);
+ cursor = response.Meta.Paginate.NextPage;
+}
```
### Автопагинация
@@ -182,11 +183,11 @@ catch (OAuthError e)
## Повторные запросы
-SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx`:
+SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx` (`500`, `502`, `503`, `504`):
- До **3 повторов** на каждый запрос
-- Если сервер вернул заголовок `Retry-After` — ждёт указанное время
-- Иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **429:** если сервер вернул заголовок `Retry-After` — ждёт указанное время, иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **5xx:** экспоненциальный backoff с jitter: ~10 сек, ~20 сек, ~40 сек
- Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов
## Отмена запросов
diff --git a/apps/docs/content/guides/sdk/go.mdx b/apps/docs/content/guides/sdk/go.mdx
index eaeb3fae..b79ff34f 100644
--- a/apps/docs/content/guides/sdk/go.mdx
+++ b/apps/docs/content/guides/sdk/go.mdx
@@ -104,7 +104,7 @@ request := pachca.ChatUpdateRequest{
## Пагинация
-Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит `Meta.Paginate.NextPage` — курсор для следующей страницы.
+Методы, возвращающие списки, используют cursor-based пагинацию. Ответ всегда содержит `Meta.Paginate.NextPage` — курсор для следующей страницы. Курсор никогда не бывает пустым — конец данных определяется по пустому слайсу `Data`.
### Ручная пагинация
@@ -116,13 +116,14 @@ for {
if err != nil {
log.Fatal(err)
}
+ if len(response.Data) == 0 {
+ break
+ }
for _, user := range response.Data {
fmt.Println(user.FirstName, user.LastName)
}
- if response.Meta == nil || response.Meta.Paginate == nil || response.Meta.Paginate.NextPage == nil {
- break
- }
- cursor = response.Meta.Paginate.NextPage
+ nextPage := response.Meta.Paginate.NextPage
+ cursor = &nextPage
}
```
@@ -205,12 +206,13 @@ if err != nil {
## Повторные запросы
-SDK автоматически повторяет запрос при получении `429 Too Many Requests`:
+SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx` (`500`, `502`, `503`, `504`):
- До **3 повторов** на каждый запрос
-- Если сервер вернул заголовок `Retry-After` — ждёт указанное время
-- Иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **429:** если сервер вернул заголовок `Retry-After` — ждёт указанное время, иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **5xx:** экспоненциальный backoff с jitter: ~10 сек, ~20 сек, ~40 сек
- Тело запроса пересоздаётся через `req.GetBody()` при каждой попытке
+- Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов
## Типы
diff --git a/apps/docs/content/guides/sdk/kotlin.mdx b/apps/docs/content/guides/sdk/kotlin.mdx
index a67cb022..403c0e84 100644
--- a/apps/docs/content/guides/sdk/kotlin.mdx
+++ b/apps/docs/content/guides/sdk/kotlin.mdx
@@ -20,7 +20,7 @@ description: Типизированный клиент для Pachca API на Ko
```kotlin
dependencies {
- implementation("com.pachca:pachca-sdk:1.0.1")
+ implementation("com.pachca:pachca-sdk:latest.release")
}
```
@@ -100,19 +100,20 @@ client.close()
## Пагинация
-Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит `meta?.paginate?.nextPage` — курсор для следующей страницы.
+Методы, возвращающие списки, используют cursor-based пагинацию. Ответ всегда содержит `meta.paginate.nextPage` — курсор для следующей страницы. Курсор никогда не бывает `null` — конец данных определяется по пустому массиву `data`.
### Ручная пагинация
```kotlin
var cursor: String? = null
-do {
+while (true) {
val response = client.users.listUsers(limit = 50, cursor = cursor)
+ if (response.data.isEmpty()) break
for (user in response.data) {
println("${user.firstName} ${user.lastName}")
}
- cursor = response.meta?.paginate?.nextPage
-} while (cursor != null)
+ cursor = response.meta.paginate.nextPage
+}
```
### Автопагинация
@@ -191,12 +192,13 @@ try {
## Повторные запросы
-SDK автоматически повторяет запрос при получении `429 Too Many Requests` или серверной ошибки (`5xx`):
+SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx` (`500`, `502`, `503`, `504`):
- До **3 повторов** на каждый запрос
-- Если сервер вернул заголовок `Retry-After` — ждёт указанное время
-- Иначе — линейный backoff: 1 сек, 2 сек, 3 сек
+- **429:** если сервер вернул заголовок `Retry-After` — ждёт указанное время, иначе — экспоненциальный backoff с jitter
+- **5xx:** экспоненциальный backoff с jitter: ~10 сек, ~20 сек, ~40 сек
- Реализовано через плагин Ktor `HttpRequestRetry`
+- Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов
## Типы
diff --git a/apps/docs/content/guides/sdk/overview.mdx b/apps/docs/content/guides/sdk/overview.mdx
index 9721819e..d2301ca2 100644
--- a/apps/docs/content/guides/sdk/overview.mdx
+++ b/apps/docs/content/guides/sdk/overview.mdx
@@ -20,7 +20,7 @@ description: Типизированные клиентские библиоте
go get · sync · net/http
JitPack · coroutines · Ktor
SPM · async throws · URLSession
- NuGet · async/await · HttpClient
+ NuGet · async/await · HttpClient
Автоматически обновляются при каждом обновлении спецификации. [Исходный код на GitHub](https://github.com/pachca/openapi/tree/main/sdk).
@@ -58,7 +58,7 @@ npx @pachca/generator --spec https://example.com/openapi.yaml --output ./generat
|-------------|----------|
| **16 сервисов** | Типизированные методы для каждого API-эндпоинта |
| **Автопагинация** | Методы `*All()` для автоматического обхода всех страниц |
-| **Повторные запросы** | Автоматический retry при `429` с экспоненциальным backoff |
+| **Повторные запросы** | Автоматический retry при `429` и `5xx` с экспоненциальным backoff |
| **Обработка ошибок** | Типизированные `ApiError` и `OAuthError` |
| **Сериализация** | Автоматическая конвертация между форматами (snake_case ↔ camelCase) |
| **Авторизация** | Bearer-токен передаётся один раз при создании клиента |
diff --git a/apps/docs/content/guides/sdk/python.mdx b/apps/docs/content/guides/sdk/python.mdx
index 87ca9e7d..83af47a7 100644
--- a/apps/docs/content/guides/sdk/python.mdx
+++ b/apps/docs/content/guides/sdk/python.mdx
@@ -90,7 +90,7 @@ await client.close()
## Пагинация
-Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит `meta.paginate.next_page` — курсор для следующей страницы.
+Методы, возвращающие списки, используют cursor-based пагинацию. Ответ всегда содержит `meta.paginate.next_page` — курсор для следующей страницы. Курсор никогда не бывает `None` — конец данных определяется по пустому списку `data`.
### Ручная пагинация
@@ -100,10 +100,10 @@ from pachca.models import ListUsersParams
cursor = None
while True:
response = await client.users.list_users(ListUsersParams(limit=50, cursor=cursor))
+ if not response.data:
+ break
for user in response.data:
print(user.first_name, user.last_name)
- if not response.meta or not response.meta.paginate or not response.meta.paginate.next_page:
- break
cursor = response.meta.paginate.next_page
```
@@ -180,12 +180,13 @@ except OAuthError as error:
## Повторные запросы
-SDK автоматически повторяет запрос при получении `429 Too Many Requests`:
+SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx` (`500`, `502`, `503`, `504`):
- До **3 повторов** на каждый запрос
-- Если сервер вернул заголовок `Retry-After` — ждёт указанное время
-- Иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **429:** если сервер вернул заголовок `Retry-After` — ждёт указанное время, иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **5xx:** экспоненциальный backoff с jitter: ~10 сек, ~20 сек, ~40 сек
- Реализовано через `RetryTransport` — обёртку над httpx-транспортом
+- Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов
## Типы
diff --git a/apps/docs/content/guides/sdk/swift.mdx b/apps/docs/content/guides/sdk/swift.mdx
index 7256b31a..b3ff6c52 100644
--- a/apps/docs/content/guides/sdk/swift.mdx
+++ b/apps/docs/content/guides/sdk/swift.mdx
@@ -20,7 +20,7 @@ description: Типизированный клиент для Pachca API на Sw
```swift
dependencies: [
- .package(url: "https://github.com/pachca/openapi", from: "1.0.1")
+ .package(url: "https://github.com/pachca/openapi", from: "1.0.0")
]
```
@@ -90,7 +90,7 @@ let client = PachcaClient(token: "YOUR_TOKEN", baseURL: "https://custom-api.exam
## Пагинация
-Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит `meta?.paginate?.nextPage` — курсор для следующей страницы.
+Методы, возвращающие списки, используют cursor-based пагинацию. Ответ всегда содержит `meta.paginate.nextPage` — курсор для следующей страницы. Курсор никогда не бывает `nil` — конец данных определяется по пустому массиву `data`.
### Ручная пагинация
@@ -98,11 +98,12 @@ let client = PachcaClient(token: "YOUR_TOKEN", baseURL: "https://custom-api.exam
var cursor: String? = nil
repeat {
let response = try await client.users.listUsers(limit: 50, cursor: cursor)
+ if response.data.isEmpty { break }
for user in response.data {
print("\(user.firstName) \(user.lastName)")
}
- cursor = response.meta?.paginate?.nextPage
-} while cursor != nil
+ cursor = response.meta.paginate.nextPage
+} while true
```
### Автопагинация
@@ -177,12 +178,13 @@ do {
## Повторные запросы
-SDK автоматически повторяет запрос при получении `429 Too Many Requests`:
+SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx` (`500`, `502`, `503`, `504`):
- До **3 повторов** на каждый запрос
-- Если сервер вернул заголовок `Retry-After` — ждёт указанное время
-- Иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **429:** если сервер вернул заголовок `Retry-After` — ждёт указанное время, иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **5xx:** экспоненциальный backoff с jitter: ~10 сек, ~20 сек, ~40 сек
- Ожидание через `Task.sleep(nanoseconds:)` — не блокирует поток
+- Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов
## Типы
diff --git a/apps/docs/content/guides/sdk/typescript.mdx b/apps/docs/content/guides/sdk/typescript.mdx
index 8376af4f..25a67a0a 100644
--- a/apps/docs/content/guides/sdk/typescript.mdx
+++ b/apps/docs/content/guides/sdk/typescript.mdx
@@ -84,20 +84,21 @@ const client = new PachcaClient("YOUR_TOKEN", "https://custom-api.example.com/ap
## Пагинация
-Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит поле `meta.paginate.nextPage` — курсор для следующей страницы.
+Методы, возвращающие списки, используют cursor-based пагинацию. Ответ всегда содержит поле `meta.paginate.nextPage` — курсор для следующей страницы. Курсор никогда не бывает `null` — конец данных определяется по пустому массиву `data`.
### Ручная пагинация
```typescript
let cursor: string | undefined
-do {
+for (;;) {
const response = await client.users.listUsers({ limit: 50, cursor })
+ if (response.data.length === 0) break
for (const user of response.data) {
console.log(user.firstName, user.lastName)
}
- cursor = response.meta?.paginate?.nextPage
-} while (cursor)
+ cursor = response.meta.paginate.nextPage
+}
```
### Автопагинация
@@ -179,12 +180,12 @@ try {
## Повторные запросы
-SDK автоматически повторяет запрос при получении `429 Too Many Requests`:
+SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx` (`500`, `502`, `503`, `504`):
- До **3 повторов** на каждый запрос
-- Если сервер вернул заголовок `Retry-After` — ждёт указанное время
-- Иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
-- Все остальные ошибки возвращаются сразу без retry
+- **429:** если сервер вернул заголовок `Retry-After` — ждёт указанное время, иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **5xx:** экспоненциальный backoff с jitter: ~10 сек, ~20 сек, ~40 сек
+- Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов
## Сериализация
diff --git a/apps/docs/content/guides/webhook.mdx b/apps/docs/content/guides/webhook.mdx
index 0100ccb4..4de717fc 100644
--- a/apps/docs/content/guides/webhook.mdx
+++ b/apps/docs/content/guides/webhook.mdx
@@ -58,6 +58,12 @@ description: Получение уведомлений о событиях в р
+### Заполнение формы
+
+Вебхук отправляется при отправке пользователем заполненной формы ([представления](/guides/forms/overview)). Подробнее об обработке результатов — в разделе [Обработка форм](/guides/forms/handling).
+
+
+
### Изменение участников чатов
Вебхук отправляется при изменении состава участников чатов, где состоит бот, и в тредах этих чатов.
diff --git a/apps/docs/content/home.mdx b/apps/docs/content/home.mdx
index e3ad8f94..2c23282c 100644
--- a/apps/docs/content/home.mdx
+++ b/apps/docs/content/home.mdx
@@ -15,6 +15,7 @@ hideTableOfContents: true
+
@@ -51,7 +52,7 @@ hideTableOfContents: true
Подключайте Пачку к внешним сервисам без написания кода.
-
+
Визуальные автоматизации
diff --git a/apps/docs/content/updates.mdx b/apps/docs/content/updates.mdx
index 3b9139f2..d97bc8e7 100644
--- a/apps/docs/content/updates.mdx
+++ b/apps/docs/content/updates.mdx
@@ -9,11 +9,37 @@ useUpdatesComponent: true
Автоматически отслеживайте обновления: подпишитесь на [RSS-ленту](/feed.xml) или используйте [markdown-версию этой страницы](/updates.md) для интеграции с инструментами и AI-агентами.
+
+
+## Аватары, сортировка и n8n Node v2
+
+Были добавлены новые методы для управления аватарами:
+
+- [Загрузка аватара](PUT /profile/avatar)
+- [Удаление аватара](DELETE /profile/avatar)
+- [Загрузка аватара сотрудника](PUT /users/{user_id}/avatar)
+- [Удаление аватара сотрудника](DELETE /users/{user_id}/avatar)
+
+С помощью этих методов вы можете загружать и удалять аватары для своего профиля и для сотрудников вашей компании.
+
+В методах [Список чатов](GET /chats) и [Список сообщений чата](GET /messages) добавлен новый формат параметров сортировки: `sort` (поле сортировки) и `order` (направление: `asc` или `desc`).
+
+Расширение [n8n](/guides/n8n/overview) для Пачки обновлено до версии 2.0.0. Нода автоматически генерируется из OpenAPI-спецификации и всегда синхронизирована с актуальным API.
+
+- **18 ресурсов и 60+ операций** — полное покрытие API, включая задачи, поиск, экспорт чатов и журнал безопасности
+- **[Pachca Trigger](/guides/n8n/trigger)** — webhook-нода с авторегистрацией вебхука и 16 типами событий
+- **Курсорная автопагинация** — Return All / Limit вместо ручного per/page
+- **[Simplify](/guides/n8n/resources#simplify)** — переключатель для получения только ключевых полей
+- **AI Tool Use** — использование узлов как инструментов AI Agent
+- **Полная [обратная совместимость](/guides/n8n/migration)** — все существующие workflow на v1 продолжают работать без изменений
+
+В [CLI](/guides/cli) добавлены команды для управления аватарами и обновлены параметры сортировки (`--sort` и `--order`). Все 6 SDK обновлены: [TypeScript](/guides/sdk/typescript), [Python](/guides/sdk/python), [Go](/guides/sdk/go), [Kotlin](/guides/sdk/kotlin), [Swift](/guides/sdk/swift) и [C#](/guides/sdk/csharp).
+
## C# SDK
-Добавлен официальный [C# SDK](/guides/sdk/csharp) для .NET 8+. Полная поддержка async/await, CancellationToken, автопагинация через `*AllAsync()` методы и автоматические повторы при `429`. Установка: `dotnet add package Pachca.Sdk`.
+Добавлен официальный [C# SDK](/guides/sdk/csharp) для .NET 8+. Типизированный клиент с поддержкой async/await, автопагинацией и автоматическими повторными запросами при ошибках сервера. Все примеры кода в документации API теперь доступны на C#.
@@ -96,7 +122,7 @@ useUpdatesComponent: true
- [Кнопки](/guides/buttons) — интерактивные кнопки в сообщениях
- [Входящие вебхуки](/guides/incoming-webhooks) — отправка сообщений через URL
- [Разворачивание ссылок](/guides/link-previews) — unfurl-боты для предпросмотра ссылок
-- [Albato](/guides/albato) и [n8n](/guides/n8n) — интеграции с no-code платформами
+- [Albato](/guides/albato) и [n8n](/guides/n8n/overview) — интеграции с no-code платформами
Документация реструктурирована: [Быстрый старт](/guides/quickstart), [Пагинация](/api/pagination), [Загрузка файлов](/api/file-uploads), [Запросы и ответы](/api/requests-responses) вынесены в отдельные страницы, раздел форм разбит на три страницы.
diff --git a/apps/docs/lib/navigation.ts b/apps/docs/lib/navigation.ts
index d698c043..0b814842 100644
--- a/apps/docs/lib/navigation.ts
+++ b/apps/docs/lib/navigation.ts
@@ -156,9 +156,11 @@ function flattenItems(sections: NavigationSection[]): NavigationItem[] {
for (const section of sections) {
for (const item of section.items) {
if (item.children) {
- result.push(...item.children);
+ for (const child of item.children) {
+ result.push({ ...child, sectionTitle: child.sectionTitle || item.title });
+ }
} else {
- result.push(item);
+ result.push({ ...item, sectionTitle: item.sectionTitle || section.title });
}
}
}
diff --git a/apps/docs/lib/openapi/mapper.ts b/apps/docs/lib/openapi/mapper.ts
index 1e11917d..68e667d2 100644
--- a/apps/docs/lib/openapi/mapper.ts
+++ b/apps/docs/lib/openapi/mapper.ts
@@ -1,5 +1,5 @@
import type { Endpoint } from './types';
-import { toSlug } from '@/lib/utils/transliterate';
+import { toSlug } from '../utils/transliterate';
// Overrides for auto-generated command URLs that produce ugly names.
// Key: "METHOD /path", value: "/section/action" for the CLI URL.
diff --git a/apps/docs/lib/openapi/resolve-links.ts b/apps/docs/lib/openapi/resolve-links.ts
index 116cb1b4..70697d75 100644
--- a/apps/docs/lib/openapi/resolve-links.ts
+++ b/apps/docs/lib/openapi/resolve-links.ts
@@ -20,7 +20,13 @@ export function resolveEndpointLinks(
if (endpoint) {
const url = generateUrlFromOperation(endpoint);
if (mdx) {
- return `${description.trim()}`;
+ const req = endpoint.requirements;
+ const attrs: string[] = [`method="${method}"`, `href="${url}"`];
+ if (req?.scope) attrs.push(`scope="${req.scope}"`);
+ if (req?.scopeRoles) attrs.push(`scopeRoles="${req.scopeRoles.join(',')}"`);
+ if (req?.plan) attrs.push(`plan="${req.plan}"`);
+ if (req?.auth === false) attrs.push(`noAuth`);
+ return `${description.trim()}`;
}
return `[${description.trim()}](${method}:${url})`;
}
diff --git a/apps/docs/lib/openapi/types.ts b/apps/docs/lib/openapi/types.ts
index 454b3b03..9b0d87fe 100644
--- a/apps/docs/lib/openapi/types.ts
+++ b/apps/docs/lib/openapi/types.ts
@@ -153,6 +153,8 @@ export interface NavigationItem {
children?: NavigationItem[];
/** Original (untranslated) title shown next to the translated one */
originalTitle?: string;
+ /** Parent section/group title for context (e.g. "SDK" for child page "Обзор") */
+ sectionTitle?: string;
/** External link (opens in new tab) */
external?: boolean;
}
diff --git a/apps/docs/lib/ordered-pages.ts b/apps/docs/lib/ordered-pages.ts
index be49b6e0..63555098 100644
--- a/apps/docs/lib/ordered-pages.ts
+++ b/apps/docs/lib/ordered-pages.ts
@@ -12,8 +12,16 @@ import { getGuideData } from './content-loader';
* Get all ordered guide pages (for generators/sitemap).
* Returns pages from all guide sections + API guide pages + home.
*/
-export function getOrderedPages(): { path: string; title: string; description: string }[] {
- const pages: { path: string; title: string; description: string }[] = [];
+export interface OrderedPage {
+ path: string;
+ title: string;
+ description: string;
+ /** Parent section/group title (e.g. "SDK" for child page "Обзор") */
+ sectionTitle?: string;
+}
+
+export function getOrderedPages(): OrderedPage[] {
+ const pages: OrderedPage[] = [];
// Home page
const homeData = getGuideData('home');
@@ -30,7 +38,7 @@ export function getOrderedPages(): { path: string; title: string; description: s
for (const item of section.items) {
if (item.children) {
for (const child of item.children) {
- addPage(pages, child, '/guides/');
+ addPage(pages, child, '/guides/', item.title);
}
} else {
addPage(pages, item, '/guides/');
@@ -59,6 +67,7 @@ export function getOrderedPages(): { path: string; title: string; description: s
path: item.path,
title: data.frontmatter.title || item.title,
description: data.frontmatter.description || '',
+ sectionTitle: 'Основы API',
});
} else {
// Dynamic pages without MDX (e.g. /api/models)
@@ -66,6 +75,7 @@ export function getOrderedPages(): { path: string; title: string; description: s
path: item.path,
title: item.title,
description: '',
+ sectionTitle: 'Основы API',
});
}
}
@@ -74,9 +84,10 @@ export function getOrderedPages(): { path: string; title: string; description: s
}
function addPage(
- pages: { path: string; title: string; description: string }[],
+ pages: OrderedPage[],
item: SidebarPageItem,
- prefix: string
+ prefix: string,
+ sectionTitle?: string
) {
const data = getGuideData(item.path.replace(prefix, ''));
if (data) {
@@ -84,6 +95,7 @@ function addPage(
path: item.path,
title: data.frontmatter.title || item.title,
description: data.frontmatter.description || '',
+ sectionTitle,
});
}
}
diff --git a/apps/docs/lib/schemas/guides/ViewSubmitWebhookPayload.json b/apps/docs/lib/schemas/guides/ViewSubmitWebhookPayload.json
deleted file mode 100644
index ca3dee1e..00000000
--- a/apps/docs/lib/schemas/guides/ViewSubmitWebhookPayload.json
+++ /dev/null
@@ -1,50 +0,0 @@
-{
- "title": "Структура исходящего вебхука о заполнении формы",
- "schema": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "description": "Тип объекта (для представлений всегда view)",
- "enum": ["view"],
- "x-enum-descriptions": { "view": "представление" }
- },
- "event": {
- "type": "string",
- "description": "Тип события (для отправки пользователем формы всегда submit)",
- "enum": ["submit"],
- "x-enum-descriptions": { "submit": "отправка формы" }
- },
- "private_metadata": {
- "type": "string",
- "description": "Строка, заданная при отправке представления",
- "example": "{\"order_id\": 123}"
- },
- "callback_id": {
- "type": "string",
- "description": "Идентификатор для распознавания этого представления, заданный при отправке представления",
- "example": "feedback_form"
- },
- "user_id": {
- "type": "integer",
- "description": "Идентификатор пользователя, который заполнил форму",
- "example": 456
- },
- "data": {
- "type": "object",
- "description": "JSON карта заполненных полей представления, где каждый ключ - значение поля name.",
- "properties": {
- "name": {
- "type": ["string", "array of strings", "array of objects", "null", "[]"],
- "description": "Значение, которое указал пользователь в поле (или массив значений, если это был множественный выбор или загруженные пользователем файлы). Если пользователь не указал значение, тогда null (или пустой массив, если это поле файлов или чекбоксов)"
- }
- }
- },
- "webhook_timestamp": {
- "type": "integer",
- "description": "Дата и время отправки вебхука (UTC+0) в формате UNIX",
- "example": 1705312200
- }
- }
- }
-}
diff --git a/apps/docs/lib/tabs-config.ts b/apps/docs/lib/tabs-config.ts
index e56c2808..bfebaf65 100644
--- a/apps/docs/lib/tabs-config.ts
+++ b/apps/docs/lib/tabs-config.ts
@@ -65,7 +65,7 @@ export const GUIDE_SECTIONS: SidebarSection[] = [
{ title: 'Go', path: '/guides/sdk/go' },
{ title: 'Kotlin', path: '/guides/sdk/kotlin' },
{ title: 'Swift', path: '/guides/sdk/swift' },
- { title: 'CSharp', path: '/guides/sdk/csharp' },
+ { title: 'C#', path: '/guides/sdk/csharp' },
],
},
{ title: 'Сценарии', path: '/guides/workflows' },
@@ -101,7 +101,20 @@ export const GUIDE_SECTIONS: SidebarSection[] = [
{
title: 'No-code интеграции',
items: [
- { title: 'n8n', path: '/guides/n8n' },
+ {
+ title: 'n8n',
+ path: '/guides/n8n/overview',
+ children: [
+ { title: 'Обзор', path: '/guides/n8n/overview' },
+ { title: 'Начало работы', path: '/guides/n8n/setup' },
+ { title: 'Ресурсы и операции', path: '/guides/n8n/resources' },
+ { title: 'Триггер', path: '/guides/n8n/trigger' },
+ { title: 'Примеры workflow', path: '/guides/n8n/workflows' },
+ { title: 'Продвинутые функции', path: '/guides/n8n/advanced' },
+ { title: 'Устранение ошибок', path: '/guides/n8n/troubleshooting' },
+ { title: 'Миграция с v1', path: '/guides/n8n/migration' },
+ ],
+ },
{ title: 'Albato', path: '/guides/albato' },
],
},
diff --git a/apps/docs/next.config.ts b/apps/docs/next.config.ts
index 273b0f71..a6ecc0fb 100644
--- a/apps/docs/next.config.ts
+++ b/apps/docs/next.config.ts
@@ -20,6 +20,9 @@ const nextConfig: NextConfig = {
pageExtensions: ['js', 'jsx', 'mdx', 'ts', 'tsx'],
// Keep flexsearch as external to avoid Turbopack worker_threads bundling issues
serverExternalPackages: ['flexsearch'],
+ turbopack: {
+ root: '../../',
+ },
async headers() {
return [
{
diff --git a/apps/docs/public/.well-known/skills/pachca-profile/SKILL.md b/apps/docs/public/.well-known/skills/pachca-profile/SKILL.md
index 8a4df339..a5fd95df 100644
--- a/apps/docs/public/.well-known/skills/pachca-profile/SKILL.md
+++ b/apps/docs/public/.well-known/skills/pachca-profile/SKILL.md
@@ -102,6 +102,23 @@ Help: `npx @pachca/cli --help` | Workflows: `npx @pachca/cli guide`
> Кастомные поля настраиваются администратором пространства.
+### Загрузить аватар профиля
+
+1. Загрузи аватар из файла:
+ ```bash
+ pachca profile update-avatar --file=<путь_к_файлу>
+ ```
+ > Файл изображения передается в формате multipart/form-data
+
+
+### Удалить аватар профиля
+
+1. Удали аватар:
+ ```bash
+ pachca profile delete-avatar --force
+ ```
+
+
## Limitations
- Rate limit: ~50 req/sec. On 429 — wait and retry.
@@ -115,6 +132,8 @@ Help: `npx @pachca/cli --help` | Workflows: `npx @pachca/cli guide`
| GET | /custom_properties | Список дополнительных полей |
| GET | /oauth/token/info | Информация о токене |
| GET | /profile | Информация о профиле |
+| PUT | /profile/avatar | Загрузка аватара |
+| DELETE | /profile/avatar | Удаление аватара |
| GET | /profile/status | Текущий статус |
| PUT | /profile/status | Новый статус |
| DELETE | /profile/status | Удаление статуса |
diff --git a/apps/docs/public/.well-known/skills/pachca-users/SKILL.md b/apps/docs/public/.well-known/skills/pachca-users/SKILL.md
index 8d28d6b5..b11fc1ae 100644
--- a/apps/docs/public/.well-known/skills/pachca-users/SKILL.md
+++ b/apps/docs/public/.well-known/skills/pachca-users/SKILL.md
@@ -158,6 +158,24 @@ Help: `npx @pachca/cli --help` | Workflows: `npx @pachca/cli guide`
```
+### Загрузить аватар сотрудника
+
+1. Загрузи аватар сотруднику:
+ ```bash
+ pachca users update-avatar --file=<путь_к_файлу>
+ ```
+ > Требует прав администратора. Файл передается в формате multipart/form-data
+
+
+### Удалить аватар сотрудника
+
+1. Удали аватар сотрудника:
+ ```bash
+ pachca users remove-avatar --force
+ ```
+ > Требует прав администратора
+
+
## Limitations
- Rate limit: ~50 req/sec. On 429 — wait and retry.
@@ -181,6 +199,8 @@ Help: `npx @pachca/cli --help` | Workflows: `npx @pachca/cli guide`
| GET | /users/{id} | Информация о сотруднике |
| PUT | /users/{id} | Редактирование сотрудника |
| DELETE | /users/{id} | Удаление сотрудника |
+| PUT | /users/{user_id}/avatar | Загрузка аватара сотрудника |
+| DELETE | /users/{user_id}/avatar | Удаление аватара сотрудника |
| GET | /users/{user_id}/status | Статус сотрудника |
| PUT | /users/{user_id}/status | Новый статус сотрудника |
| DELETE | /users/{user_id}/status | Удаление статуса сотрудника |
diff --git a/apps/docs/public/api/authorization.md b/apps/docs/public/api/authorization.md
index 1c4c7a2c..14aff33b 100644
--- a/apps/docs/public/api/authorization.md
+++ b/apps/docs/public/api/authorization.md
@@ -117,8 +117,10 @@ Authorization: Bearer
| `profile:read` | Просмотр информации о своем профиле | Все |
| `profile_status:read` | Просмотр статуса профиля | Все |
| `profile_status:write` | Изменение и удаление статуса профиля | Все |
+| `profile_avatar:write` | Изменение и удаление аватара профиля | Все |
| `user_status:read` | Просмотр статуса сотрудника | Владелец, Администратор |
| `user_status:write` | Изменение и удаление статуса сотрудника | Владелец, Администратор |
+| `user_avatar:write` | Изменение и удаление аватара сотрудника | Владелец, Администратор |
| `custom_properties:read` | Просмотр дополнительных полей | Все |
| `audit_events:read` | Просмотр журнала аудита | Владелец |
| `tasks:read` | Просмотр задач | Все |
diff --git a/apps/docs/public/api/bots/list-events.md b/apps/docs/public/api/bots/list-events.md
index 1a6d6c53..5d70fc1d 100644
--- a/apps/docs/public/api/bots/list-events.md
+++ b/apps/docs/public/api/bots/list-events.md
@@ -81,6 +81,18 @@ curl "https://api.pachca.com/api/shared/v1/webhooks/events?limit=1" \
- `user_id: integer, int32` (required) — Идентификатор пользователя, который нажал кнопку
- `chat_id: integer, int32` (required) — Идентификатор чата, в котором была нажата кнопка
- `webhook_timestamp: integer, int32` (required) — Дата и время отправки вебхука (UTC+0) в формате UNIX
+ - **ViewSubmitWebhookPayload**: Структура исходящего вебхука о заполнении формы
+ - `type: string` (required) — Тип объекта
+ Значения: `view` — Для формы всегда view
+ - `event: string` (required) — Тип события
+ Значения: `submit` — Отправка формы
+ - `callback_id: string` (required) — Идентификатор обратного вызова, указанный при открытии представления
+ - `private_metadata: string` (required) — Приватные метаданные, указанные при открытии представления
+ - `user_id: integer, int32` (required) — Идентификатор пользователя, который отправил форму
+ - `data: Record` (required) — Данные заполненных полей представления. Ключ — `action_id` поля, значение — введённые данные
+ **Структура значений Record:**
+ - Тип значения: `any`
+ - `webhook_timestamp: integer, int32` (required) — Дата и время отправки вебхука (UTC+0) в формате UNIX
- **ChatMemberWebhookPayload**: Структура исходящего вебхука об участниках чата
- `type: string` (required) — Тип объекта
Значения: `chat_member` — Для участника чата всегда chat_member
@@ -113,9 +125,9 @@ curl "https://api.pachca.com/api/shared/v1/webhooks/events?limit=1" \
- `created_at: date-time` (required) — Дата и время создания сообщения (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
- `webhook_timestamp: integer, int32` (required) — Дата и время отправки вебхука (UTC+0) в формате UNIX
- `created_at: date-time` (required) — Дата и время создания события (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
diff --git a/apps/docs/public/api/chats/list.md b/apps/docs/public/api/chats/list.md
index 04b32d2d..cd9467c8 100644
--- a/apps/docs/public/api/chats/list.md
+++ b/apps/docs/public/api/chats/list.md
@@ -12,7 +12,8 @@
### Query параметры
-- `sort[{field}]: string` (default: desc) — Составной параметр сортировки сущностей выборки
+- `sort: string` (default: id) — Поле сортировки
+- `order: string` (default: desc) — Направление сортировки
- `availability: string` (default: is_member) — Параметр, который отвечает за доступность и выборку чатов для пользователя
- `last_message_at_after: date-time` — Фильтрация по времени создания последнего сообщения. Будут возвращены те чаты, время последнего созданного сообщения в которых не раньше чем указанное (в формате YYYY-MM-DDThh:mm:ss.sssZ).
- `last_message_at_before: date-time` — Фильтрация по времени создания последнего сообщения. Будут возвращены те чаты, время последнего созданного сообщения в которых не позже чем указанное (в формате YYYY-MM-DDThh:mm:ss.sssZ).
@@ -25,7 +26,7 @@
```bash
# Для получения следующей страницы используйте cursor из meta.paginate.next_page
-curl "https://api.pachca.com/api/shared/v1/chats?sort[id]=desc&availability=is_member&last_message_at_after=2025-01-01T00:00:00.000Z&last_message_at_before=2025-02-01T00:00:00.000Z&personal=false&limit=1" \
+curl "https://api.pachca.com/api/shared/v1/chats?sort=id&order=desc&availability=is_member&last_message_at_after=2025-01-01T00:00:00.000Z&last_message_at_before=2025-02-01T00:00:00.000Z&personal=false&limit=1" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
@@ -47,9 +48,9 @@ curl "https://api.pachca.com/api/shared/v1/chats?sort[id]=desc&availability=is_m
- `public: boolean` (required) — Открытый доступ
- `last_message_at: date-time` (required) — Дата и время создания последнего сообщения в чате (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
- `meet_room_url: string` (required) — Ссылка на Видеочат
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
diff --git a/apps/docs/public/api/group-tags/list-users.md b/apps/docs/public/api/group-tags/list-users.md
index f986974a..0c49e68d 100644
--- a/apps/docs/public/api/group-tags/list-users.md
+++ b/apps/docs/public/api/group-tags/list-users.md
@@ -68,9 +68,9 @@ curl "https://api.pachca.com/api/shared/v1/group_tags/9111/users?limit=1" \
- `last_activity_at: date-time` (required) — Дата последней активности пользователя (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
- `time_zone: string` (required) — Часовой пояс пользователя
- `image_url: string` (required) — Ссылка на скачивание аватарки пользователя
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
diff --git a/apps/docs/public/api/group-tags/list.md b/apps/docs/public/api/group-tags/list.md
index c84dc75b..b250344c 100644
--- a/apps/docs/public/api/group-tags/list.md
+++ b/apps/docs/public/api/group-tags/list.md
@@ -35,9 +35,9 @@ curl "https://api.pachca.com/api/shared/v1/group_tags?names[]=Design&names[]=Pro
- `id: integer, int32` (required) — Идентификатор тега
- `name: string` (required) — Название тега
- `users_count: integer, int32` (required) — Количество сотрудников, которые имеют этот тег
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
diff --git a/apps/docs/public/api/members/list.md b/apps/docs/public/api/members/list.md
index f8b363a8..6d974444 100644
--- a/apps/docs/public/api/members/list.md
+++ b/apps/docs/public/api/members/list.md
@@ -71,9 +71,9 @@ curl "https://api.pachca.com/api/shared/v1/chats/334/members?role=all&limit=1" \
- `last_activity_at: date-time` (required) — Дата последней активности пользователя (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
- `time_zone: string` (required) — Часовой пояс пользователя
- `image_url: string` (required) — Ссылка на скачивание аватарки пользователя
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
diff --git a/apps/docs/public/api/messages/list.md b/apps/docs/public/api/messages/list.md
index d93e0131..28ad2acc 100644
--- a/apps/docs/public/api/messages/list.md
+++ b/apps/docs/public/api/messages/list.md
@@ -15,7 +15,8 @@
### Query параметры
- `chat_id: integer, int32` (required) — Идентификатор чата (беседа, канал, диалог или чат треда)
-- `sort[{field}]: string` (default: desc) — Составной параметр сортировки сущностей выборки
+- `sort: string` (default: id) — Поле сортировки
+- `order: string` (default: desc) — Направление сортировки
- `limit: integer, int32` (default: 50) — Количество возвращаемых сущностей за один запрос
- `cursor: string` — Курсор для пагинации (из `meta.paginate.next_page`)
@@ -24,7 +25,7 @@
```bash
# Для получения следующей страницы используйте cursor из meta.paginate.next_page
-curl "https://api.pachca.com/api/shared/v1/messages?chat_id=198&sort[id]=desc&limit=1" \
+curl "https://api.pachca.com/api/shared/v1/messages?chat_id=198&sort=id&order=desc&limit=1" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
@@ -71,9 +72,9 @@ curl "https://api.pachca.com/api/shared/v1/messages?chat_id=198&sort[id]=desc&li
- `display_name: string` (required) — Полное имя отправителя сообщения
- `changed_at: date-time` (required) — Дата и время последнего редактирования сообщения (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
- `deleted_at: date-time` (required) — Дата и время удаления сообщения (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
diff --git a/apps/docs/public/api/models.md b/apps/docs/public/api/models.md
index 1bffa87f..0ee247d2 100644
--- a/apps/docs/public/api/models.md
+++ b/apps/docs/public/api/models.md
@@ -3,7 +3,7 @@
Все модели данных, возвращаемые в ответах API. Каждая модель содержит связанные методы и таблицу свойств.
-> Методы [Получение подписи](POST /uploads) и [Загрузка файла](POST /direct_url) не возвращают модели данных.
+> Методы [Получение подписи](POST /uploads), [Загрузка файла](POST /direct_url), [Загрузка аватара](PUT /profile/avatar), [Удаление аватара](DELETE /profile/avatar), [Загрузка аватара сотрудника](PUT /users/{user_id}/avatar) и [Удаление аватара сотрудника](DELETE /users/{user_id}/avatar) не возвращают модели данных.
## Дополнительное поле
@@ -435,6 +435,18 @@
- `user_id: integer, int32` (required) — Идентификатор пользователя, который нажал кнопку. Пример: `2345`
- `chat_id: integer, int32` (required) — Идентификатор чата, в котором была нажата кнопка. Пример: `9012`
- `webhook_timestamp: integer, int32` (required) — Дата и время отправки вебхука (UTC+0) в формате UNIX. Пример: `1747574400`
+ - **ViewSubmitWebhookPayload**: Структура исходящего вебхука о заполнении формы
+ - `type: string` (required) — Тип объекта. Пример: `"view"`
+ Значения: `view` — Для формы всегда view
+ - `event: string` (required) — Тип события. Пример: `"submit"`
+ Значения: `submit` — Отправка формы
+ - `callback_id: string` (required) — Идентификатор обратного вызова, указанный при открытии представления. Пример: `"timeoff_request_form"`
+ - `private_metadata: string` (required) — Приватные метаданные, указанные при открытии представления. Пример: `"{'timeoff_id':4378}"`
+ - `user_id: integer, int32` (required) — Идентификатор пользователя, который отправил форму. Пример: `1235523`
+ - `data: Record` (required) — Данные заполненных полей представления. Ключ — `action_id` поля, значение — введённые данные
+ **Структура значений Record:**
+ - Тип значения: `any`
+ - `webhook_timestamp: integer, int32` (required) — Дата и время отправки вебхука (UTC+0) в формате UNIX. Пример: `1755075544`
- **ChatMemberWebhookPayload**: Структура исходящего вебхука об участниках чата
- `type: string` (required) — Тип объекта. Пример: `"chat_member"`
Значения: `chat_member` — Для участника чата всегда chat_member
diff --git a/apps/docs/public/api/pagination.md b/apps/docs/public/api/pagination.md
index 135ae367..1f122417 100644
--- a/apps/docs/public/api/pagination.md
+++ b/apps/docs/public/api/pagination.md
@@ -16,8 +16,8 @@ API Пачки использует **cursor-based** пагинацию для
#### PaginationMeta
-- `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
```json title="Пример ответа"
@@ -33,7 +33,7 @@ API Пачки использует **cursor-based** пагинацию для
Для перехода на следующую страницу передайте значение `next_page` в параметр `cursor` следующего запроса. Последняя страница достигнута, когда массив `data` вернулся пустым.
-> Не определяйте последнюю страницу по количеству записей в ответе — оно может быть меньше `limit` и на промежуточных страницах. Проверяйте пустой массив `data`. Курсор — непрозрачный токен: не парсите и не сохраняйте его между сессиями. Всегда явно указывайте `limit` — не полагайтесь на значение по умолчанию.
+> Поле `next_page` **всегда присутствует** в ответе и никогда не бывает `null` — даже на последней странице. Не используйте `next_page == null` как признак конца данных. Единственный надёжный способ — проверять, что массив `data` пуст. Количество записей в ответе может быть меньше `limit` и на промежуточных страницах — не полагайтесь на него. Курсор — непрозрачный токен: не парсите и не сохраняйте его между сессиями. Всегда явно указывайте `limit` — не полагайтесь на значение по умолчанию.
### Методы поиска
diff --git a/apps/docs/public/api/profile/delete-avatar.md b/apps/docs/public/api/profile/delete-avatar.md
new file mode 100644
index 00000000..6b4418b6
--- /dev/null
+++ b/apps/docs/public/api/profile/delete-avatar.md
@@ -0,0 +1,103 @@
+# Удаление аватара
+
+**Метод**: `DELETE`
+
+**Путь**: `/profile/avatar`
+
+> **Скоуп:** `profile_avatar:write`
+
+Метод для удаления аватара своего профиля.
+
+## Пример запроса
+
+```bash
+curl -X DELETE "https://api.pachca.com/api/shared/v1/profile/avatar" \
+ -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
+```
+
+## Ответы
+
+### 204: There is no content to send for this request, but the headers may be useful.
+
+### 401: Access is unauthorized.
+
+**Схема ответа при ошибке:**
+
+- `error: string` (required) — Код ошибки
+- `error_description: string` (required) — Описание ошибки
+
+**Пример ответа:**
+
+```json
+{
+ "error": "invalid_token",
+ "error_description": "Access token is missing"
+}
+```
+
+### 402: Client error
+
+**Схема ответа при ошибке:**
+
+- `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
+### 403: Access is forbidden.
+
+**Схема ответа при ошибке:**
+
+**anyOf** - один из вариантов:
+
+- **ApiError**: Ошибка API (используется для 400, 402, 403, 404, 409, 410, 422)
+ - `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+- **OAuthError**: Ошибка OAuth авторизации (используется для 401 и 403)
+ - `error: string` (required) — Код ошибки
+ - `error_description: string` (required) — Описание ошибки
+
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
diff --git a/apps/docs/public/api/profile/update-avatar.md b/apps/docs/public/api/profile/update-avatar.md
new file mode 100644
index 00000000..120c3c2a
--- /dev/null
+++ b/apps/docs/public/api/profile/update-avatar.md
@@ -0,0 +1,152 @@
+# Загрузка аватара
+
+**Метод**: `PUT`
+
+**Путь**: `/profile/avatar`
+
+> **Скоуп:** `profile_avatar:write`
+
+Метод для загрузки или обновления аватара своего профиля. Файл передается в формате `multipart/form-data`.
+
+## Тело запроса
+
+
+## Пример запроса
+
+```bash
+curl -X PUT "https://api.pachca.com/api/shared/v1/profile/avatar" \
+ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
+ -F "image=@filename.png"
+```
+
+## Ответы
+
+### 200: The request has succeeded.
+
+**Схема ответа:**
+
+- `data: object` (required) — Данные аватара
+ - `image_url: string` (required) — URL аватара
+
+**Пример ответа:**
+
+```json
+{
+ "data": {
+ "image_url": "https://pachca-prod.s3.amazonaws.com/uploads/0001/0001/image.jpg"
+ }
+}
+```
+
+### 401: Access is unauthorized.
+
+**Схема ответа при ошибке:**
+
+- `error: string` (required) — Код ошибки
+- `error_description: string` (required) — Описание ошибки
+
+**Пример ответа:**
+
+```json
+{
+ "error": "invalid_token",
+ "error_description": "Access token is missing"
+}
+```
+
+### 402: Client error
+
+**Схема ответа при ошибке:**
+
+- `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
+### 403: Access is forbidden.
+
+**Схема ответа при ошибке:**
+
+**anyOf** - один из вариантов:
+
+- **ApiError**: Ошибка API (используется для 400, 402, 403, 404, 409, 410, 422)
+ - `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+- **OAuthError**: Ошибка OAuth авторизации (используется для 401 и 403)
+ - `error: string` (required) — Код ошибки
+ - `error_description: string` (required) — Описание ошибки
+
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
+### 422: Client error
+
+**Схема ответа при ошибке:**
+
+- `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
diff --git a/apps/docs/public/api/reactions/list.md b/apps/docs/public/api/reactions/list.md
index d631b566..98f083fb 100644
--- a/apps/docs/public/api/reactions/list.md
+++ b/apps/docs/public/api/reactions/list.md
@@ -39,9 +39,9 @@ curl "https://api.pachca.com/api/shared/v1/messages/194275/reactions?limit=1" \
- `created_at: date-time` (required) — Дата и время добавления реакции (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
- `code: string` (required) — Emoji символ реакции
- `name: string` (required) — Название emoji реакции
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
diff --git a/apps/docs/public/api/read-member/list-readers.md b/apps/docs/public/api/read-member/list-readers.md
index cf081e46..0927b77d 100644
--- a/apps/docs/public/api/read-member/list-readers.md
+++ b/apps/docs/public/api/read-member/list-readers.md
@@ -35,9 +35,9 @@ curl "https://api.pachca.com/api/shared/v1/messages/194275/read_member_ids?limit
**Схема ответа:**
- `data: array of integer` (required)
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
diff --git a/apps/docs/public/api/security/list.md b/apps/docs/public/api/security/list.md
index 2c2e7cfc..ea0e48a3 100644
--- a/apps/docs/public/api/security/list.md
+++ b/apps/docs/public/api/security/list.md
@@ -99,9 +99,9 @@ curl "https://api.pachca.com/api/shared/v1/audit_events?start_time=2025-05-01T09
- Тип значения: `any`
- `ip_address: string` (required) — IP-адрес, с которого было выполнено действие
- `user_agent: string` (required) — User agent клиента
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
diff --git a/apps/docs/public/api/tasks/list.md b/apps/docs/public/api/tasks/list.md
index e2b0c9cd..2858db6e 100644
--- a/apps/docs/public/api/tasks/list.md
+++ b/apps/docs/public/api/tasks/list.md
@@ -50,9 +50,9 @@ curl "https://api.pachca.com/api/shared/v1/tasks?limit=1" \
- `data_type: string` (required) — Тип поля
Значения: `string` — Строковое значение, `number` — Числовое значение, `date` — Дата, `link` — Ссылка
- `value: string` (required) — Значение
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
diff --git a/apps/docs/public/api/users/list.md b/apps/docs/public/api/users/list.md
index 1a7fa97a..4dd65b6b 100644
--- a/apps/docs/public/api/users/list.md
+++ b/apps/docs/public/api/users/list.md
@@ -65,9 +65,9 @@ curl "https://api.pachca.com/api/shared/v1/users?query=Олег&limit=1" \
- `last_activity_at: date-time` (required) — Дата последней активности пользователя (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
- `time_zone: string` (required) — Часовой пояс пользователя
- `image_url: string` (required) — Ссылка на скачивание аватарки пользователя
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
diff --git a/apps/docs/public/api/users/remove-avatar.md b/apps/docs/public/api/users/remove-avatar.md
new file mode 100644
index 00000000..02549d6c
--- /dev/null
+++ b/apps/docs/public/api/users/remove-avatar.md
@@ -0,0 +1,140 @@
+# Удаление аватара сотрудника
+
+**Метод**: `DELETE`
+
+**Путь**: `/users/{user_id}/avatar`
+
+> **Скоуп:** `user_avatar:write`
+
+Метод для удаления аватара сотрудника.
+
+## Параметры
+
+### Path параметры
+
+- `user_id: integer, int32` (required) — Идентификатор пользователя
+
+
+## Пример запроса
+
+```bash
+curl -X DELETE "https://api.pachca.com/api/shared/v1/users/12/avatar" \
+ -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
+```
+
+## Ответы
+
+### 204: There is no content to send for this request, but the headers may be useful.
+
+### 401: Access is unauthorized.
+
+**Схема ответа при ошибке:**
+
+- `error: string` (required) — Код ошибки
+- `error_description: string` (required) — Описание ошибки
+
+**Пример ответа:**
+
+```json
+{
+ "error": "invalid_token",
+ "error_description": "Access token is missing"
+}
+```
+
+### 402: Client error
+
+**Схема ответа при ошибке:**
+
+- `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
+### 403: Access is forbidden.
+
+**Схема ответа при ошибке:**
+
+**anyOf** - один из вариантов:
+
+- **ApiError**: Ошибка API (используется для 400, 402, 403, 404, 409, 410, 422)
+ - `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+- **OAuthError**: Ошибка OAuth авторизации (используется для 401 и 403)
+ - `error: string` (required) — Код ошибки
+ - `error_description: string` (required) — Описание ошибки
+
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
+### 404: The server cannot find the requested resource.
+
+**Схема ответа при ошибке:**
+
+- `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
diff --git a/apps/docs/public/api/users/update-avatar.md b/apps/docs/public/api/users/update-avatar.md
new file mode 100644
index 00000000..292ead33
--- /dev/null
+++ b/apps/docs/public/api/users/update-avatar.md
@@ -0,0 +1,189 @@
+# Загрузка аватара сотрудника
+
+**Метод**: `PUT`
+
+**Путь**: `/users/{user_id}/avatar`
+
+> **Скоуп:** `user_avatar:write`
+
+Метод для загрузки или обновления аватара сотрудника. Файл передается в формате `multipart/form-data`.
+
+## Параметры
+
+### Path параметры
+
+- `user_id: integer, int32` (required) — Идентификатор пользователя
+
+
+## Тело запроса
+
+
+## Пример запроса
+
+```bash
+curl -X PUT "https://api.pachca.com/api/shared/v1/users/12/avatar" \
+ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
+ -F "image=@filename.png"
+```
+
+## Ответы
+
+### 200: The request has succeeded.
+
+**Схема ответа:**
+
+- `data: object` (required) — Данные аватара
+ - `image_url: string` (required) — URL аватара
+
+**Пример ответа:**
+
+```json
+{
+ "data": {
+ "image_url": "https://pachca-prod.s3.amazonaws.com/uploads/0001/0001/image.jpg"
+ }
+}
+```
+
+### 401: Access is unauthorized.
+
+**Схема ответа при ошибке:**
+
+- `error: string` (required) — Код ошибки
+- `error_description: string` (required) — Описание ошибки
+
+**Пример ответа:**
+
+```json
+{
+ "error": "invalid_token",
+ "error_description": "Access token is missing"
+}
+```
+
+### 402: Client error
+
+**Схема ответа при ошибке:**
+
+- `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
+### 403: Access is forbidden.
+
+**Схема ответа при ошибке:**
+
+**anyOf** - один из вариантов:
+
+- **ApiError**: Ошибка API (используется для 400, 402, 403, 404, 409, 410, 422)
+ - `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+- **OAuthError**: Ошибка OAuth авторизации (используется для 401 и 403)
+ - `error: string` (required) — Код ошибки
+ - `error_description: string` (required) — Описание ошибки
+
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
+### 404: The server cannot find the requested resource.
+
+**Схема ответа при ошибке:**
+
+- `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
+### 422: Client error
+
+**Схема ответа при ошибке:**
+
+- `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
diff --git a/apps/docs/public/guides/cli.md b/apps/docs/public/guides/cli.md
index 08fcd781..dcbae0cc 100644
--- a/apps/docs/public/guides/cli.md
+++ b/apps/docs/public/guides/cli.md
@@ -1,7 +1,7 @@
# CLI
-[@pachca/cli](https://www.npmjs.com/package/@pachca/cli) 2026.3.10 · 21 марта 2026
+[@pachca/cli](https://www.npmjs.com/package/@pachca/cli) 2026.4.0 · 7 апреля 2026
Официальный CLI для работы с Pachca API из терминала. Каждый API-метод доступен как команда с типизированными флагами, валидацией и интерактивными подсказками. Требуется Node.js 20 или новее.
@@ -132,15 +132,19 @@ dev.pachca.com/api/members/add → pachca members add
| `pachca profile get-info` | Информация о токене |
| `pachca profile get` | Информация о профиле |
| `pachca profile get-status` | Текущий статус |
+| `pachca profile update-avatar` | Загрузка аватара |
| `pachca profile update-status` | Новый статус |
+| `pachca profile delete-avatar` | Удаление аватара |
| `pachca profile delete-status` | Удаление статуса |
| `pachca users create` | Создать сотрудника |
| `pachca users list` | Список сотрудников |
| `pachca users get` | Информация о сотруднике |
| `pachca users get-status` | Статус сотрудника |
| `pachca users update` | Редактирование сотрудника |
+| `pachca users update-avatar` | Загрузка аватара сотрудника |
| `pachca users update-status` | Новый статус сотрудника |
| `pachca users delete` | Удаление сотрудника |
+| `pachca users remove-avatar` | Удаление аватара сотрудника |
| `pachca users remove-status` | Удаление статуса сотрудника |
| `pachca group-tags create` | Новый тег |
| `pachca group-tags list` | Список тегов сотрудников |
diff --git a/apps/docs/public/guides/forms/handling.md b/apps/docs/public/guides/forms/handling.md
index b20e0650..3452667d 100644
--- a/apps/docs/public/guides/forms/handling.md
+++ b/apps/docs/public/guides/forms/handling.md
@@ -23,18 +23,19 @@
Каждый исходящий вебхук защищён с помощью подписи, основанной на хешировании содержимого. Подробнее об этом — в разделе [Безопасность](/guides/webhook#bezopasnost).
-#### Структура исходящего вебхука о заполнении формы
-
-- `type: string` — Тип объекта (для представлений всегда view)
- Значения: `view` — представление
-- `event: string` — Тип события (для отправки пользователем формы всегда submit)
- Значения: `submit` — отправка формы
-- `private_metadata: string` — Строка, заданная при отправке представления
-- `callback_id: string` — Идентификатор для распознавания этого представления, заданный при отправке представления
-- `user_id: integer` — Идентификатор пользователя, который заполнил форму
-- `data: object` — JSON карта заполненных полей представления, где каждый ключ - значение поля name.
- - `name: string | array of strings | array of objects | null | []` — Значение, которое указал пользователь в поле (или массив значений, если это был множественный выбор или загруженные пользователем файлы). Если пользователь не указал значение, тогда null (или пустой массив, если это поле файлов или чекбоксов)
-- `webhook_timestamp: integer` — Дата и время отправки вебхука (UTC+0) в формате UNIX
+#### ViewSubmitWebhookPayload
+
+- `type: string` (required) — Тип объекта
+ Значения: `view` — Для формы всегда view
+- `event: string` (required) — Тип события
+ Значения: `submit` — Отправка формы
+- `callback_id: string` (required) — Идентификатор обратного вызова, указанный при открытии представления
+- `private_metadata: string` (required) — Приватные метаданные, указанные при открытии представления
+- `user_id: integer, int32` (required) — Идентификатор пользователя, который отправил форму
+- `data: Record` (required) — Данные заполненных полей представления. Ключ — `action_id` поля, значение — введённые данные
+ **Структура значений Record:**
+ - Тип значения: `any`
+- `webhook_timestamp: integer, int32` (required) — Дата и время отправки вебхука (UTC+0) в формате UNIX
```json title="Пример вебхука о заполнении формы"
diff --git a/apps/docs/public/guides/n8n.md b/apps/docs/public/guides/n8n.md
deleted file mode 100644
index 3845e6da..00000000
--- a/apps/docs/public/guides/n8n.md
+++ /dev/null
@@ -1,200 +0,0 @@
-
-# n8n
-
-## Что такое n8n
-
-n8n — платформа для автоматизации рабочих процессов с открытым исходным кодом. Можно развернуть на собственном сервере или использовать веб-версию. Платформа позволяет создавать интеграции с сервисами без написания кода, используя визуальный редактор с узлами (nodes).
-
-В n8n встроено более 400 готовых узлов для популярных сервисов. Узлы бывают двух типов:
-
-- **Узел триггера** — событие, запускающее рабочий процесс: новое сообщение, нажатие кнопки, обновление статуса и др.
-- **Узел действия** — логика после триггера: отправка сообщения в чат, создание задачи, добавление записи в БД и т.д.
-
-n8n автоматически выполняет каждое действие по триггеру в указанном порядке.
-
-
-
-*Визуальный редактор n8n*
-
-
-## Настройка
-
-
- ### Шаг 1. Установка n8n
-
-> **Внимание:** Расширение Пачки доступно пока только в Beta. Его нет в веб-версии n8n — для использования нужно развернуть коробочную версию на собственном сервере.
-
-
- Два способа установки:
-
- **С помощью команды** (требуется Node.js):
-
- ```bash
- npx n8n
- ```
-
- **С помощью Docker:**
-
- ```bash
- docker volume create n8n_data
-
- docker run -it --rm \
- --name n8n \
- -p 5678:5678 \
- -e GENERIC_TIMEZONE="" \
- -e TZ="" \
- -e N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true \
- -e N8N_RUNNERS_ENABLED=true \
- -v n8n_data:/home/node/.n8n \
- docker.n8n.io/n8nio/n8n
- ```
-
- Подробные инструкции — в [официальной документации n8n](https://docs.n8n.io/hosting/) и на [GitHub](https://github.com/n8n-io/n8n).
-
- После запуска настройте аккаунт владельца (Owner Account), указав почту, имя и пароль.
-
- 
-
-*Настройка Owner Account*
-
-
- ### Шаг 2. Установка расширения Пачки
-
-Расширение доступно на [GitHub](https://github.com/pachca/n8n-nodes-pachca) и [npmjs](https://www.npmjs.com/package/n8n-nodes-pachca).
-
- Три способа установки:
-
- 1. Зайти в **Settings** > **Community nodes** и добавить `n8n-nodes-pachca` (рекомендуется)
- 2. Выполнить команду `npm i n8n-nodes-pachca` в директории n8n
- 3. Следовать README-инструкции на [GitHub](https://github.com/pachca/n8n-nodes-pachca)
-
- 
-
-*Установка через Community nodes*
-
-
- ### Шаг 3. Создание Credentials
-
-Credentials — данные для авторизации.
-
- Нажмите **«Add Credential»**, найдите **Pachca API** в списке и заполните поля:
-
- - **Base URL:** `https://api.pachca.com/api/shared/v1`
- - **Access Token:** Токен доступа к API. В Пачке доступны два типа токенов:
- - Персональный токен — доступен в разделе **Автоматизации** > **Интеграции** > **API**
- - Токен бота — доступен в настройках бота на вкладке **API**
-
- Подробнее о токенах — в разделе [Авторизация](/api/authorization). Credentials можно создать несколько — для разных операций и токенов.
-
- 
-
-*Настройка Pachca API Credentials*
-
-
- ### Шаг 4. Создание Workflow
-
-Workflow — визуальный редактор, в котором выстраиваются цепочки триггеров и действий.
-
- 
-
-*Редактор workflow*
-
-
- Пример: отправка сообщения от лица бота. Триггер — нажатие кнопки **«Execute Workflow»**, действие — **Send a message** в Пачке:
-
- - **Credential** — от чьего лица будет отправлено сообщение
- - **Entity ID** — ID чата
- - **Content** — содержание сообщения
-
- Не забудьте добавить бота в чат.
-
- 
-
-*Пример отправки сообщения*
-
-
- В платформу встроено более 400 узлов для популярных сервисов, а дополнительные Community nodes можно установить из интерфейса. При необходимости доступны HTTP-запросы к любому API, условия, кастомный JavaScript- или Python-код.
-
-
-## Методы API в nodes Pachca (Beta)
-
-Nodes (узлы) в расширении Пачки для n8n совпадают с методами API. Вот список доступных:
-
-### Действия с сообщениями
-
-- **Send a message** — [Новое сообщение](POST /messages)
-- **Get a message** — [Информация о сообщении](GET /messages/{id})
-- **Get messages from the chat** — [Список сообщений чата](GET /messages)
-- **Update a message** — [Редактирование сообщения](PUT /messages/{id})
-- **Delete a message** — [Удаление сообщения](DELETE /messages/{id})
-- **Pin a message** — [Закрепление сообщения](POST /messages/{id}/pin)
-- **Unpin a message** — [Открепление сообщения](DELETE /messages/{id}/pin)
-- **Get message readers** — [Список прочитавших сообщение](GET /messages/{id}/read_member_ids)
-- **Unfurl message links** — [Unfurl (разворачивание ссылок)](POST /messages/{id}/link_previews)
-
-### Действия с тредами
-
-- **Create a thread** — [Новый тред](POST /messages/{id}/thread)
-- **Get a thread** — [Информация о треде](GET /threads/{id})
-
-### Действия с реакциями
-
-- **Add a reaction** — [Новая реакция](POST /messages/{id}/reactions)
-- **Remove a reaction** — [Удаление реакции](DELETE /messages/{id}/reactions)
-- **Get message reactions** — [Список реакций на сообщение](GET /messages/{id}/reactions)
-
-### Действия с чатом
-
-- **Get all chats** — [Список чатов](GET /chats)
-- **Get a chat** — [Информация о чате](GET /chats/{id})
-- **Create a chat** — [Новый чат](POST /chats)
-- **Update a chat** — [Обновление чата](PUT /chats/{id})
-- **Archive a chat** — [Архивация чата](PUT /chats/{id}/archive)
-- **Unarchive a chat** — [Разархивация чата](PUT /chats/{id}/unarchive)
-- **Get chat members** — [Список участников чата](GET /chats/{id}/members)
-- **Add users to chat** — [Добавление пользователей](POST /chats/{id}/members)
-- **Remove user from chat** — [Исключение пользователя](DELETE /chats/{id}/members/{user_id})
-- **Update user role in chat** — [Редактирование роли](PUT /chats/{id}/members/{user_id})
-- **Leave a chat** — [Выход из чата](DELETE /chats/{id}/leave)
-
-### Действия с пользователями
-
-- **Get all users** — [Список сотрудников](GET /users)
-- **Get a user** — [Информация о сотруднике](GET /users/{id})
-- **Create a user** — [Новый сотрудник](POST /users)
-- **Update a user** — [Редактирование сотрудника](PUT /users/{id})
-- **Delete a user** — [Удаление сотрудника](DELETE /users/{id})
-
-### Действия с тегами пользователей
-
-- **Get all group tags** — [Список тегов сотрудников](GET /group_tags)
-- **Get a group tag** — [Информация о теге](GET /group_tags/{id})
-- **Create a group tag** — [Новый тег](POST /group_tags)
-- **Update a group tag** — [Редактирование тега](PUT /group_tags/{id})
-- **Delete a group tag** — [Удаление тега](DELETE /group_tags/{id})
-- **Get users in group tag** — [Список сотрудников тега](GET /group_tags/{id}/users)
-- **Add tags to chat** — [Добавление тегов](POST /chats/{id}/group_tags)
-- **Remove tag from chat** — [Исключение тега](DELETE /chats/{id}/group_tags/{tag_id})
-
-### Действия со статусом и профилем
-
-- **Get my profile** — [Информация о профиле](GET /profile)
-- **Get my status** — [Текущий статус](GET /profile/status)
-- **Set my status** — [Новый статус](PUT /profile/status)
-- **Clear my status** — [Удаление статуса](DELETE /profile/status)
-
-### Действия с формами
-
-- **Create a form** — [Открытие представления](POST /views/open)
-- **Process form submission** — закрытие и отображение ошибок
-- **Get form templates**
-
-### Другие действия
-
-- **Get custom properties** — [Список дополнительных полей](GET /custom_properties)
-- **Create a task** — [Новое напоминание](POST /tasks)
-- **Update a bot** — [Редактирование бота](PUT /bots/{id})
-- **Upload a file** — [Загрузка файла](POST /direct_url)
-
-> **Внимание:** Некоторые действия доступны только с персональным токеном (владельца или участника с ролью «Администратор»).
-
diff --git a/apps/docs/public/guides/n8n/advanced.md b/apps/docs/public/guides/n8n/advanced.md
new file mode 100644
index 00000000..124da1d4
--- /dev/null
+++ b/apps/docs/public/guides/n8n/advanced.md
@@ -0,0 +1,225 @@
+
+# Продвинутые функции
+
+## Загрузка файлов
+
+
+
+*Ресурс File с параметрами загрузки*
+
+
+Ресурс **File** позволяет загружать файлы через двухшаговый S3 upload. n8n автоматически выполняет оба этапа: запрашивает presigned URL через API и загружает файл на S3.
+
+**Два источника файлов:**
+
+| Источник | Описание |
+|----------|----------|
+| **URL** | Файл загружается по ссылке. Укажите `fileUrl`, `fileName` и `contentType` |
+| **Binary Data** | Файл из предыдущего узла workflow (например, из HTTP Request). Укажите `binaryProperty` |
+
+**Пример workflow: загрузка PDF и отправка в чат**
+
+1. **HTTP Request** — скачать файл по URL
+2. **Pachca** (File > Create) — загрузить файл, получить `key`
+3. **Pachca** (Message > Create) — отправить сообщение с прикреплённым файлом, указав `key` в поле `files`
+
+> Загруженные файлы привязываются к сообщениям через массив `files` при создании или обновлении сообщения. Каждый файл описывается объектом с полями: `key`, `name`, `file_type`, `size`.
+
+
+Подробнее — в [документации загрузки файлов](/api/file-uploads).
+
+---
+
+## Загрузка аватара
+
+Операции **Update Avatar** для ресурсов **Profile** и **User** позволяют загружать аватар через `multipart/form-data`.
+
+**Как использовать:**
+
+1. **HTTP Request** или **Read Binary File** — загрузите изображение в бинарное свойство (по умолчанию `data`)
+2. **Pachca** (Profile > Update Avatar или User > Update Avatar) — в поле **Input Binary Field** укажите имя бинарного свойства
+
+Для удаления аватара используйте операцию **Delete Avatar** — она не требует параметров (для User — только `userId`).
+
+> Операции с аватарами сотрудников (User > Update/Delete Avatar) требуют прав администратора.
+
+
+---
+
+## Экспорт сообщений
+
+Ресурс **Chat Export** позволяет выгружать сообщения из чатов. Экспорт выполняется асинхронно: вы запрашиваете экспорт, а Пачка присылает уведомление на вебхук, когда архив готов.
+
+**Пример workflow:**
+
+
+ ### Шаг 1. Настройте приём вебхука
+
+Создайте отдельный workflow с узлом **Webhook** (встроенный в n8n). Он создаст URL, на который Пачка отправит уведомление о готовности экспорта. Скопируйте этот URL — он понадобится на следующем шаге.
+
+
+ ### Шаг 2. Запросите экспорт
+
+В основном workflow добавьте узел **Pachca** с ресурсом **Chat Export** и операцией **Create**. Укажите период (`startAt`, `endAt`) и вставьте URL вебхука из первого шага в поле `webhookUrl`.
+
+
+ ### Шаг 3. Обработайте уведомление
+
+Когда экспорт будет готов, Пачка отправит JSON на ваш вебхук:
+ ```json
+ {
+ "type": "export",
+ "event": "ready",
+ "export_id": 22322,
+ "created_at": "2025-03-20T12:33:58Z"
+ }
+ ```
+
+
+ ### Шаг 4. Скачайте архив
+
+В workflow с Webhook-узлом добавьте узел **Pachca** с ресурсом **Chat Export** и операцией **Get**. Передайте `export_id` из данных вебхука в поле **ID**. Архив будет скачан автоматически.
+
+
+**Ограничения:**
+- Максимальный период одной выгрузки: 45 дней (366 дней при указании конкретных чатов)
+- Максимум 50 чатов при фильтрации по `chatIds`
+- Новый запрос можно сделать только после завершения текущего
+
+Подробнее — в [документации экспорта](/guides/export).
+
+---
+
+## Кнопки в сообщениях
+
+
+
+*Кнопки в параметрах сообщения*
+
+
+При создании или обновлении сообщения можно добавить интерактивные кнопки через поле **Buttons**. Кнопки передаются как JSON-строка.
+
+Два типа кнопок:
+
+| Тип | Описание |
+|-----|----------|
+| **URL-кнопка** | Открывает ссылку в браузере |
+| **Data-кнопка** | Отправляет вебхук с `button_pressed` событием |
+
+Пример JSON для одной строки кнопок:
+
+```json
+[
+ [
+ { "text": "Открыть сайт", "url": "https://example.com" },
+ { "text": "Подтвердить", "data": "confirm_action" }
+ ]
+]
+```
+
+Максимум 100 кнопок на сообщение, до 8 в одной строке.
+
+Подробнее о кнопках, их внешнем виде в чате и обработке нажатий — в [документации кнопок](/guides/buttons).
+
+---
+
+## Формы
+
+Ресурс **Form** позволяет открывать модальные формы (представления) для пользователей.
+
+**Как это работает:**
+
+1. Пользователь нажимает Data-кнопку в сообщении бота
+2. Бот получает вебхук с `trigger_id`
+3. Бот вызывает [Открытие представления](POST /views/open) с `trigger_id` и описанием формы
+4. Пользователь видит модальное окно с полями
+
+**Ключевые параметры:**
+
+| Параметр | Описание |
+|----------|----------|
+| `triggerId` | Уникальный ID из вебхука кнопки (действителен 3 секунды) |
+| `title` | Заголовок модального окна |
+| `type` | Тип представления (по умолчанию `modal`) |
+| `blocks` | JSON-массив блоков формы |
+| `submitText` | Текст кнопки отправки |
+| `closeText` | Текст кнопки закрытия |
+
+**Пример workflow:**
+
+1. **Pachca Trigger** — событие `Button Pressed`
+2. **Pachca** (Form > Create) — открыть форму с `trigger_id` из триггера
+3. **Pachca Trigger** — событие `Form Submitted` (в отдельном workflow)
+4. Обработка данных формы
+
+Подробнее о формах, типах полей и внешнем виде модального окна в интерфейсе Пачки — в [документации форм](/guides/forms/overview).
+
+---
+
+## AI-агент
+
+
+
+*Pachca Tool в панели инструментов AI Agent*
+
+
+Оба узла (Pachca и Pachca Trigger) поддерживают `usableAsTool: true` — их можно использовать как инструменты для **AI Agent** в n8n.
+
+**Что это значит:**
+
+- AI Agent может вызывать операции Pachca для выполнения задач
+- Примеры: поиск сообщений, отправка ответов, создание задач
+- Agent автоматически выбирает подходящую операцию на основе запроса
+
+**Пример: AI-помощник для команды**
+
+
+ ### Шаг 1. Добавьте AI Agent
+
+Создайте новый workflow. Добавьте узел **AI Agent** и подключите LLM-модель (OpenAI, Anthropic и др.) через соответствующие Credentials.
+
+
+ ### Шаг 2. Подключите инструменты Pachca
+
+Нажмите **+** на входе **Tool** узла AI Agent и добавьте узел **Pachca**. Выберите нужную операцию — например, **Search > Get Many Messages** для поиска по сообщениям. Добавьте ещё один узел Pachca для **Message > Create** — отправки ответов.
+
+
+ ### Шаг 3. Настройте триггер
+
+Добавьте **Pachca Trigger** с событием `New Message` на вход workflow. AI Agent будет автоматически отвечать на сообщения пользователей, используя поиск по истории чатов.
+
+
+AI Agent самостоятельно выбирает подходящий инструмент на основе запроса пользователя — ищет информацию, создаёт задачи или отправляет сообщения.
+
+> Для использования AI Agent необходимо настроить LLM-провайдер (OpenAI, Anthropic и др.) в Credentials n8n.
+
+
+---
+
+## Разворачивание ссылок
+
+Ресурс **Link Preview** позволяет формировать кастомные превью для ссылок в сообщениях бота.
+
+Когда бот отправляет сообщение со ссылкой, Пачка может запросить у бота данные для превью. Бот может ответить через [Создание превью ссылки](POST /messages/{id}/link_previews) с заголовком, описанием и изображением.
+
+Подробнее — в [документации разворачивания ссылок](/guides/link-previews).
+
+---
+
+## Журнал безопасности
+
+Ресурс **Security** предоставляет доступ к журналу аудита — списку событий безопасности в пространстве.
+
+**Доступные фильтры:**
+
+| Фильтр | Описание |
+|--------|----------|
+| `eventKey` | Тип события (login, message_created, user_deleted и др.) |
+| `actorId` | ID пользователя, совершившего действие |
+| `actorType` | Тип актора (user или bot) |
+| `entityId` | ID сущности, над которой совершено действие |
+| `entityType` | Тип сущности |
+| `startTime` | Начало временного диапазона |
+| `endTime` | Конец временного диапазона |
+
+Подробнее — в [документации журнала аудита](/guides/audit-events).
diff --git a/apps/docs/public/guides/n8n/migration.md b/apps/docs/public/guides/n8n/migration.md
new file mode 100644
index 00000000..33e7cdbd
--- /dev/null
+++ b/apps/docs/public/guides/n8n/migration.md
@@ -0,0 +1,110 @@
+
+# Миграция с v1
+
+> Обновление необязательно. Все существующие workflow на v1 продолжают работать без изменений.
+
+
+## Полная обратная совместимость
+
+Версия 2.0 на 100% совместима с v1. При обновлении расширения:
+
+- Существующие workflow остаются на v1 и работают как прежде
+- Новые workflow по умолчанию создаются на v2 с обновлёнными именами
+- Переход с v1 на v2 — опциональный
+
+## Переименованные ресурсы
+
+| v1 | v2 | Изменение |
+|----|-----|-----------|
+| `reactions` | `reaction` | Единственное число |
+| `status` | `profile` | Более точное имя |
+| `customFields` | `customProperty` | Более точное имя |
+
+## Переименованные операции
+
+| Ресурс | v1 | v2 |
+|--------|-----|-----|
+| Message | `send` | `create` |
+| Message | `getById` | `get` |
+| Chat | `getById` | `get` |
+| User | `getById` | `get` |
+| Group Tag | `getById` | `get` |
+| Group Tag | `getUsers` | `getAllUsers` |
+| Reaction | `addReaction` | `create` |
+| Reaction | `deleteReaction` | `delete` |
+| Reaction | `getReactions` | `getAll` |
+| Custom Property | `getCustomProperties` | `get` |
+| Profile | `getProfile` | `get` |
+| Thread | `createThread` | `create` |
+| Thread | `getThread` | `get` |
+| Form | `createView` | `create` |
+| File | `upload` | `create` |
+
+## Перенесённые операции
+
+Некоторые операции из v1 ресурсов были перенесены в новые v2 ресурсы:
+
+| v1 ресурс | v1 операция | v2 ресурс | v2 операция |
+|-----------|-------------|-----------|-------------|
+| Chat | `getMembers` | Chat Member | `getAll` |
+| Chat | `addUsers` | Chat Member | `create` |
+| Chat | `removeUser` | Chat Member | `delete` |
+| Chat | `updateRole` | Chat Member | `update` |
+| Chat | `leaveChat` | Chat Member | `leave` |
+| Group Tag | `addTags` | Chat Member | `addGroupTags` |
+| Group Tag | `removeTag` | Chat Member | `removeGroupTags` |
+| Message | `getReadMembers` | Read Member | `getAll` |
+| Message | `unfurl` | Link Preview | `create` |
+
+> Все перенесённые операции продолжают работать в v1 workflow без изменений. Маршрутизатор автоматически транслирует v1 имена в v2.
+
+
+## Новые ресурсы (только v2)
+
+| Ресурс | Описание |
+|--------|----------|
+| **Chat Member** | Управление участниками чата: добавление, удаление, роли, теги |
+| **Custom Property** | Дополнительные поля пространства |
+| **Read Member** | Список прочитавших сообщение |
+| **Link Preview** | Разворачивание ссылок в сообщениях |
+| **Search** | Полнотекстовый поиск по чатам, сообщениям, пользователям |
+| **Chat Export** | Экспорт сообщений из чатов |
+| **Security** | Журнал безопасности |
+
+## Новые функции
+
+| Функция | Описание |
+|---------|----------|
+| **Return All / Limit** | Курсорная автопагинация вместо ручного `per`/`page` |
+| **Simplify** | Переключатель для получения только ключевых полей из ответа API ([подробнее](/guides/n8n/resources#simplify)) |
+| **Pachca Trigger** | Webhook-нода с авторегистрацией вебхука через Bot ID, 16 типов событий |
+| **AI Tool Use** | Использование узлов как инструментов AI Agent |
+| **Searchable Dropdowns** | Поиск по чатам и пользователям в выпадающих списках |
+| **File Upload** | Загрузка файлов через S3 с поддержкой URL и Binary Data |
+| **Task CRUD** | Полный CRUD для задач (было только создание) |
+
+## Как работает совместимость
+
+Расширение использует паттерн **VersionedNodeType** с `defaultVersion: 2`:
+
+- **V1** и **V2** — отдельные классы с собственными описаниями ресурсов и операций
+- Общий **SharedRouter** обрабатывает запросы обеих версий, транслируя v1 имена ресурсов и операций в v2 на лету
+- При обновлении расширения существующие ноды сохраняют `typeVersion: 1` и используют V1 класс — все параметры и поведение остаются прежними
+- Новые ноды по умолчанию создаются с `typeVersion: 2` и используют чистый V2 класс
+- В Node Creator отображаются только v2 операции — без дубликатов
+- V1 ноды в существующих workflow показывают жёлтый баннер «New node version available» с предложением обновиться
+
+## Как обновить workflow (необязательно)
+
+При открытии существующего workflow вы увидите жёлтый баннер в настройках v1 нод — это информационное уведомление, менять ничего не нужно.
+
+Если вы хотите перевести ноду на v2:
+
+1. Откройте workflow в n8n
+2. Удалите v1 ноду Pachca
+3. Добавьте новую ноду Pachca (по умолчанию v2)
+4. Перенастройте с v2 именами ресурсов и операций
+5. API-вызовы идентичны — изменились только имена в UI
+
+> При удалении ноды и добавлении новой вы получите v2 с обновлёнными именами и новыми ресурсами. Все параметры и API endpoint-ы остались прежними.
+
diff --git a/apps/docs/public/guides/n8n/overview.md b/apps/docs/public/guides/n8n/overview.md
new file mode 100644
index 00000000..308bfb1a
--- /dev/null
+++ b/apps/docs/public/guides/n8n/overview.md
@@ -0,0 +1,57 @@
+
+# n8n
+
+## Что такое n8n
+
+[n8n](https://n8n.io) — платформа для автоматизации рабочих процессов с открытым исходным кодом. Можно развернуть на собственном сервере или использовать веб-версию. Платформа позволяет создавать интеграции с сервисами без написания кода, используя визуальный редактор с узлами (nodes).
+
+В n8n встроено более 400 готовых узлов для популярных сервисов. Узлы бывают двух типов:
+
+- **Узел триггера** — событие, запускающее рабочий процесс: новое сообщение, нажатие кнопки, обновление статуса и др.
+- **Узел действия** — логика после триггера: отправка сообщения в чат, создание задачи, добавление записи в БД и т.д.
+
+
+
+*Визуальный редактор n8n*
+
+
+## Что можно автоматизировать
+
+- **Уведомления из внешних систем** — пересылайте алерты из мониторинга, CI/CD, CRM и других сервисов в чаты Пачки
+- **Бот-ассистент** — подключите AI-агент, который ищет по сообщениям и отвечает на вопросы команды
+- **Задачи по сообщениям** — автоматически создавайте задачи из сообщений с определённым тегом или реакцией
+- **Согласование** — отправляйте кнопки «Одобрить / Отклонить» и обрабатывайте ответ в workflow
+- **Онбординг** — приветствуйте новых сотрудников личным сообщением от бота
+- **Синхронизация данных** — экспортируйте сообщения, задачи и события в Google Sheets, Notion или другие сервисы
+- **Мониторинг и алерты** — проверяйте состояние сервисов по расписанию и отправляйте алерт в чат при сбое
+
+## Расширение Пачки
+
+Расширение Пачки для n8n предоставляет два узла:
+
+| Узел | Тип | Описание |
+|------|-----|----------|
+| **Pachca** | Действие | 18 ресурсов и более 60 операций для работы с API |
+| **Pachca Trigger** | Триггер | 16 типов событий через вебхуки |
+
+- [18 ресурсов](/guides/n8n/resources) — Сообщения, чаты, пользователи, задачи, формы, поиск и другие
+- [Триггер с вебхуком](/guides/n8n/trigger) — 16 типов событий с авторегистрацией вебхука
+- [AI-агент](/guides/n8n/advanced) — Используйте Pachca как инструмент AI Agent
+- [Автопагинация](/guides/n8n/resources) — Cursor-based пагинация с Return All / Limit
+
+
+## Что нового в v2.0
+
+Версия 2.0 — полностью автогенерируемая из OpenAPI-спецификации с сохранением 100% обратной совместимости с v1.
+
+- **7 новых ресурсов:** Chat Member, Custom Property, Read Member, Link Preview, Search, Chat Export, Security
+- **Курсорная пагинация** с Return All / Limit вместо ручного `per`/`page`
+- **AI Tool Use** — `usableAsTool: true` для использования с AI Agent
+- **Trigger-нода** с авторегистрацией вебхука через Bot ID и 16 типами событий
+- **Поисковые выпадающие списки** для Chat ID и User ID
+- **Загрузка файлов** через S3 с поддержкой URL и Binary Data
+- **Полнотекстовый поиск** по сообщениям, чатам и пользователям
+- **Журнал безопасности** — аудит действий пользователей
+
+> Все существующие workflow на v1 продолжают работать без изменений. Подробнее — в разделе [Миграция с v1](/guides/n8n/migration).
+
diff --git a/apps/docs/public/guides/n8n/resources.md b/apps/docs/public/guides/n8n/resources.md
new file mode 100644
index 00000000..73d6f2bc
--- /dev/null
+++ b/apps/docs/public/guides/n8n/resources.md
@@ -0,0 +1,363 @@
+
+# Ресурсы и операции
+
+В расширении Пачки каждый узел **Pachca** работает по модели **Resource → Operation**: вы выбираете ресурс (например, Message) и операцию над ним (например, Create).
+
+
+
+*Выбор ресурса в узле Pachca*
+
+
+Для каждого ресурса доступен свой набор операций.
+
+
+
+*Операции для ресурса Message*
+
+
+## Список ресурсов
+
+| # | Ресурс | Операций | Описание | Только v2 |
+|---|--------|:---:|----------|:---:|
+| 1 | [Message](#message) | 7 | Сообщения: создание, редактирование, удаление, закрепление | |
+| 2 | [Chat](#chat) | 6 | Чаты: создание, обновление, архивация | |
+| 3 | [Chat Member](#chat-member) | 7 | Участники чата: добавление, удаление, роли, теги | да |
+| 4 | [User](#user) | 10 | Сотрудники: CRUD, аватар, статус | |
+| 5 | [Group Tag](#group-tag) | 6 | Теги сотрудников: CRUD, список пользователей | |
+| 6 | [Thread](#thread) | 2 | Треды: создание, получение | |
+| 7 | [Reaction](#reaction) | 3 | Реакции: создание, удаление, список | |
+| 8 | [Profile](#profile) | 7 | Мой профиль: информация, аватар, статус | |
+| 9 | [Task](#task) | 5 | Задачи: полный CRUD | |
+| 10 | [Bot](#bot) | 3 | Боты: обновление, события, удаление событий | |
+| 11 | [File](#file) | 1 | Загрузка файлов через S3 | |
+| 12 | [Form](#form) | 1 | Модальные формы | |
+| 13 | [Custom Property](#custom-property) | 1 | Дополнительные поля | да |
+| 14 | [Read Member](#read-member) | 1 | Список прочитавших сообщение | да |
+| 15 | [Link Preview](#link-preview) | 1 | Разворачивание ссылок | да |
+| 16 | [Search](#search) | 3 | Полнотекстовый поиск | да |
+| 17 | [Chat Export](#chat-export) | 2 | Экспорт сообщений из чатов | да |
+| 18 | [Security](#security) | 1 | Журнал безопасности | да |
+
+> **Внимание:** Для некоторых операций требуются скоупы, которые доступны только определённым ролям (администратор, владелец). При создании персонального токена отображаются только скоупы, доступные вашей роли. Подробнее — в разделе [Авторизация](/api/authorization).
+
+
+---
+
+## Message
+
+Сообщения: создание, получение, редактирование, удаление, закрепление и открепление.
+
+| Операция | API |
+|----------|-----|
+| Create | [Создание сообщения](POST /messages) |
+| Get Many | [Список сообщений чата](GET /messages) |
+| Get | [Информация о сообщении](GET /messages/{id}) |
+| Update | [Редактирование сообщения](PUT /messages/{id}) |
+| Delete | [Удаление сообщения](DELETE /messages/{id}) |
+| Pin | [Закрепление сообщения](POST /messages/{id}/pin) |
+| Unpin | [Открепление сообщения](DELETE /messages/{id}/pin) |
+
+**Ключевые параметры Create:** `entityId` (ID чата или пользователя), `content` (текст, Markdown), `entityType` (discussion, user, thread), `files`, `buttons`, `parentMessageId`.
+
+**Сортировка в Get Many:** параметры `sort` (по умолчанию `id`) и `order` (`asc` / `desc`) определяют порядок выдачи сообщений.
+
+
+
+*Настройка Message → Get Many*
+
+
+---
+
+## Chat
+
+Чаты: создание, получение, обновление, архивация и разархивация.
+
+| Операция | API |
+|----------|-----|
+| Create | [Создание чата](POST /chats) |
+| Get Many | [Список чатов](GET /chats) |
+| Get | [Информация о чате](GET /chats/{id}) |
+| Update | [Обновление чата](PUT /chats/{id}) |
+| Archive | [Архивация чата](PUT /chats/{id}/archive) |
+| Unarchive | [Разархивация чата](PUT /chats/{id}/unarchive) |
+
+**Сортировка в Get Many:** параметры `sort` (`id` или `last_message_at`) и `order` (`asc` / `desc`). Также доступны фильтры `availability`, `lastMessageAtAfter`, `lastMessageAtBefore`.
+
+---
+
+## Chat Member
+
+Управление участниками чата: добавление, удаление, изменение ролей, управление тегами.
+
+> В v1 эти операции были частью ресурса Chat. В v2 они выделены в отдельный ресурс Chat Member.
+
+
+| Операция | API |
+|----------|-----|
+| Get Many | [Список участников чата](GET /chats/{id}/members) |
+| Create | [Добавление пользователей в чат](POST /chats/{id}/members) |
+| Delete | [Удаление пользователя из чата](DELETE /chats/{id}/members/{user_id}) |
+| Update | [Изменение роли участника](PUT /chats/{id}/members/{user_id}) |
+| Leave | [Выход из чата](DELETE /chats/{id}/leave) |
+| Add Group Tags | [Добавление тегов к чату](POST /chats/{id}/group_tags) |
+| Remove Group Tags | [Удаление тегов из чата](DELETE /chats/{id}/group_tags/{tag_id}) |
+
+---
+
+## User
+
+Сотрудники: полный CRUD, получение и управление статусом.
+
+| Операция | API |
+|----------|-----|
+| Create | [Создание сотрудника](POST /users) |
+| Get Many | [Список сотрудников](GET /users) |
+| Get | [Информация о сотруднике](GET /users/{id}) |
+| Update | [Обновление сотрудника](PUT /users/{id}) |
+| Delete | [Удаление сотрудника](DELETE /users/{id}) |
+| Update Avatar | [Обновление аватара](PUT /users/{user_id}/avatar) |
+| Delete Avatar | [Удаление аватара](DELETE /users/{user_id}/avatar) |
+| Get Status | [Получение статуса](GET /users/{user_id}/status) |
+| Update Status | [Обновление статуса](PUT /users/{user_id}/status) |
+| Delete Status | [Удаление статуса](DELETE /users/{user_id}/status) |
+
+---
+
+## Group Tag
+
+Теги (группы) сотрудников: создание, обновление, удаление, список пользователей.
+
+| Операция | API |
+|----------|-----|
+| Create | [Создание тега](POST /group_tags) |
+| Get Many | [Список тегов](GET /group_tags) |
+| Get | [Информация о теге](GET /group_tags/{id}) |
+| Update | [Обновление тега](PUT /group_tags/{id}) |
+| Delete | [Удаление тега](DELETE /group_tags/{id}) |
+| Get Many Users | [Список пользователей тега](GET /group_tags/{id}/users) |
+
+---
+
+## Thread
+
+Треды (комментарии к сообщениям): создание и получение.
+
+| Операция | API |
+|----------|-----|
+| Create | [Создание треда](POST /messages/{id}/thread) |
+| Get | [Информация о треде](GET /threads/{id}) |
+
+---
+
+## Reaction
+
+Реакции на сообщения: создание, удаление, список.
+
+| Операция | API |
+|----------|-----|
+| Create | [Добавление реакции](POST /messages/{id}/reactions) |
+| Delete | [Удаление реакции](DELETE /messages/{id}/reactions) |
+| Get Many | [Список реакций](GET /messages/{id}/reactions) |
+
+---
+
+## Profile
+
+Профиль текущего пользователя: информация, статус, информация о токене.
+
+| Операция | API |
+|----------|-----|
+| Get | [Информация о профиле](GET /profile) |
+| Get Info | [Информация о токене](GET /oauth/token/info) |
+| Update Avatar | [Обновление аватара](PUT /profile/avatar) |
+| Delete Avatar | [Удаление аватара](DELETE /profile/avatar) |
+| Get Status | [Получение статуса](GET /profile/status) |
+| Update Status | [Обновление статуса](PUT /profile/status) |
+| Delete Status | [Удаление статуса](DELETE /profile/status) |
+
+**Загрузка аватара:** операция Update Avatar принимает бинарные данные из предыдущего узла (например, HTTP Request или Read Binary File). В поле **Input Binary Field** укажите имя бинарного свойства (по умолчанию `data`).
+
+---
+
+## Task
+
+Задачи (напоминания): полный CRUD.
+
+| Операция | API |
+|----------|-----|
+| Create | [Создание задачи](POST /tasks) |
+| Get Many | [Список задач](GET /tasks) |
+| Get | [Информация о задаче](GET /tasks/{id}) |
+| Update | [Обновление задачи](PUT /tasks/{id}) |
+| Delete | [Удаление задачи](DELETE /tasks/{id}) |
+
+**Типы задач:** `call`, `email`, `event`, `meeting`, `reminder`.
+
+---
+
+## Bot
+
+Управление ботами: обновление настроек, получение и удаление событий.
+
+| Операция | API |
+|----------|-----|
+| Update | [Обновление бота](PUT /bots/{id}) |
+| Get Many Events | [Список событий бота](GET /webhooks/events) |
+| Remove Events | [Удаление событий](DELETE /webhooks/events/{id}) |
+
+---
+
+## File
+
+Загрузка файлов через двухшаговый S3 upload.
+
+| Операция | API |
+|----------|-----|
+| Create | [Загрузка файла](POST /uploads) |
+
+Подробнее — в разделе [Продвинутые функции](/guides/n8n/advanced#zagruzka-fajlov).
+
+---
+
+## Form
+
+Модальные формы (представления).
+
+| Операция | API |
+|----------|-----|
+| Create | [Открытие представления](POST /views/open) |
+
+Подробнее — в разделе [Продвинутые функции](/guides/n8n/advanced#formy) и в [документации форм](/guides/forms/overview).
+
+---
+
+## Custom Property
+
+Дополнительные поля пространства.
+
+| Операция | API |
+|----------|-----|
+| Get | [Список дополнительных полей](GET /custom_properties) |
+
+---
+
+## Read Member
+
+Список пользователей, прочитавших сообщение.
+
+| Операция | API |
+|----------|-----|
+| Get Many | [Список прочитавших](GET /messages/{id}/read_member_ids) |
+
+---
+
+## Link Preview
+
+Разворачивание ссылок в сообщениях.
+
+| Операция | API |
+|----------|-----|
+| Create | [Создание превью ссылки](POST /messages/{id}/link_previews) |
+
+Подробнее — в [документации разворачивания ссылок](/guides/link-previews).
+
+---
+
+## Search
+
+Полнотекстовый поиск по сообщениям, чатам и пользователям.
+
+| Операция | API |
+|----------|-----|
+| Get Many Chats | [Поиск чатов](GET /search/chats) |
+| Get Many Messages | [Поиск сообщений](GET /search/messages) |
+| Get Many Users | [Поиск пользователей](GET /search/users) |
+
+**Обязательный параметр:** `query` — строка поиска.
+
+---
+
+## Chat Export
+
+Экспорт сообщений из чатов: запрос экспорта и скачивание архива.
+
+| Операция | API |
+|----------|-----|
+| Create | [Запрос экспорта](POST /chats/exports) |
+| Get | [Скачивание архива](GET /chats/exports/{id}) |
+
+**Ключевые параметры Create:** `startAt` (дата начала, YYYY-MM-DD), `endAt` (дата окончания), `webhookUrl` (URL для уведомления о готовности).
+
+**Дополнительные параметры:** `chatIds` (экспорт конкретных чатов, до 50), `skipChatsFile` (не создавать chats.json).
+
+Экспорт выполняется асинхронно. После завершения Пачка отправит вебхук на указанный `webhookUrl` с `export_id`. Используйте операцию **Get** для скачивания готового архива.
+
+Подробнее — в разделе [Продвинутые функции](/guides/n8n/advanced#eksport-soobshhenij) и в [документации экспорта](/guides/export).
+
+---
+
+## Security
+
+Журнал безопасности: отслеживание действий пользователей.
+
+| Операция | API |
+|----------|-----|
+| Get Many | [Список событий аудита](GET /audit_events) |
+
+**Фильтры:** `eventKey`, `actorId`, `actorType`, `entityId`, `entityType`, `startTime`, `endTime`.
+
+Подробнее — в [документации журнала аудита](/guides/audit-events).
+
+---
+
+## Пагинация
+
+Все операции Get Many поддерживают автоматическую курсорную пагинацию:
+
+- **Return All** = `true` — получить все результаты автоматически, переключаясь между страницами
+- **Return All** = `false` — получить не более **Limit** результатов (по умолчанию 50)
+
+
+
+*Return All и Limit для операции Get Many*
+
+
+n8n автоматически отправляет повторные запросы с курсором до получения всех данных.
+
+> Для операций со списками (Get Many) рекомендуется использовать **Return All = false** с разумным **Limit**, чтобы избежать долгих запросов при большом объёме данных.
+
+
+---
+
+## Simplify
+
+Операции получения данных (Get, Get Many) поддерживают переключатель **Simplify** (включён по умолчанию). Когда Simplify включён, из ответа API возвращаются только ключевые поля — остальные отбрасываются.
+
+| Ресурс | Ключевые поля |
+|--------|---------------|
+| Message | `id`, `entity_id`, `chat_id`, `content`, `user_id`, `created_at` |
+| Chat | `id`, `name`, `channel`, `public`, `members_count`, `created_at` |
+| User | `id`, `first_name`, `last_name`, `nickname`, `email`, `role`, `suspended` |
+| Task | `id`, `content`, `kind`, `status`, `priority`, `due_at`, `created_at` |
+| Bot | `id`, `name`, `created_at` |
+| Group Tag | `id`, `name`, `users_count` |
+| Reaction | `id`, `code`, `user_id`, `created_at` |
+| Chat Export | `id`, `status`, `created_at` |
+
+Чтобы получить все поля ответа — выключите **Simplify**.
+
+> Simplify доступен только в v2. В v1 workflow всегда возвращают полный ответ API.
+
+
+---
+
+## Поисковые выпадающие списки
+
+
+
+*Поиск чата по имени в поле Chat ID*
+
+
+Для поля **Chat ID** доступен поиск по имени: начните вводить текст, и n8n покажет подходящие результаты из вашего пространства Пачки.
+
+Поиск вызывает API-эндпоинт [Поиск чатов](GET /search/chats) и работает только с валидным `Access Token` в Credentials.
diff --git a/apps/docs/public/guides/n8n/setup.md b/apps/docs/public/guides/n8n/setup.md
new file mode 100644
index 00000000..6a38dd4f
--- /dev/null
+++ b/apps/docs/public/guides/n8n/setup.md
@@ -0,0 +1,166 @@
+
+# Начало работы
+
+## Установка
+
+
+ ### Шаг 1. Установка n8n
+
+Два способа установки:
+
+ **С помощью команды** (требуется Node.js 22+):
+
+ ```bash
+ npx n8n
+ ```
+
+ **С помощью Docker:**
+
+ ```bash
+ docker volume create n8n_data
+
+ docker run -it --rm \
+ --name n8n \
+ -p 5678:5678 \
+ -e GENERIC_TIMEZONE="Europe/Moscow" \
+ -e TZ="Europe/Moscow" \
+ -e N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true \
+ -e N8N_RUNNERS_ENABLED=true \
+ -v n8n_data:/home/node/.n8n \
+ docker.n8n.io/n8nio/n8n
+ ```
+
+ Подробные инструкции — в [официальной документации n8n](https://docs.n8n.io/hosting/) и на [GitHub](https://github.com/n8n-io/n8n).
+
+ После запуска откройте `http://localhost:5678` и настройте аккаунт владельца (Owner Account), указав почту, имя и пароль.
+
+ 
+
+*Настройка Owner Account*
+
+
+ После входа вы увидите главный экран n8n с пустым списком workflow.
+
+ 
+
+*Главная страница n8n*
+
+
+ ### Шаг 2. Установка расширения Пачки
+
+Расширение доступно на [npm](https://www.npmjs.com/package/n8n-nodes-pachca) и [GitHub](https://github.com/pachca/openapi/tree/main/integrations/n8n).
+
+ Три способа установки:
+
+ 1. Зайти в **Settings** > **Community nodes** и добавить `n8n-nodes-pachca` (рекомендуется)
+ 2. Выполнить команду `npm i n8n-nodes-pachca` в директории n8n
+ 3. Следовать README-инструкции на [GitHub](https://github.com/pachca/openapi/tree/main/integrations/n8n)
+
+
+## Настройка Credentials
+
+
+ ### Шаг 1. Создание Credentials
+
+Credentials — данные для авторизации. Перейдите в **Credentials** и нажмите **Add Credential**.
+
+ 
+
+*Список Credentials*
+
+
+ Найдите **Pachca API** в списке и заполните поля:
+
+ 
+
+*Поиск Pachca API в списке Credentials*
+
+
+ | Поле | Обязательное | Описание |
+ |------|:---:|----------|
+ | **Base URL** | нет | Базовый URL API. По умолчанию `https://api.pachca.com/api/shared/v1`. Менять только для on-premise |
+ | **Access Token** | да | Токен доступа к API |
+ | **Bot ID** | нет | ID бота — нужен для авторегистрации вебхука в [Pachca Trigger](/guides/n8n/trigger). Автоопределяется из токена бота |
+ | **Signing Secret** | нет | Секрет для верификации входящих webhook-запросов (HMAC-SHA256) |
+ | **Webhook Allowed IPs** | нет | Список IP-адресов через запятую, с которых разрешены входящие вебхуки. Пачка отправляет с `37.200.70.177`. Пустое поле — разрешить все |
+
+ 
+
+*Форма Pachca API Credentials*
+
+
+ ### Шаг 2. Где получить токен
+
+В Пачке доступны два типа токенов:
+
+ - **Персональный токен** — доступен в разделе **Автоматизации** > **Интеграции** > **API**
+ - **Токен бота** — доступен в настройках бота на вкладке **API**
+
+ Доступные операции зависят от [скоупов](/api/authorization#skoupy) токена, а не от его типа. Подробнее — в разделе [Авторизация](/api/authorization).
+
+ После заполнения полей нажмите **Test** — n8n проверит подключение вызовом [Информация о токене](GET /oauth/token/info). При успехе вы увидите подтверждение.
+
+ 
+
+*Connection tested successfully*
+
+
+ > Если тест не проходит — проверьте правильность токена и доступность API. Подробнее — в разделе [Устранение ошибок](/guides/n8n/troubleshooting).
+
+
+## Первый workflow
+
+
+ ### Шаг 1. Создание workflow
+
+Workflow — визуальный редактор, в котором выстраиваются цепочки триггеров и действий. Создайте новый workflow и добавьте триггер **Manual Trigger** для ручного запуска.
+
+ Нажмите **+** на выходе триггера и найдите **Pachca** в списке узлов.
+
+ 
+
+*Поиск узла Pachca*
+
+
+ После добавления узла на канвасе появится цепочка: Manual Trigger → Pachca.
+
+ 
+
+*Pachca на канвасе*
+
+
+ ### Шаг 2. Настройка и запуск
+
+Дважды кликните на узел **Pachca** и настройте:
+
+ - **Credential:** выберите созданный Pachca API
+ - **Resource:** Message
+ - **Operation:** Create
+ - **Entity ID:** ID чата (число)
+ - **Content:** текст сообщения
+
+ 
+
+*Настройка Message → Create*
+
+
+ > **Внимание:** Перед отправкой сообщения [добавьте бота в чат](/guides/bots#dostupy-bota-k-chatam-i-soobscheniyam).
+
+
+ Закройте панель настроек и нажмите **Execute Workflow**. При успехе узлы подсветятся зелёным и покажут количество обработанных элементов.
+
+ 
+
+*Workflow выполнен*
+
+
+ Откройте узел Pachca, чтобы увидеть ответ API с данными созданного сообщения.
+
+ 
+
+*Ответ API в панели Output*
+
+
+ Больше примеров — в разделе [Примеры workflow](/guides/n8n/workflows).
+
+
diff --git a/apps/docs/public/guides/n8n/trigger.md b/apps/docs/public/guides/n8n/trigger.md
new file mode 100644
index 00000000..ce8aec35
--- /dev/null
+++ b/apps/docs/public/guides/n8n/trigger.md
@@ -0,0 +1,169 @@
+
+# Триггер
+
+Узел **Pachca Trigger** запускает workflow при наступлении события в Пачке — новое сообщение, нажатие кнопки, отправка формы, изменение состава команды и др.
+
+## Поддерживаемые события
+
+### Сообщения и чаты
+
+| Событие | Значение | Описание |
+|---------|----------|----------|
+| Новое сообщение | `new_message` | Создание сообщения в чате |
+| Сообщение изменено | `message_updated` | Редактирование существующего сообщения |
+| Сообщение удалено | `message_deleted` | Удаление сообщения |
+| Новая реакция | `new_reaction` | Добавление реакции к сообщению |
+| Реакция удалена | `reaction_deleted` | Удаление реакции с сообщения |
+| Участник добавлен | `chat_member_added` | Добавление участника в чат |
+| Участник удалён | `chat_member_removed` | Удаление участника из чата |
+
+### Интерактивные элементы
+
+| Событие | Значение | Описание |
+|---------|----------|----------|
+| Нажатие кнопки | `button_pressed` | Клик по Data-кнопке в сообщении |
+| Отправка формы | `form_submitted` | Отправка модальной формы |
+| Ссылка отправлена | `link_shared` | Бот может развернуть превью ссылки |
+
+### Сотрудники
+
+| Событие | Значение | Описание |
+|---------|----------|----------|
+| Приглашение сотрудника | `company_member_invite` | Отправлено приглашение новому сотруднику |
+| Подтверждение регистрации | `company_member_confirm` | Сотрудник подтвердил регистрацию |
+| Активация сотрудника | `company_member_activate` | Сотрудник активирован |
+| Обновление сотрудника | `company_member_update` | Изменение данных сотрудника |
+| Приостановка сотрудника | `company_member_suspend` | Сотрудник приостановлен |
+| Удаление сотрудника | `company_member_delete` | Сотрудник удалён |
+
+### Wildcard
+
+| Событие | Значение | Описание |
+|---------|----------|----------|
+| Все события | `*` | Получать все типы событий |
+
+
+
+*16 типов событий в Pachca Trigger*
+
+
+> **Внимание:** Бот получает события только из чатов, в которых он состоит. Убедитесь, что бот добавлен в нужные чаты.
+
+
+## Настройка
+
+Добавьте узел **Pachca Trigger** в workflow — найдите его через поиск в панели узлов.
+
+
+
+*Поиск Pachca Trigger*
+
+
+### Автоматический режим (рекомендуется)
+
+
+
+*Настройка Pachca Trigger*
+
+
+При наличии **Bot ID** в [Credentials](/guides/n8n/setup#sozdanie-credentials) вебхук регистрируется автоматически:
+
+
+ ### Шаг 1. Укажите Bot ID в Credentials
+
+Откройте Pachca API Credentials и заполните поле **Bot ID** — это ID вашего бота в Пачке.
+
+
+ ### Шаг 2. Добавьте Pachca Trigger
+
+Создайте новый workflow и добавьте узел **Pachca Trigger**. Выберите нужный тип события.
+
+
+ ### Шаг 3. Активируйте workflow
+
+Нажмите **Activate**. n8n автоматически вызовет [Обновление бота](PUT /bots/{id}) и зарегистрирует webhook URL в настройках бота.
+
+
+При деактивации workflow вебхук автоматически удаляется.
+
+**Автоматическая регистрация вебхука**
+
+```mermaid
+sequenceDiagram
+ participant n8n
+ participant Pachca as Pachca API
+ participant Bot as Бот в Пачке
+
+ Note over n8n: Активация workflow
+ n8n->>Pachca: PUT /bots/{id}
webhook_url=n8n_url
+ Pachca->>Bot: Webhook зарегистрирован
+
+ Note over Bot: Событие в чате
+ Bot->>Pachca: Новое сообщение
+ Pachca->>n8n: POST webhook_url
+ payload + signature
+ Note over n8n: Проверка подписи
+ n8n->>n8n: Запуск workflow
+
+ Note over n8n: Деактивация workflow
+ n8n->>Pachca: PUT /bots/{id}
webhook_url=null
+ Pachca->>Bot: Webhook удалён
+```
+
+
+### Ручной режим
+
+Если **Bot ID** не указан в Credentials:
+
+1. Добавьте узел **Pachca Trigger** в workflow
+2. Скопируйте сгенерированный **Webhook URL** из настроек узла
+3. Вставьте URL в настройки бота в Пачке (раздел **Webhook URL**)
+4. Активируйте workflow
+
+## Безопасность
+
+### Проверка подписи
+
+Для защиты от поддельных запросов добавьте **Signing Secret** бота в [Credentials](/guides/n8n/setup#sozdanie-credentials). Trigger автоматически проверяет HMAC-SHA256 подпись каждого входящего запроса через заголовок `pachca-signature` и отклоняет невалидные.
+
+Подробнее о механизме подписи — в разделе [Исходящие вебхуки](/guides/webhook#bezopasnost).
+
+> Рекомендуется всегда использовать Signing Secret в продакшене для защиты от несанкционированных запросов.
+
+
+### Ограничение по IP
+
+Укажите **Webhook Allowed IPs** в [Credentials](/guides/n8n/setup#sozdanie-credentials) — через запятую список IP-адресов, с которых принимаются вебхуки. Пачка отправляет вебхуки с IP `37.200.70.177`.
+
+Если поле пустое — проверка IP отключена и запросы принимаются с любого адреса.
+
+> **Внимание:** Ограничение по IP — дополнительная мера. Заголовок `x-forwarded-for` может быть подменён, если n8n не стоит за доверенным reverse proxy. Используйте вместе с Signing Secret.
+
+
+### Защита от повторов
+
+Trigger автоматически отклоняет события старше **5 минут** (по полю `webhook_timestamp` в теле запроса). Это защищает от replay-атак — повторной отправки перехваченного запроса.
+
+## Фильтрация событий
+
+Выберите конкретный тип события для фильтрации — workflow будет запускаться только при совпадении. Можно выбрать только один тип события на один узел Trigger.
+
+> Используйте **All Events** (`*`) и фильтруйте в последующих узлах (например, через IF или Switch), если нужна сложная логика маршрутизации по типу события или обработка нескольких типов в одном workflow.
+
+
+## Пример: бот-эхо
+
+
+
+*Workflow с триггером и действием Pachca*
+
+
+Простой workflow, который отвечает на каждое новое сообщение:
+
+1. **Pachca Trigger** — событие `New Message`
+2. **IF** — условие: `message.user_id` не равен ID бота (чтобы бот не отвечал сам себе)
+3. **Pachca** — операция `Message > Create`, `entityId` = ID чата из триггера, `content` = текст ответа
+
+> ID бота — это `user_id` из ответа [Информация о профиле](GET /profile) при авторизации токеном бота.
+
+
+Больше готовых сценариев — в разделе [Примеры workflow](/guides/n8n/workflows).
diff --git a/apps/docs/public/guides/n8n/troubleshooting.md b/apps/docs/public/guides/n8n/troubleshooting.md
new file mode 100644
index 00000000..f5d6b0a6
--- /dev/null
+++ b/apps/docs/public/guides/n8n/troubleshooting.md
@@ -0,0 +1,140 @@
+
+# Устранение ошибок
+
+## Ошибки авторизации
+
+### 401 Unauthorized — неверный токен
+
+
+
+*Ошибка авторизации*
+
+
+**Причина:** указан некорректный или просроченный Access Token.
+
+**Решение:**
+
+1. Откройте **Credentials** и проверьте значение **Access Token**
+2. Убедитесь, что токен скопирован целиком, без лишних пробелов
+3. Нажмите **Test** — при ошибке создайте новый токен в Пачке
+4. Для бот-токена: **Настройки бота** → вкладка **API** → скопируйте токен
+5. Для персонального токена: **Автоматизации** → **Интеграции** → **API**
+
+### 403 Forbidden — недостаточно прав
+
+**Причина:** операция требует более высокий уровень доступа, чем предоставляет текущий токен.
+
+**Решение:**
+
+Доступ к операциям определяется [скоупами](/api/authorization#skoupy) токена, а не его типом. Убедитесь, что ваш токен включает нужные скоупы:
+
+| Операция | Требуемые скоупы |
+|----------|-----------------|
+| Управление сотрудниками (User > Create/Update/Delete) | `users:write` (доступен администраторам и владельцам) |
+| Журнал безопасности (Security > Get Many) | `audit_events:read` (доступен администраторам и владельцам) |
+| Управление тегами (Group Tag > Create/Update/Delete) | `group_tags:write` (доступен администраторам и владельцам) |
+| Отправка сообщений, чаты, задачи | `messages:write`, `chats:write`, `tasks:write` |
+
+Подробнее — в разделе [Авторизация](/api/authorization).
+
+---
+
+## Ошибки лимитов
+
+### 429 Too Many Requests — превышение лимита
+
+**Причина:** слишком много запросов за единицу времени.
+
+**Лимиты API:**
+
+| Тип операции | Лимит |
+|-------------|-------|
+| Отправка сообщений | ~4 запроса/сек на чат |
+| Остальные операции | ~50 запросов/сек |
+
+**Решение:**
+
+1. Добавьте узел **Wait** между операциями для замедления
+2. Используйте **Batching** в настройках узла Pachca (Additional Fields → Request Options → Batching)
+3. Для массовых операций используйте **Return All** = `false` с ограниченным **Limit**
+
+> При получении 429 или 5xx расширение автоматически повторяет запросы с экспоненциальной задержкой и jitter (до 5 попыток). Учитывается заголовок `Retry-After` из ответа API.
+
+
+---
+
+## Ошибки триггера
+
+### Вебхук не приходит
+
+**Возможные причины и решения:**
+
+1. **Бот не добавлен в чат** — бот получает события только из чатов, в которых он состоит. Добавьте бота в нужный канал
+2. **Workflow не активирован** — нажмите **Activate** в правом верхнем углу. Неактивные workflow не принимают вебхуки
+3. **Bot ID не указан** — при отсутствии Bot ID в Credentials авторегистрация не работает. Укажите Bot ID или настройте вебхук вручную
+4. **n8n недоступен извне** — при локальной установке Пачка не может отправить вебхук на `localhost`. Используйте туннель (ngrok, Cloudflare Tunnel) или разверните n8n на сервере с публичным IP
+
+### Ошибка подписи (Signature Mismatch)
+
+**Причина:** Signing Secret в Credentials не совпадает с секретом бота в Пачке.
+
+**Решение:** скопируйте Signing Secret из настроек бота в Пачке (вкладка **API**) и вставьте в Credentials.
+
+---
+
+## Ошибки данных
+
+### Entity ID не найден
+
+**Причина:** указан несуществующий ID чата, пользователя или сообщения.
+
+**Решение:**
+
+- Для чатов: используйте **Search** (Search > Get Many Chats) для поиска по имени
+- Для пользователей: используйте **Search** (Search > Get Many Users) для поиска
+- Для сообщений: убедитесь, что сообщение не было удалено
+
+### Бот не может отправить сообщение
+
+**Причина:** бот не является участником целевого чата.
+
+**Решение:** добавьте бота в чат. Бот может отправлять сообщения только в те чаты, в которых он состоит.
+
+---
+
+## Ошибки форм
+
+### Форма не открывается
+
+**Причина:** `trigger_id` из события `button_pressed` действителен только **3 секунды**. Если между получением вебхука и вызовом [Открытие представления](POST /views/open) проходит больше времени — форма не откроется.
+
+**Решение:**
+
+1. Убедитесь, что узел **Form > Create** стоит сразу после триггера, без долгих операций между ними
+2. Не используйте узел **Wait** между получением `trigger_id` и открытием формы
+3. Если нужна дополнительная логика — выполняйте её после отправки формы, а не до
+
+Подробнее о формах — в [документации форм](/guides/forms/overview).
+
+---
+
+## Ошибки пагинации
+
+### Возвращаются не все данные
+
+**Причина:** API возвращает данные постранично. Если **Return All** выключен, вы получаете только первую страницу (по умолчанию до 50 записей).
+
+**Решение:**
+
+1. Включите **Return All** = `true` в настройках узла — n8n автоматически пройдёт по всем страницам через cursor-based пагинацию
+2. Если нужен ограниченный набор — используйте **Return All** = `false` и укажите нужное число в поле **Limit**
+3. Для больших объёмов данных учитывайте [лимиты API](#oshibki-limitov) — при Return All n8n делает несколько запросов последовательно
+
+---
+
+## Общие рекомендации
+
+1. **Включите Retry On Fail** в настройках узла (Settings → Retry On Fail) для автоматического повтора при временных ошибках
+2. **Используйте Error Trigger** для обработки ошибок в отдельном workflow — отправляйте уведомление об ошибке в специальный канал
+3. **Проверяйте Credentials** кнопкой **Test** перед запуском workflow
+4. **Используйте Execute Step** для отладки узлов по одному, не запуская весь workflow
diff --git a/apps/docs/public/guides/n8n/workflows.md b/apps/docs/public/guides/n8n/workflows.md
new file mode 100644
index 00000000..bcc80fe7
--- /dev/null
+++ b/apps/docs/public/guides/n8n/workflows.md
@@ -0,0 +1,208 @@
+
+# Примеры workflow
+
+Ниже — готовые сценарии, которые можно воспроизвести в n8n. Каждый пример использует узлы **Pachca** и **Pachca Trigger** из расширения.
+
+## Приветствие нового сотрудника
+
+Автоматическое приветственное сообщение при добавлении сотрудника в канал.
+
+
+
+*Приветствие нового сотрудника*
+
+
+**Как работает:**
+
+1. **Pachca Trigger** — событие `New Message`
+2. **IF** — проверка: сообщение системное (тип `user_joined`)
+3. **Pachca** (Message > Create) — отправка приветствия в тот же чат
+
+**Что можно добавить:** отправка личного сообщения с полезными ссылками, добавление сотрудника в рабочие каналы.
+
+
+ Готовый workflow для импорта в n8n
+
+
+> После импорта замените Credentials на свои во всех узлах Pachca.
+
+
+---
+
+## Пересылка сообщений между каналами
+
+Автоматическая пересылка сообщений из одного чата в другой.
+
+
+
+*Пересылка сообщений между каналами*
+
+
+**Как работает:**
+
+1. **Pachca Trigger** — событие `New Message` (в исходном канале)
+2. **IF** — фильтрация: пропускать сервисные сообщения, пересылать только пользовательские
+3. **Pachca** (Message > Create) — отправка в целевой канал с указанием автора
+
+**Когда полезно:** дублирование важных новостей в общие каналы, агрегация обсуждений.
+
+
+ Готовый workflow для импорта в n8n
+
+
+> После импорта замените Credentials, `CHAT_ID_ИСТОЧНИКА` и `CHAT_ID_ЦЕЛЕВОГО` на свои значения.
+
+
+---
+
+## Напоминание о задачах
+
+Ежедневная проверка просроченных задач с уведомлением в чат.
+
+
+
+*Напоминание о просроченных задачах*
+
+
+**Как работает:**
+
+1. **Schedule Trigger** — ежедневный запуск (например, в 10:00)
+2. **Pachca** (Task > Get Many) — получение списка задач
+3. **IF** — фильтр: только просроченные (дедлайн < сегодня)
+4. **Pachca** (Message > Create) — уведомление в чат со списком задач
+
+**Что можно добавить:** личные уведомления ответственным, группировка по проектам.
+
+
+ Готовый workflow для импорта в n8n
+
+
+> После импорта замените Credentials и `CHAT_ID` во всех узлах Pachca.
+
+
+---
+
+## Согласование с кнопками
+
+Запрос на согласование через сообщение с кнопками и обработка ответа.
+
+
+
+*Согласование с Data-кнопками*
+
+
+**Как работает:**
+
+1. **Manual Trigger** или **Webhook** — инициация запроса
+2. **Pachca** (Message > Create) — отправка сообщения с кнопками «Согласовать» / «Отклонить»
+3. **Pachca Trigger** (в отдельном workflow) — событие `Button Pressed`
+4. **Switch** — маршрутизация по `data` из нажатой кнопки
+5. **Pachca** (Message > Update) — обновление исходного сообщения с результатом
+
+**Пример кнопок:**
+
+```json
+[
+ [
+ { "text": "Согласовать", "data": "approve" },
+ { "text": "Отклонить", "data": "reject" }
+ ]
+]
+```
+
+Подробнее о кнопках — в разделе [Продвинутые функции](/guides/n8n/advanced#knopki-v-soobshheniyax).
+
+- [approval.json](/workflows/n8n/approval.json) — Отправка запроса с кнопками
+- [approval-handler.json](/workflows/n8n/approval-handler.json) — Обработка нажатий кнопок
+
+
+> После импорта замените Credentials и `CHAT_ID` во всех узлах Pachca.
+
+
+---
+
+## Мониторинг и алерты
+
+Периодическая проверка состояния и отправка алерта при аномалиях.
+
+
+
+*Мониторинг с отправкой алертов в Пачку*
+
+
+**Как работает:**
+
+1. **Schedule Trigger** — проверка каждые 5 минут
+2. **HTTP Request** — запрос к внешнему API или сервису
+3. **IF** — проверка: статус не 200 или метрика выше порога
+4. **Pachca** (Message > Create) — отправка алерта в канал мониторинга
+
+**Что можно добавить:** проверка нескольких сервисов, графики через разворачивание ссылок, эскалация в личные сообщения.
+
+
+ Готовый workflow для импорта в n8n
+
+
+> После импорта замените Credentials, `CHAT_ID_МОНИТОРИНГА` и URL сервиса во всех узлах Pachca.
+
+
+---
+
+## Заявка на отпуск
+
+Полноценный сценарий с кнопками, формой и согласованием в треде — бот принимает заявку через модальную форму и отправляет на согласование руководителю.
+
+
+
+*Заявка на отпуск: триггер → кнопка → форма → тред → согласование*
+
+
+**Как работает (два workflow):**
+
+**Workflow 1 — Приём заявки:**
+
+1. **Pachca Trigger** — событие `New Message`, фильтр по команде `/отпуск`
+2. **Pachca** (Message > Create) — отправка сообщения с Data-кнопкой «Создать заявку»
+3. **Pachca Trigger** (событие `Button Pressed`) — пользователь нажимает кнопку
+4. **Pachca** (Form > Create) — открытие модальной формы с полями «Дата начала», «Дата окончания», «Комментарий»
+
+**Workflow 2 — Обработка и согласование:**
+
+1. **Pachca Trigger** — событие `Form Submitted`
+2. **Pachca** (Thread > Create) — создание треда с деталями заявки
+3. **Pachca** (Message > Create) — отправка в тред кнопок «Согласовать» / «Отклонить»
+4. **Pachca Trigger** (событие `Button Pressed`) — руководитель нажимает кнопку
+5. **Pachca** (Message > Create) — уведомление сотрудника о результате
+
+**Что задействовано:** триггер, кнопки, формы, треды, условная логика.
+
+> Подробнее о кнопках — в разделе [Кнопки в сообщениях](/guides/n8n/advanced#knopki-v-soobshheniyax), о формах — в разделе [Формы](/guides/n8n/advanced#formy).
+
+
+- [vacation.json](/workflows/n8n/vacation.json) — Приём команды и кнопка заявки
+- [vacation-handler.json](/workflows/n8n/vacation-handler.json) — Форма, тред и согласование
+
+
+> После импорта замените Credentials и `CHAT_ID_HR` во всех узлах Pachca.
+
+
+---
+
+## AI-ассистент
+
+Бот, использующий AI для ответов на вопросы на основе истории чата.
+
+
+
+*AI Agent с инструментами Pachca*
+
+
+**Как работает:**
+
+1. **Pachca Trigger** — событие `New Message`
+2. **AI Agent** — обработка запроса с LLM
+3. **Pachca** (Search > Get Many Messages) — как Tool для поиска информации
+4. **Pachca** (Message > Create) — как Tool для отправки ответа
+
+> Для использования AI Agent необходимо настроить LLM-провайдер (OpenAI, Anthropic и др.) в n8n. Подробнее — в разделе [Продвинутые функции](/guides/n8n/advanced#ai-agent).
+
diff --git a/apps/docs/public/guides/sdk/csharp.md b/apps/docs/public/guides/sdk/csharp.md
index dcfc8a30..825ce660 100644
--- a/apps/docs/public/guides/sdk/csharp.md
+++ b/apps/docs/public/guides/sdk/csharp.md
@@ -1,5 +1,5 @@
-# CSharp
+# C#
[Pachca.Sdk](https://www.nuget.org/packages/Pachca.Sdk) NuGet
@@ -69,15 +69,19 @@ using var client = new PachcaClient("YOUR_TOKEN", "https://custom-api.example.co
| `client.Profile.GetTokenInfoAsync()` | [Информация о токене](/api/profile/get-info) |
| `client.Profile.GetProfileAsync()` | [Информация о профиле](/api/profile/get) |
| `client.Profile.GetStatusAsync()` | [Текущий статус](/api/profile/get-status) |
+| `client.Profile.UpdateProfileAvatarAsync()` | [Загрузка аватара](/api/profile/update-avatar) |
| `client.Profile.UpdateStatusAsync()` | [Новый статус](/api/profile/update-status) |
+| `client.Profile.DeleteProfileAvatarAsync()` | [Удаление аватара](/api/profile/delete-avatar) |
| `client.Profile.DeleteStatusAsync()` | [Удаление статуса](/api/profile/delete-status) |
| `client.Users.CreateUserAsync()` | [Создать сотрудника](/api/users/create) |
| `client.Users.ListUsersAsync()` | [Список сотрудников](/api/users/list) |
| `client.Users.GetUserAsync()` | [Информация о сотруднике](/api/users/get) |
| `client.Users.GetUserStatusAsync()` | [Статус сотрудника](/api/users/get-status) |
| `client.Users.UpdateUserAsync()` | [Редактирование сотрудника](/api/users/update) |
+| `client.Users.UpdateUserAvatarAsync()` | [Загрузка аватара сотрудника](/api/users/update-avatar) |
| `client.Users.UpdateUserStatusAsync()` | [Новый статус сотрудника](/api/users/update-status) |
| `client.Users.DeleteUserAsync()` | [Удаление сотрудника](/api/users/delete) |
+| `client.Users.DeleteUserAvatarAsync()` | [Удаление аватара сотрудника](/api/users/remove-avatar) |
| `client.Users.DeleteUserStatusAsync()` | [Удаление статуса сотрудника](/api/users/remove-status) |
| `client.GroupTags.CreateTagAsync()` | [Новый тег](/api/group-tags/create) |
| `client.GroupTags.ListTagsAsync()` | [Список тегов сотрудников](/api/group-tags/list) |
@@ -137,8 +141,8 @@ using var client = new PachcaClient("YOUR_TOKEN", "https://custom-api.example.co
using Pachca.Sdk;
// Список чатов
-var response = await client.Chats.ListChatsAsync(SortOrder.Desc, ChatAvailability.IsMember, "2025-01-01T00:00:00.000Z", "2025-02-01T00:00:00.000Z", false, 1, "eyJpZCI6MTAsImRpciI6ImFzYyJ9");
-// → ListChatsResponse(Data: List, Meta: PaginationMeta?)
+var response = await client.Chats.ListChatsAsync(ChatSortField.Id, SortOrder.Desc, ChatAvailability.IsMember, DateTimeOffset.Parse("2025-01-01T00:00:00.000Z"), DateTimeOffset.Parse("2025-02-01T00:00:00.000Z"), false, 1, "eyJpZCI6MTAsImRpciI6ImFzYyJ9");
+// → ListChatsResponse(Data: List, Meta: PaginationMeta)
```
@@ -177,7 +181,7 @@ var response = await client.Chats.GetChatAsync(334);
## Пагинация
-Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит поле `Meta.Paginate.NextPage` — курсор для следующей страницы.
+Методы, возвращающие списки, используют cursor-based пагинацию. Ответ всегда содержит поле `Meta.Paginate.NextPage` — курсор для следующей страницы. Курсор никогда не бывает `null` — конец данных определяется по пустому массиву `Data`.
### Ручная пагинация
@@ -185,12 +189,13 @@ var response = await client.Chats.GetChatAsync(334);
var chats = new List();
string? cursor = null;
-do
+while (true)
{
var response = await client.Chats.ListChatsAsync(cursor: cursor);
+ if (response.Data.Count == 0) break;
chats.AddRange(response.Data);
- cursor = response.Meta?.Paginate?.NextPage;
-} while (cursor != null);
+ cursor = response.Meta.Paginate.NextPage;
+}
```
### Автопагинация
@@ -275,11 +280,11 @@ catch (OAuthError e)
## Повторные запросы
-SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx`:
+SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx` (`500`, `502`, `503`, `504`):
- До **3 повторов** на каждый запрос
-- Если сервер вернул заголовок `Retry-After` — ждёт указанное время
-- Иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **429:** если сервер вернул заголовок `Retry-After` — ждёт указанное время, иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **5xx:** экспоненциальный backoff с jitter: ~10 сек, ~20 сек, ~40 сек
- Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов
## Отмена запросов
@@ -406,7 +411,7 @@ var response = await client.Messages.CreateMessageAsync(request);
// Список сотрудников
var response = await client.Users.ListUsersAsync("Олег", 1, "eyJpZCI6MTAsImRpciI6ImFzYyJ9");
-// → ListUsersResponse(Data: List, Meta: PaginationMeta?)
+// → ListUsersResponse(Data: List, Meta: PaginationMeta)
// Создание задачи
var request = new TaskCreateRequest
@@ -415,7 +420,7 @@ var request = new TaskCreateRequest
{
Kind = TaskKind.Reminder,
Content = "Забрать со склада 21 заказ",
- DueAt = "2020-06-05T12:00:00.000+03:00",
+ DueAt = DateTimeOffset.Parse("2020-06-05T12:00:00.000+03:00"),
Priority = 2,
PerformerIds = new List { 123 },
ChatId = 456,
diff --git a/apps/docs/public/guides/sdk/go.md b/apps/docs/public/guides/sdk/go.md
index 1e43a6ae..697d7fc8 100644
--- a/apps/docs/public/guides/sdk/go.md
+++ b/apps/docs/public/guides/sdk/go.md
@@ -76,15 +76,19 @@ user, err := client.Profile.GetProfile(ctx)
| `client.Profile.GetTokenInfo()` | [Информация о токене](/api/profile/get-info) |
| `client.Profile.GetProfile()` | [Информация о профиле](/api/profile/get) |
| `client.Profile.GetStatus()` | [Текущий статус](/api/profile/get-status) |
+| `client.Profile.UpdateProfileAvatar()` | [Загрузка аватара](/api/profile/update-avatar) |
| `client.Profile.UpdateStatus()` | [Новый статус](/api/profile/update-status) |
+| `client.Profile.DeleteProfileAvatar()` | [Удаление аватара](/api/profile/delete-avatar) |
| `client.Profile.DeleteStatus()` | [Удаление статуса](/api/profile/delete-status) |
| `client.Users.CreateUser()` | [Создать сотрудника](/api/users/create) |
| `client.Users.ListUsers()` | [Список сотрудников](/api/users/list) |
| `client.Users.GetUser()` | [Информация о сотруднике](/api/users/get) |
| `client.Users.GetUserStatus()` | [Статус сотрудника](/api/users/get-status) |
| `client.Users.UpdateUser()` | [Редактирование сотрудника](/api/users/update) |
+| `client.Users.UpdateUserAvatar()` | [Загрузка аватара сотрудника](/api/users/update-avatar) |
| `client.Users.UpdateUserStatus()` | [Новый статус сотрудника](/api/users/update-status) |
| `client.Users.DeleteUser()` | [Удаление сотрудника](/api/users/delete) |
+| `client.Users.DeleteUserAvatar()` | [Удаление аватара сотрудника](/api/users/remove-avatar) |
| `client.Users.DeleteUserStatus()` | [Удаление статуса сотрудника](/api/users/remove-status) |
| `client.GroupTags.CreateTag()` | [Новый тег](/api/group-tags/create) |
| `client.GroupTags.ListTags()` | [Список тегов сотрудников](/api/group-tags/list) |
@@ -145,7 +149,8 @@ import pachca "github.com/pachca/openapi/sdk/go/generated"
// Список чатов
params := &ListChatsParams{
- SortID: Ptr(SortOrderDesc),
+ Sort: Ptr(ChatSortFieldID),
+ Order: Ptr(SortOrderDesc),
Availability: Ptr(ChatAvailabilityIsMember),
LastMessageAtAfter: Ptr("2025-01-01T00:00:00.000Z"),
LastMessageAtBefore: Ptr("2025-02-01T00:00:00.000Z"),
@@ -154,7 +159,7 @@ params := &ListChatsParams{
Cursor: Ptr("eyJpZCI6MTAsImRpciI6ImFzYyJ9"),
}
response, err := client.Chats.ListChats(ctx, params)
-// → ListChatsResponse{Data: []Chat, Meta: *PaginationMeta}
+// → ListChatsResponse{Data: []Chat, Meta: PaginationMeta}
```
@@ -204,7 +209,7 @@ request := pachca.ChatUpdateRequest{
## Пагинация
-Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит `Meta.Paginate.NextPage` — курсор для следующей страницы.
+Методы, возвращающие списки, используют cursor-based пагинацию. Ответ всегда содержит `Meta.Paginate.NextPage` — курсор для следующей страницы. Курсор никогда не бывает пустым — конец данных определяется по пустому слайсу `Data`.
### Ручная пагинация
@@ -216,13 +221,14 @@ for {
if err != nil {
log.Fatal(err)
}
+ if len(response.Data) == 0 {
+ break
+ }
for _, user := range response.Data {
fmt.Println(user.FirstName, user.LastName)
}
- if response.Meta == nil || response.Meta.Paginate == nil || response.Meta.Paginate.NextPage == nil {
- break
- }
- cursor = response.Meta.Paginate.NextPage
+ nextPage := response.Meta.Paginate.NextPage
+ cursor = &nextPage
}
```
@@ -305,12 +311,13 @@ if err != nil {
## Повторные запросы
-SDK автоматически повторяет запрос при получении `429 Too Many Requests`:
+SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx` (`500`, `502`, `503`, `504`):
- До **3 повторов** на каждый запрос
-- Если сервер вернул заголовок `Retry-After` — ждёт указанное время
-- Иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **429:** если сервер вернул заголовок `Retry-After` — ждёт указанное время, иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **5xx:** экспоненциальный backoff с jitter: ~10 сек, ~20 сек, ~40 сек
- Тело запроса пересоздаётся через `req.GetBody()` при каждой попытке
+- Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов
## Типы
@@ -383,7 +390,7 @@ params := &ListUsersParams{
Cursor: Ptr("eyJpZCI6MTAsImRpciI6ImFzYyJ9"),
}
response, err := client.Users.ListUsers(ctx, params)
-// → ListUsersResponse{Data: []User, Meta: *PaginationMeta}
+// → ListUsersResponse{Data: []User, Meta: PaginationMeta}
// Создание задачи
request := TaskCreateRequest{
diff --git a/apps/docs/public/guides/sdk/kotlin.md b/apps/docs/public/guides/sdk/kotlin.md
index a50ab4a4..0331591e 100644
--- a/apps/docs/public/guides/sdk/kotlin.md
+++ b/apps/docs/public/guides/sdk/kotlin.md
@@ -15,7 +15,7 @@
```kotlin
dependencies {
- implementation("com.pachca:pachca-sdk:1.0.1")
+ implementation("com.pachca:pachca-sdk:latest.release")
}
```
@@ -36,7 +36,7 @@ val client = PachcaClient("YOUR_TOKEN")
```kotlin
// Получение профиля
val response = client.profile.getProfile()
-// → User(id: Int, firstName: String, lastName: String, nickname: String, email: String, phoneNumber: String, department: String, title: String, role: UserRole, suspended: Boolean, inviteStatus: InviteStatus, listTags: List, customProperties: List, userStatus: UserStatus(emoji: String, title: String, expiresAt: String?, isAway: Boolean, awayMessage: UserStatusAwayMessage(text: String)?)?, bot: Boolean, sso: Boolean, createdAt: String, lastActivityAt: String, timeZone: String, imageUrl: String?)
+// → User(id: Int, firstName: String, lastName: String, nickname: String, email: String, phoneNumber: String, department: String, title: String, role: UserRole, suspended: Boolean, inviteStatus: InviteStatus, listTags: List, customProperties: List, userStatus: UserStatus(emoji: String, title: String, expiresAt: OffsetDateTime?, isAway: Boolean, awayMessage: UserStatusAwayMessage(text: String)?)?, bot: Boolean, sso: Boolean, createdAt: OffsetDateTime, lastActivityAt: OffsetDateTime, timeZone: String, imageUrl: String?)
```
@@ -84,15 +84,19 @@ client.close()
| `client.profile.getTokenInfo()` | [Информация о токене](/api/profile/get-info) |
| `client.profile.getProfile()` | [Информация о профиле](/api/profile/get) |
| `client.profile.getStatus()` | [Текущий статус](/api/profile/get-status) |
+| `client.profile.updateProfileAvatar()` | [Загрузка аватара](/api/profile/update-avatar) |
| `client.profile.updateStatus()` | [Новый статус](/api/profile/update-status) |
+| `client.profile.deleteProfileAvatar()` | [Удаление аватара](/api/profile/delete-avatar) |
| `client.profile.deleteStatus()` | [Удаление статуса](/api/profile/delete-status) |
| `client.users.createUser()` | [Создать сотрудника](/api/users/create) |
| `client.users.listUsers()` | [Список сотрудников](/api/users/list) |
| `client.users.getUser()` | [Информация о сотруднике](/api/users/get) |
| `client.users.getUserStatus()` | [Статус сотрудника](/api/users/get-status) |
| `client.users.updateUser()` | [Редактирование сотрудника](/api/users/update) |
+| `client.users.updateUserAvatar()` | [Загрузка аватара сотрудника](/api/users/update-avatar) |
| `client.users.updateUserStatus()` | [Новый статус сотрудника](/api/users/update-status) |
| `client.users.deleteUser()` | [Удаление сотрудника](/api/users/delete) |
+| `client.users.deleteUserAvatar()` | [Удаление аватара сотрудника](/api/users/remove-avatar) |
| `client.users.deleteUserStatus()` | [Удаление статуса сотрудника](/api/users/remove-status) |
| `client.groupTags.createTag()` | [Новый тег](/api/group-tags/create) |
| `client.groupTags.listTags()` | [Список тегов сотрудников](/api/group-tags/list) |
@@ -150,11 +154,14 @@ client.close()
```kotlin
import com.pachca.sdk.ChatAvailability
+import com.pachca.sdk.ChatSortField
import com.pachca.sdk.SortOrder
// Список чатов
-val response = client.chats.listChats(sortId = SortOrder.DESC, availability = ChatAvailability.IS_MEMBER, lastMessageAtAfter = "2025-01-01T00:00:00.000Z", lastMessageAtBefore = "2025-02-01T00:00:00.000Z", personal = false, limit = 1, cursor = "eyJpZCI6MTAsImRpciI6ImFzYyJ9")
-// → ListChatsResponse(data: List, meta: PaginationMeta?)
+val lastMessageAtAfter = OffsetDateTime.parse("2025-01-01T00:00:00.000Z")
+val lastMessageAtBefore = OffsetDateTime.parse("2025-02-01T00:00:00.000Z")
+val response = client.chats.listChats(sort = ChatSortField.ID, order = SortOrder.DESC, availability = ChatAvailability.IS_MEMBER, lastMessageAtAfter = lastMessageAtAfter, lastMessageAtBefore = lastMessageAtBefore, personal = false, limit = 1, cursor = "eyJpZCI6MTAsImRpciI6ImFzYyJ9")
+// → ListChatsResponse(data: List, meta: PaginationMeta)
```
@@ -175,7 +182,7 @@ val request = ChatCreateRequest(
)
)
val response = client.chats.createChat(request = request)
-// → Chat(id: Int, name: String, createdAt: String, ownerId: Int, memberIds: List, groupTagIds: List, channel: Boolean, personal: Boolean, public: Boolean, lastMessageAt: String, meetRoomUrl: String)
+// → Chat(id: Int, name: String, createdAt: OffsetDateTime, ownerId: Int, memberIds: List, groupTagIds: List, channel: Boolean, personal: Boolean, public: Boolean, lastMessageAt: OffsetDateTime, meetRoomUrl: String)
```
@@ -184,25 +191,26 @@ val response = client.chats.createChat(request = request)
```kotlin
// Получение чата
val response = client.chats.getChat(id = 334)
-// → Chat(id: Int, name: String, createdAt: String, ownerId: Int, memberIds: List, groupTagIds: List, channel: Boolean, personal: Boolean, public: Boolean, lastMessageAt: String, meetRoomUrl: String)
+// → Chat(id: Int, name: String, createdAt: OffsetDateTime, ownerId: Int, memberIds: List, groupTagIds: List, channel: Boolean, personal: Boolean, public: Boolean, lastMessageAt: OffsetDateTime, meetRoomUrl: String)
```
## Пагинация
-Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит `meta?.paginate?.nextPage` — курсор для следующей страницы.
+Методы, возвращающие списки, используют cursor-based пагинацию. Ответ всегда содержит `meta.paginate.nextPage` — курсор для следующей страницы. Курсор никогда не бывает `null` — конец данных определяется по пустому массиву `data`.
### Ручная пагинация
```kotlin
var cursor: String? = null
-do {
+while (true) {
val response = client.users.listUsers(limit = 50, cursor = cursor)
+ if (response.data.isEmpty()) break
for (user in response.data) {
println("${user.firstName} ${user.lastName}")
}
- cursor = response.meta?.paginate?.nextPage
-} while (cursor != null)
+ cursor = response.meta.paginate.nextPage
+}
```
### Автопагинация
@@ -281,12 +289,13 @@ try {
## Повторные запросы
-SDK автоматически повторяет запрос при получении `429 Too Many Requests` или серверной ошибки (`5xx`):
+SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx` (`500`, `502`, `503`, `504`):
- До **3 повторов** на каждый запрос
-- Если сервер вернул заголовок `Retry-After` — ждёт указанное время
-- Иначе — линейный backoff: 1 сек, 2 сек, 3 сек
+- **429:** если сервер вернул заголовок `Retry-After` — ждёт указанное время, иначе — экспоненциальный backoff с jitter
+- **5xx:** экспоненциальный backoff с jitter: ~10 сек, ~20 сек, ~40 сек
- Реализовано через плагин Ktor `HttpRequestRetry`
+- Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов
## Типы
@@ -370,18 +379,18 @@ val request = MessageCreateRequest(
linkPreview = false
)
val response = client.messages.createMessage(request = request)
-// → Message(id: Int, entityType: MessageEntityType, entityId: Int, chatId: Int, rootChatId: Int, content: String, userId: Int, createdAt: String, url: String, files: List, buttons: List>?, thread: MessageThread(id: Long, chatId: Long)?, forwarding: Forwarding(originalMessageId: Int, originalChatId: Int, authorId: Int, originalCreatedAt: String, originalThreadId: Int?, originalThreadMessageId: Int?, originalThreadParentChatId: Int?)?, parentMessageId: Int?, displayAvatarUrl: String?, displayName: String?, changedAt: String?, deletedAt: String?)
+// → Message(id: Int, entityType: MessageEntityType, entityId: Int, chatId: Int, rootChatId: Int, content: String, userId: Int, createdAt: OffsetDateTime, url: String, files: List, buttons: List>?, thread: MessageThread(id: Long, chatId: Long)?, forwarding: Forwarding(originalMessageId: Int, originalChatId: Int, authorId: Int, originalCreatedAt: OffsetDateTime, originalThreadId: Int?, originalThreadMessageId: Int?, originalThreadParentChatId: Int?)?, parentMessageId: Int?, displayAvatarUrl: String?, displayName: String?, changedAt: OffsetDateTime?, deletedAt: OffsetDateTime?)
// Список сотрудников
val response = client.users.listUsers(query = "Олег", limit = 1, cursor = "eyJpZCI6MTAsImRpciI6ImFzYyJ9")
-// → ListUsersResponse(data: List, meta: PaginationMeta?)
+// → ListUsersResponse(data: List, meta: PaginationMeta)
// Создание задачи
val request = TaskCreateRequest(
task = TaskCreateRequestTask(
kind = TaskKind.REMINDER,
content = "Забрать со склада 21 заказ",
- dueAt = "2020-06-05T12:00:00.000+03:00",
+ dueAt = OffsetDateTime.parse("2020-06-05T12:00:00.000+03:00"),
priority = 2,
performerIds = listOf(123),
chatId = 456,
@@ -390,7 +399,7 @@ val request = TaskCreateRequest(
)
)
val response = client.tasks.createTask(request = request)
-// → Task(id: Int, kind: TaskKind, content: String, dueAt: String?, priority: Int, userId: Int, chatId: Int?, status: TaskStatus, createdAt: String, performerIds: List, allDay: Boolean, customProperties: List)
+// → Task(id: Int, kind: TaskKind, content: String, dueAt: OffsetDateTime?, priority: Int, userId: Int, chatId: Int?, status: TaskStatus, createdAt: OffsetDateTime, performerIds: List, allDay: Boolean, customProperties: List)
```
diff --git a/apps/docs/public/guides/sdk/overview.md b/apps/docs/public/guides/sdk/overview.md
index 37d3cbc6..7dfaa233 100644
--- a/apps/docs/public/guides/sdk/overview.md
+++ b/apps/docs/public/guides/sdk/overview.md
@@ -45,7 +45,7 @@ npx @pachca/generator --spec https://example.com/openapi.yaml --output ./generat
|-------------|----------|
| **16 сервисов** | Типизированные методы для каждого API-эндпоинта |
| **Автопагинация** | Методы `*All()` для автоматического обхода всех страниц |
-| **Повторные запросы** | Автоматический retry при `429` с экспоненциальным backoff |
+| **Повторные запросы** | Автоматический retry при `429` и `5xx` с экспоненциальным backoff |
| **Обработка ошибок** | Типизированные `ApiError` и `OAuthError` |
| **Сериализация** | Автоматическая конвертация между форматами (snake_case ↔ camelCase) |
| **Авторизация** | Bearer-токен передаётся один раз при создании клиента |
diff --git a/apps/docs/public/guides/sdk/python.md b/apps/docs/public/guides/sdk/python.md
index 94eb5465..c45847d9 100644
--- a/apps/docs/public/guides/sdk/python.md
+++ b/apps/docs/public/guides/sdk/python.md
@@ -32,7 +32,7 @@ client = PachcaClient("YOUR_TOKEN")
```python
# Получение профиля
response = await client.profile.get_profile()
-# → User(id: int, first_name: str, last_name: str, nickname: str, email: str, phone_number: str, department: str, title: str, role: UserRole, suspended: bool, invite_status: InviteStatus, list_tags: list[str], custom_properties: list[CustomProperty(id: int, name: str, data_type: CustomPropertyDataType, value: str)], user_status: UserStatus(emoji: str, title: str, expires_at: str | None, is_away: bool, away_message: UserStatusAwayMessage(text: str) | None) | None, bot: bool, sso: bool, created_at: str, last_activity_at: str, time_zone: str, image_url: str | None)
+# → User(id: int, first_name: str, last_name: str, nickname: str, email: str, phone_number: str, department: str, title: str, role: UserRole, suspended: bool, invite_status: InviteStatus, list_tags: list[str], custom_properties: list[CustomProperty(id: int, name: str, data_type: CustomPropertyDataType, value: str)], user_status: UserStatus(emoji: str, title: str, expires_at: datetime | None, is_away: bool, away_message: UserStatusAwayMessage(text: str) | None) | None, bot: bool, sso: bool, created_at: datetime, last_activity_at: datetime, time_zone: str, image_url: str | None)
```
@@ -74,15 +74,19 @@ await client.close()
| `client.profile.get_token_info()` | [Информация о токене](/api/profile/get-info) |
| `client.profile.get_profile()` | [Информация о профиле](/api/profile/get) |
| `client.profile.get_status()` | [Текущий статус](/api/profile/get-status) |
+| `client.profile.update_profile_avatar()` | [Загрузка аватара](/api/profile/update-avatar) |
| `client.profile.update_status()` | [Новый статус](/api/profile/update-status) |
+| `client.profile.delete_profile_avatar()` | [Удаление аватара](/api/profile/delete-avatar) |
| `client.profile.delete_status()` | [Удаление статуса](/api/profile/delete-status) |
| `client.users.create_user()` | [Создать сотрудника](/api/users/create) |
| `client.users.list_users()` | [Список сотрудников](/api/users/list) |
| `client.users.get_user()` | [Информация о сотруднике](/api/users/get) |
| `client.users.get_user_status()` | [Статус сотрудника](/api/users/get-status) |
| `client.users.update_user()` | [Редактирование сотрудника](/api/users/update) |
+| `client.users.update_user_avatar()` | [Загрузка аватара сотрудника](/api/users/update-avatar) |
| `client.users.update_user_status()` | [Новый статус сотрудника](/api/users/update-status) |
| `client.users.delete_user()` | [Удаление сотрудника](/api/users/delete) |
+| `client.users.delete_user_avatar()` | [Удаление аватара сотрудника](/api/users/remove-avatar) |
| `client.users.delete_user_status()` | [Удаление статуса сотрудника](/api/users/remove-status) |
| `client.group_tags.create_tag()` | [Новый тег](/api/group-tags/create) |
| `client.group_tags.list_tags()` | [Список тегов сотрудников](/api/group-tags/list) |
@@ -139,20 +143,21 @@ await client.close()
**GET с параметрами:**
```python
-from pachca.models import ChatAvailability, ListChatsParams, SortOrder
+from pachca.models import ChatAvailability, ChatSortField, ListChatsParams, SortOrder
# Список чатов
params = ListChatsParams(
- sort_id=SortOrder.DESC,
+ sort=ChatSortField.ID,
+ order=SortOrder.DESC,
availability=ChatAvailability.IS_MEMBER,
- last_message_at_after="2025-01-01T00:00:00.000Z",
- last_message_at_before="2025-02-01T00:00:00.000Z",
+ last_message_at_after=datetime.fromisoformat("2025-01-01T00:00:00.000Z"),
+ last_message_at_before=datetime.fromisoformat("2025-02-01T00:00:00.000Z"),
personal=False,
limit=1,
cursor="eyJpZCI6MTAsImRpciI6ImFzYyJ9"
)
response = await client.chats.list_chats(params=params)
-# → ListChatsResponse(data: list[Chat], meta: PaginationMeta | None)
+# → ListChatsResponse(data: list[Chat], meta: PaginationMeta)
```
@@ -172,7 +177,7 @@ request = ChatCreateRequest(
)
)
response = await client.chats.create_chat(request=request)
-# → Chat(id: int, name: str, created_at: str, owner_id: int, member_ids: list[int], group_tag_ids: list[int], channel: bool, personal: bool, public: bool, last_message_at: str, meet_room_url: str)
+# → Chat(id: int, name: str, created_at: datetime, owner_id: int, member_ids: list[int], group_tag_ids: list[int], channel: bool, personal: bool, public: bool, last_message_at: datetime, meet_room_url: str)
```
@@ -181,13 +186,13 @@ response = await client.chats.create_chat(request=request)
```python
# Получение чата
response = await client.chats.get_chat(id=334)
-# → Chat(id: int, name: str, created_at: str, owner_id: int, member_ids: list[int], group_tag_ids: list[int], channel: bool, personal: bool, public: bool, last_message_at: str, meet_room_url: str)
+# → Chat(id: int, name: str, created_at: datetime, owner_id: int, member_ids: list[int], group_tag_ids: list[int], channel: bool, personal: bool, public: bool, last_message_at: datetime, meet_room_url: str)
```
## Пагинация
-Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит `meta.paginate.next_page` — курсор для следующей страницы.
+Методы, возвращающие списки, используют cursor-based пагинацию. Ответ всегда содержит `meta.paginate.next_page` — курсор для следующей страницы. Курсор никогда не бывает `None` — конец данных определяется по пустому списку `data`.
### Ручная пагинация
@@ -197,10 +202,10 @@ from pachca.models import ListUsersParams
cursor = None
while True:
response = await client.users.list_users(ListUsersParams(limit=50, cursor=cursor))
+ if not response.data:
+ break
for user in response.data:
print(user.first_name, user.last_name)
- if not response.meta or not response.meta.paginate or not response.meta.paginate.next_page:
- break
cursor = response.meta.paginate.next_page
```
@@ -277,12 +282,13 @@ except OAuthError as error:
## Повторные запросы
-SDK автоматически повторяет запрос при получении `429 Too Many Requests`:
+SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx` (`500`, `502`, `503`, `504`):
- До **3 повторов** на каждый запрос
-- Если сервер вернул заголовок `Retry-After` — ждёт указанное время
-- Иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **429:** если сервер вернул заголовок `Retry-After` — ждёт указанное время, иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **5xx:** экспоненциальный backoff с jitter: ~10 сек, ~20 сек, ~40 сек
- Реализовано через `RetryTransport` — обёртку над httpx-транспортом
+- Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов
## Типы
@@ -352,7 +358,7 @@ request = MessageCreateRequest(
link_preview=False
)
response = await client.messages.create_message(request=request)
-# → Message(id: int, entity_type: MessageEntityType, entity_id: int, chat_id: int, root_chat_id: int, content: str, user_id: int, created_at: str, url: str, files: list[File(id: int, key: str, name: str, file_type: FileType, url: str, width: int | None, height: int | None)], buttons: list[list[Button(text: str, url: str | None, data: str | None)]] | None, thread: MessageThread(id: int, chat_id: int) | None, forwarding: Forwarding(original_message_id: int, original_chat_id: int, author_id: int, original_created_at: str, original_thread_id: int | None, original_thread_message_id: int | None, original_thread_parent_chat_id: int | None) | None, parent_message_id: int | None, display_avatar_url: str | None, display_name: str | None, changed_at: str | None, deleted_at: str | None)
+# → Message(id: int, entity_type: MessageEntityType, entity_id: int, chat_id: int, root_chat_id: int, content: str, user_id: int, created_at: datetime, url: str, files: list[File(id: int, key: str, name: str, file_type: FileType, url: str, width: int | None, height: int | None)], buttons: list[list[Button(text: str, url: str | None, data: str | None)]] | None, thread: MessageThread(id: int, chat_id: int) | None, forwarding: Forwarding(original_message_id: int, original_chat_id: int, author_id: int, original_created_at: datetime, original_thread_id: int | None, original_thread_message_id: int | None, original_thread_parent_chat_id: int | None) | None, parent_message_id: int | None, display_avatar_url: str | None, display_name: str | None, changed_at: datetime | None, deleted_at: datetime | None)
# Список сотрудников
params = ListUsersParams(
@@ -361,14 +367,14 @@ params = ListUsersParams(
cursor="eyJpZCI6MTAsImRpciI6ImFzYyJ9"
)
response = await client.users.list_users(params=params)
-# → ListUsersResponse(data: list[User], meta: PaginationMeta | None)
+# → ListUsersResponse(data: list[User], meta: PaginationMeta)
# Создание задачи
request = TaskCreateRequest(
task=TaskCreateRequestTask(
kind=TaskKind.REMINDER,
content="Забрать со склада 21 заказ",
- due_at="2020-06-05T12:00:00.000+03:00",
+ due_at=datetime.fromisoformat("2020-06-05T12:00:00.000+03:00"),
priority=2,
performer_ids=[123],
chat_id=456,
@@ -377,7 +383,7 @@ request = TaskCreateRequest(
)
)
response = await client.tasks.create_task(request=request)
-# → Task(id: int, kind: TaskKind, content: str, due_at: str | None, priority: int, user_id: int, chat_id: int | None, status: TaskStatus, created_at: str, performer_ids: list[int], all_day: bool, custom_properties: list[CustomProperty(id: int, name: str, data_type: CustomPropertyDataType, value: str)])
+# → Task(id: int, kind: TaskKind, content: str, due_at: datetime | None, priority: int, user_id: int, chat_id: int | None, status: TaskStatus, created_at: datetime, performer_ids: list[int], all_day: bool, custom_properties: list[CustomProperty(id: int, name: str, data_type: CustomPropertyDataType, value: str)])
```
diff --git a/apps/docs/public/guides/sdk/swift.md b/apps/docs/public/guides/sdk/swift.md
index e1333211..8dea1e77 100644
--- a/apps/docs/public/guides/sdk/swift.md
+++ b/apps/docs/public/guides/sdk/swift.md
@@ -15,7 +15,7 @@
```swift
dependencies: [
- .package(url: "https://github.com/pachca/openapi", from: "1.0.1")
+ .package(url: "https://github.com/pachca/openapi", from: "1.0.0")
]
```
@@ -75,15 +75,19 @@ let client = PachcaClient(token: "YOUR_TOKEN", baseURL: "https://custom-api.exam
| `client.profile.getTokenInfo()` | [Информация о токене](/api/profile/get-info) |
| `client.profile.getProfile()` | [Информация о профиле](/api/profile/get) |
| `client.profile.getStatus()` | [Текущий статус](/api/profile/get-status) |
+| `client.profile.updateProfileAvatar()` | [Загрузка аватара](/api/profile/update-avatar) |
| `client.profile.updateStatus()` | [Новый статус](/api/profile/update-status) |
+| `client.profile.deleteProfileAvatar()` | [Удаление аватара](/api/profile/delete-avatar) |
| `client.profile.deleteStatus()` | [Удаление статуса](/api/profile/delete-status) |
| `client.users.createUser()` | [Создать сотрудника](/api/users/create) |
| `client.users.listUsers()` | [Список сотрудников](/api/users/list) |
| `client.users.getUser()` | [Информация о сотруднике](/api/users/get) |
| `client.users.getUserStatus()` | [Статус сотрудника](/api/users/get-status) |
| `client.users.updateUser()` | [Редактирование сотрудника](/api/users/update) |
+| `client.users.updateUserAvatar()` | [Загрузка аватара сотрудника](/api/users/update-avatar) |
| `client.users.updateUserStatus()` | [Новый статус сотрудника](/api/users/update-status) |
| `client.users.deleteUser()` | [Удаление сотрудника](/api/users/delete) |
+| `client.users.deleteUserAvatar()` | [Удаление аватара сотрудника](/api/users/remove-avatar) |
| `client.users.deleteUserStatus()` | [Удаление статуса сотрудника](/api/users/remove-status) |
| `client.groupTags.createTag()` | [Новый тег](/api/group-tags/create) |
| `client.groupTags.listTags()` | [Список тегов сотрудников](/api/group-tags/list) |
@@ -143,8 +147,8 @@ let client = PachcaClient(token: "YOUR_TOKEN", baseURL: "https://custom-api.exam
import PachcaSDK
// Список чатов
-let response = try await client.chats.listChats(sortId: .desc, availability: .isMember, lastMessageAtAfter: "2025-01-01T00:00:00.000Z", lastMessageAtBefore: "2025-02-01T00:00:00.000Z", personal: false, limit: 1, cursor: "eyJpZCI6MTAsImRpciI6ImFzYyJ9")
-// → ListChatsResponse(data: [Chat], meta: PaginationMeta?)
+let response = try await client.chats.listChats(sort: .id, order: .desc, availability: .isMember, lastMessageAtAfter: "2025-01-01T00:00:00.000Z", lastMessageAtBefore: "2025-02-01T00:00:00.000Z", personal: false, limit: 1, cursor: "eyJpZCI6MTAsImRpciI6ImFzYyJ9")
+// → ListChatsResponse(data: [Chat], meta: PaginationMeta)
```
@@ -181,7 +185,7 @@ let response = try await client.chats.getChat(id: 334)
## Пагинация
-Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит `meta?.paginate?.nextPage` — курсор для следующей страницы.
+Методы, возвращающие списки, используют cursor-based пагинацию. Ответ всегда содержит `meta.paginate.nextPage` — курсор для следующей страницы. Курсор никогда не бывает `nil` — конец данных определяется по пустому массиву `data`.
### Ручная пагинация
@@ -189,11 +193,12 @@ let response = try await client.chats.getChat(id: 334)
var cursor: String? = nil
repeat {
let response = try await client.users.listUsers(limit: 50, cursor: cursor)
+ if response.data.isEmpty { break }
for user in response.data {
print("\(user.firstName) \(user.lastName)")
}
- cursor = response.meta?.paginate?.nextPage
-} while cursor != nil
+ cursor = response.meta.paginate.nextPage
+} while true
```
### Автопагинация
@@ -268,12 +273,13 @@ do {
## Повторные запросы
-SDK автоматически повторяет запрос при получении `429 Too Many Requests`:
+SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx` (`500`, `502`, `503`, `504`):
- До **3 повторов** на каждый запрос
-- Если сервер вернул заголовок `Retry-After` — ждёт указанное время
-- Иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **429:** если сервер вернул заголовок `Retry-After` — ждёт указанное время, иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **5xx:** экспоненциальный backoff с jitter: ~10 сек, ~20 сек, ~40 сек
- Ожидание через `Task.sleep(nanoseconds:)` — не блокирует поток
+- Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов
## Типы
@@ -346,7 +352,7 @@ let response = try await client.messages.createMessage(body: body)
// Список сотрудников
let response = try await client.users.listUsers(query: "Олег", limit: 1, cursor: "eyJpZCI6MTAsImRpciI6ImFzYyJ9")
-// → ListUsersResponse(data: [User], meta: PaginationMeta?)
+// → ListUsersResponse(data: [User], meta: PaginationMeta)
// Создание задачи
let body = TaskCreateRequest(
diff --git a/apps/docs/public/guides/sdk/typescript.md b/apps/docs/public/guides/sdk/typescript.md
index 3fd242a3..78fc4506 100644
--- a/apps/docs/public/guides/sdk/typescript.md
+++ b/apps/docs/public/guides/sdk/typescript.md
@@ -67,15 +67,19 @@ const client = new PachcaClient("YOUR_TOKEN", "https://custom-api.example.com/ap
| `client.profile.getTokenInfo()` | [Информация о токене](/api/profile/get-info) |
| `client.profile.getProfile()` | [Информация о профиле](/api/profile/get) |
| `client.profile.getStatus()` | [Текущий статус](/api/profile/get-status) |
+| `client.profile.updateProfileAvatar()` | [Загрузка аватара](/api/profile/update-avatar) |
| `client.profile.updateStatus()` | [Новый статус](/api/profile/update-status) |
+| `client.profile.deleteProfileAvatar()` | [Удаление аватара](/api/profile/delete-avatar) |
| `client.profile.deleteStatus()` | [Удаление статуса](/api/profile/delete-status) |
| `client.users.createUser()` | [Создать сотрудника](/api/users/create) |
| `client.users.listUsers()` | [Список сотрудников](/api/users/list) |
| `client.users.getUser()` | [Информация о сотруднике](/api/users/get) |
| `client.users.getUserStatus()` | [Статус сотрудника](/api/users/get-status) |
| `client.users.updateUser()` | [Редактирование сотрудника](/api/users/update) |
+| `client.users.updateUserAvatar()` | [Загрузка аватара сотрудника](/api/users/update-avatar) |
| `client.users.updateUserStatus()` | [Новый статус сотрудника](/api/users/update-status) |
| `client.users.deleteUser()` | [Удаление сотрудника](/api/users/delete) |
+| `client.users.deleteUserAvatar()` | [Удаление аватара сотрудника](/api/users/remove-avatar) |
| `client.users.deleteUserStatus()` | [Удаление статуса сотрудника](/api/users/remove-status) |
| `client.groupTags.createTag()` | [Новый тег](/api/group-tags/create) |
| `client.groupTags.listTags()` | [Список тегов сотрудников](/api/group-tags/list) |
@@ -132,11 +136,12 @@ const client = new PachcaClient("YOUR_TOKEN", "https://custom-api.example.com/ap
**GET с параметрами:**
```typescript
-import { ChatAvailability, SortOrder } from "@pachca/sdk"
+import { ChatAvailability, ChatSortField, SortOrder } from "@pachca/sdk"
// Список чатов
const response = client.chats.listChats({
- sortId: SortOrder.Desc,
+ sort: ChatSortField.Id,
+ order: SortOrder.Desc,
availability: ChatAvailability.IsMember,
lastMessageAtAfter: "2025-01-01T00:00:00.000Z",
lastMessageAtBefore: "2025-02-01T00:00:00.000Z",
@@ -144,7 +149,7 @@ const response = client.chats.listChats({
limit: 1,
cursor: "eyJpZCI6MTAsImRpciI6ImFzYyJ9"
})
-// → ListChatsResponse({ data: Chat[], meta?: PaginationMeta })
+// → ListChatsResponse({ data: Chat[], meta: PaginationMeta })
```
@@ -179,20 +184,21 @@ const response = client.chats.getChat(334)
## Пагинация
-Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит поле `meta.paginate.nextPage` — курсор для следующей страницы.
+Методы, возвращающие списки, используют cursor-based пагинацию. Ответ всегда содержит поле `meta.paginate.nextPage` — курсор для следующей страницы. Курсор никогда не бывает `null` — конец данных определяется по пустому массиву `data`.
### Ручная пагинация
```typescript
let cursor: string | undefined
-do {
+for (;;) {
const response = await client.users.listUsers({ limit: 50, cursor })
+ if (response.data.length === 0) break
for (const user of response.data) {
console.log(user.firstName, user.lastName)
}
- cursor = response.meta?.paginate?.nextPage
-} while (cursor)
+ cursor = response.meta.paginate.nextPage
+}
```
### Автопагинация
@@ -274,12 +280,12 @@ try {
## Повторные запросы
-SDK автоматически повторяет запрос при получении `429 Too Many Requests`:
+SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx` (`500`, `502`, `503`, `504`):
- До **3 повторов** на каждый запрос
-- Если сервер вернул заголовок `Retry-After` — ждёт указанное время
-- Иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
-- Все остальные ошибки возвращаются сразу без retry
+- **429:** если сервер вернул заголовок `Retry-After` — ждёт указанное время, иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **5xx:** экспоненциальный backoff с jitter: ~10 сек, ~20 сек, ~40 сек
+- Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов
## Сериализация
@@ -366,7 +372,7 @@ const response = client.users.listUsers({
limit: 1,
cursor: "eyJpZCI6MTAsImRpciI6ImFzYyJ9"
})
-// → ListUsersResponse({ data: User[], meta?: PaginationMeta })
+// → ListUsersResponse({ data: User[], meta: PaginationMeta })
// Создание задачи
const request: TaskCreateRequest = {
diff --git a/apps/docs/public/guides/webhook.md b/apps/docs/public/guides/webhook.md
index 1ab746a2..156590af 100644
--- a/apps/docs/public/guides/webhook.md
+++ b/apps/docs/public/guides/webhook.md
@@ -104,6 +104,25 @@
- `webhook_timestamp: integer, int32` (required) — Дата и время отправки вебхука (UTC+0) в формате UNIX
+### Заполнение формы
+
+Вебхук отправляется при отправке пользователем заполненной формы ([представления](/guides/forms/overview)). Подробнее об обработке результатов — в разделе [Обработка форм](/guides/forms/handling).
+
+#### ViewSubmitWebhookPayload
+
+- `type: string` (required) — Тип объекта
+ Значения: `view` — Для формы всегда view
+- `event: string` (required) — Тип события
+ Значения: `submit` — Отправка формы
+- `callback_id: string` (required) — Идентификатор обратного вызова, указанный при открытии представления
+- `private_metadata: string` (required) — Приватные метаданные, указанные при открытии представления
+- `user_id: integer, int32` (required) — Идентификатор пользователя, который отправил форму
+- `data: Record` (required) — Данные заполненных полей представления. Ключ — `action_id` поля, значение — введённые данные
+ **Структура значений Record:**
+ - Тип значения: `any`
+- `webhook_timestamp: integer, int32` (required) — Дата и время отправки вебхука (UTC+0) в формате UNIX
+
+
### Изменение участников чатов
Вебхук отправляется при изменении состава участников чатов, где состоит бот, и в тредах этих чатов.
diff --git a/apps/docs/public/guides/workflows.md b/apps/docs/public/guides/workflows.md
index 6bf3f358..ffec4a40 100644
--- a/apps/docs/public/guides/workflows.md
+++ b/apps/docs/public/guides/workflows.md
@@ -37,6 +37,14 @@
> Кастомные поля настраиваются администратором пространства.
+**Загрузить аватар профиля**
+
+1. Загрузи аватар из файла
+
+**Удалить аватар профиля**
+
+1. Удали аватар
+
### pachca-users
**Получить сотрудника по ID**
@@ -85,6 +93,14 @@
2. Установить статус
3. Удалить статус
+**Загрузить аватар сотрудника**
+
+1. Загрузи аватар сотруднику
+
+**Удалить аватар сотрудника**
+
+1. Удали аватар сотрудника
+
### pachca-chats
**Создать канал и пригласить участников**
diff --git a/apps/docs/public/images/n8n/ai-agent-tool.avif b/apps/docs/public/images/n8n/ai-agent-tool.avif
new file mode 100644
index 00000000..6e1b14db
Binary files /dev/null and b/apps/docs/public/images/n8n/ai-agent-tool.avif differ
diff --git a/apps/docs/public/images/n8n/community-nodes.avif b/apps/docs/public/images/n8n/community-nodes.avif
deleted file mode 100644
index 5edbaa80..00000000
Binary files a/apps/docs/public/images/n8n/community-nodes.avif and /dev/null differ
diff --git a/apps/docs/public/images/n8n/credentials-list.avif b/apps/docs/public/images/n8n/credentials-list.avif
new file mode 100644
index 00000000..621e3eeb
Binary files /dev/null and b/apps/docs/public/images/n8n/credentials-list.avif differ
diff --git a/apps/docs/public/images/n8n/credentials-search.avif b/apps/docs/public/images/n8n/credentials-search.avif
new file mode 100644
index 00000000..a76dee87
Binary files /dev/null and b/apps/docs/public/images/n8n/credentials-search.avif differ
diff --git a/apps/docs/public/images/n8n/credentials-test.avif b/apps/docs/public/images/n8n/credentials-test.avif
new file mode 100644
index 00000000..30b22f19
Binary files /dev/null and b/apps/docs/public/images/n8n/credentials-test.avif differ
diff --git a/apps/docs/public/images/n8n/credentials-v2.avif b/apps/docs/public/images/n8n/credentials-v2.avif
new file mode 100644
index 00000000..2ca886be
Binary files /dev/null and b/apps/docs/public/images/n8n/credentials-v2.avif differ
diff --git a/apps/docs/public/images/n8n/credentials.avif b/apps/docs/public/images/n8n/credentials.avif
deleted file mode 100644
index 29a2efad..00000000
Binary files a/apps/docs/public/images/n8n/credentials.avif and /dev/null differ
diff --git a/apps/docs/public/images/n8n/error-invalid-token.avif b/apps/docs/public/images/n8n/error-invalid-token.avif
new file mode 100644
index 00000000..5cc6a6e5
Binary files /dev/null and b/apps/docs/public/images/n8n/error-invalid-token.avif differ
diff --git a/apps/docs/public/images/n8n/file-upload.avif b/apps/docs/public/images/n8n/file-upload.avif
new file mode 100644
index 00000000..ddd0c96a
Binary files /dev/null and b/apps/docs/public/images/n8n/file-upload.avif differ
diff --git a/apps/docs/public/images/n8n/main-dashboard.avif b/apps/docs/public/images/n8n/main-dashboard.avif
new file mode 100644
index 00000000..6d62c621
Binary files /dev/null and b/apps/docs/public/images/n8n/main-dashboard.avif differ
diff --git a/apps/docs/public/images/n8n/message-buttons.avif b/apps/docs/public/images/n8n/message-buttons.avif
new file mode 100644
index 00000000..0f3611d3
Binary files /dev/null and b/apps/docs/public/images/n8n/message-buttons.avif differ
diff --git a/apps/docs/public/images/n8n/message-get-many.avif b/apps/docs/public/images/n8n/message-get-many.avif
new file mode 100644
index 00000000..7650b472
Binary files /dev/null and b/apps/docs/public/images/n8n/message-get-many.avif differ
diff --git a/apps/docs/public/images/n8n/n8n-interface.avif b/apps/docs/public/images/n8n/n8n-interface.avif
index c3afcf80..79f2c6ba 100644
Binary files a/apps/docs/public/images/n8n/n8n-interface.avif and b/apps/docs/public/images/n8n/n8n-interface.avif differ
diff --git a/apps/docs/public/images/n8n/node-message-create.avif b/apps/docs/public/images/n8n/node-message-create.avif
new file mode 100644
index 00000000..ae09b5a0
Binary files /dev/null and b/apps/docs/public/images/n8n/node-message-create.avif differ
diff --git a/apps/docs/public/images/n8n/operation-dropdown.avif b/apps/docs/public/images/n8n/operation-dropdown.avif
new file mode 100644
index 00000000..e7b2786c
Binary files /dev/null and b/apps/docs/public/images/n8n/operation-dropdown.avif differ
diff --git a/apps/docs/public/images/n8n/owner-account.avif b/apps/docs/public/images/n8n/owner-account.avif
index 7f46456e..7819c733 100644
Binary files a/apps/docs/public/images/n8n/owner-account.avif and b/apps/docs/public/images/n8n/owner-account.avif differ
diff --git a/apps/docs/public/images/n8n/resource-dropdown-all.avif b/apps/docs/public/images/n8n/resource-dropdown-all.avif
new file mode 100644
index 00000000..0d670bab
Binary files /dev/null and b/apps/docs/public/images/n8n/resource-dropdown-all.avif differ
diff --git a/apps/docs/public/images/n8n/resource-dropdown.avif b/apps/docs/public/images/n8n/resource-dropdown.avif
new file mode 100644
index 00000000..4d4082f3
Binary files /dev/null and b/apps/docs/public/images/n8n/resource-dropdown.avif differ
diff --git a/apps/docs/public/images/n8n/return-all.avif b/apps/docs/public/images/n8n/return-all.avif
new file mode 100644
index 00000000..16668a06
Binary files /dev/null and b/apps/docs/public/images/n8n/return-all.avif differ
diff --git a/apps/docs/public/images/n8n/searchable-dropdown.avif b/apps/docs/public/images/n8n/searchable-dropdown.avif
new file mode 100644
index 00000000..5519fe03
Binary files /dev/null and b/apps/docs/public/images/n8n/searchable-dropdown.avif differ
diff --git a/apps/docs/public/images/n8n/trigger-add.avif b/apps/docs/public/images/n8n/trigger-add.avif
new file mode 100644
index 00000000..bfdf5f4f
Binary files /dev/null and b/apps/docs/public/images/n8n/trigger-add.avif differ
diff --git a/apps/docs/public/images/n8n/trigger-events.avif b/apps/docs/public/images/n8n/trigger-events.avif
new file mode 100644
index 00000000..221b678f
Binary files /dev/null and b/apps/docs/public/images/n8n/trigger-events.avif differ
diff --git a/apps/docs/public/images/n8n/trigger-node.avif b/apps/docs/public/images/n8n/trigger-node.avif
new file mode 100644
index 00000000..15bd11c4
Binary files /dev/null and b/apps/docs/public/images/n8n/trigger-node.avif differ
diff --git a/apps/docs/public/images/n8n/workflow-add-node.avif b/apps/docs/public/images/n8n/workflow-add-node.avif
new file mode 100644
index 00000000..bfdf5f4f
Binary files /dev/null and b/apps/docs/public/images/n8n/workflow-add-node.avif differ
diff --git a/apps/docs/public/images/n8n/workflow-approval.avif b/apps/docs/public/images/n8n/workflow-approval.avif
new file mode 100644
index 00000000..edb0e4df
Binary files /dev/null and b/apps/docs/public/images/n8n/workflow-approval.avif differ
diff --git a/apps/docs/public/images/n8n/workflow-editor.avif b/apps/docs/public/images/n8n/workflow-editor.avif
deleted file mode 100644
index 5d739a11..00000000
Binary files a/apps/docs/public/images/n8n/workflow-editor.avif and /dev/null differ
diff --git a/apps/docs/public/images/n8n/workflow-example.avif b/apps/docs/public/images/n8n/workflow-example.avif
deleted file mode 100644
index e6c4d515..00000000
Binary files a/apps/docs/public/images/n8n/workflow-example.avif and /dev/null differ
diff --git a/apps/docs/public/images/n8n/workflow-execute-success.avif b/apps/docs/public/images/n8n/workflow-execute-success.avif
new file mode 100644
index 00000000..44d41831
Binary files /dev/null and b/apps/docs/public/images/n8n/workflow-execute-success.avif differ
diff --git a/apps/docs/public/images/n8n/workflow-forward.avif b/apps/docs/public/images/n8n/workflow-forward.avif
new file mode 100644
index 00000000..38e9477c
Binary files /dev/null and b/apps/docs/public/images/n8n/workflow-forward.avif differ
diff --git a/apps/docs/public/images/n8n/workflow-monitoring.avif b/apps/docs/public/images/n8n/workflow-monitoring.avif
new file mode 100644
index 00000000..306c1e36
Binary files /dev/null and b/apps/docs/public/images/n8n/workflow-monitoring.avif differ
diff --git a/apps/docs/public/images/n8n/workflow-output-data.avif b/apps/docs/public/images/n8n/workflow-output-data.avif
new file mode 100644
index 00000000..f5f8cc47
Binary files /dev/null and b/apps/docs/public/images/n8n/workflow-output-data.avif differ
diff --git a/apps/docs/public/images/n8n/workflow-pachca-node.avif b/apps/docs/public/images/n8n/workflow-pachca-node.avif
new file mode 100644
index 00000000..03a40c53
Binary files /dev/null and b/apps/docs/public/images/n8n/workflow-pachca-node.avif differ
diff --git a/apps/docs/public/images/n8n/workflow-reminder.avif b/apps/docs/public/images/n8n/workflow-reminder.avif
new file mode 100644
index 00000000..a69fd775
Binary files /dev/null and b/apps/docs/public/images/n8n/workflow-reminder.avif differ
diff --git a/apps/docs/public/images/n8n/workflow-trigger-example.avif b/apps/docs/public/images/n8n/workflow-trigger-example.avif
new file mode 100644
index 00000000..566cd047
Binary files /dev/null and b/apps/docs/public/images/n8n/workflow-trigger-example.avif differ
diff --git a/apps/docs/public/images/n8n/workflow-vacation.avif b/apps/docs/public/images/n8n/workflow-vacation.avif
new file mode 100644
index 00000000..4553e63e
Binary files /dev/null and b/apps/docs/public/images/n8n/workflow-vacation.avif differ
diff --git a/apps/docs/public/images/n8n/workflow-welcome.avif b/apps/docs/public/images/n8n/workflow-welcome.avif
new file mode 100644
index 00000000..622e417b
Binary files /dev/null and b/apps/docs/public/images/n8n/workflow-welcome.avif differ
diff --git a/apps/docs/public/index.md b/apps/docs/public/index.md
index c0455705..e398e47a 100644
--- a/apps/docs/public/index.md
+++ b/apps/docs/public/index.md
@@ -8,6 +8,7 @@ REST API Пачки позволяет автоматизировать рабо
- [Модели API](/api/models)
- [CLI](/guides/cli)
- [SDK](/guides/sdk/overview)
+- [n8n](/guides/n8n/overview)
**Отправка сообщения**
@@ -68,7 +69,7 @@ curl "https://api.pachca.com/api/shared/v1/messages" \
Подключайте Пачку к внешним сервисам без написания кода.
-- [n8n](/guides/n8n) — Визуальные автоматизации
+- [n8n](/guides/n8n/overview) — Визуальные автоматизации
- [Albato](/guides/albato) — Подключение 250+ сервисов
diff --git a/apps/docs/public/llms-full.txt b/apps/docs/public/llms-full.txt
index e141a5eb..ccd19602 100644
--- a/apps/docs/public/llms-full.txt
+++ b/apps/docs/public/llms-full.txt
@@ -11,34 +11,41 @@
- [Быстрый старт](#быстрый-старт)
- [AI агенты](#ai-агенты)
- [CLI](#cli)
-- [Обзор](#обзор)
-- [TypeScript](#typescript)
-- [Python](#python)
-- [Go](#go)
-- [Kotlin](#kotlin)
-- [Swift](#swift)
-- [CSharp](#csharp)
+- [SDK: Обзор](#sdk:-обзор)
+- [SDK: TypeScript](#sdk:-typescript)
+- [SDK: Python](#sdk:-python)
+- [SDK: Go](#sdk:-go)
+- [SDK: Kotlin](#sdk:-kotlin)
+- [SDK: Swift](#sdk:-swift)
+- [SDK: C#](#sdk:-c)
- [Сценарии](#сценарии)
- [Боты](#боты)
- [Входящие вебхуки](#входящие-вебхуки)
- [Исходящие вебхуки](#исходящие-вебхуки)
- [Кнопки в сообщениях](#кнопки-в-сообщениях)
-- [Обзор](#обзор)
-- [Блоки представления](#блоки-представления)
-- [Обработка форм](#обработка-форм)
+- [Формы: Обзор](#формы:-обзор)
+- [Формы: Блоки представления](#формы:-блоки-представления)
+- [Формы: Обработка форм](#формы:-обработка-форм)
- [Разворачивание ссылок](#разворачивание-ссылок)
- [Экспорт сообщений](#экспорт-сообщений)
- [DLP-система](#dlp-система)
- [Журнал аудита событий](#журнал-аудита-событий)
-- [n8n](#n8n)
+- [n8n: Обзор](#n8n:-обзор)
+- [n8n: Начало работы](#n8n:-начало-работы)
+- [n8n: Ресурсы и операции](#n8n:-ресурсы-и-операции)
+- [n8n: Триггер](#n8n:-триггер)
+- [n8n: Примеры workflow](#n8n:-примеры-workflow)
+- [n8n: Продвинутые функции](#n8n:-продвинутые-функции)
+- [n8n: Устранение ошибок](#n8n:-устранение-ошибок)
+- [n8n: Миграция с v1](#n8n:-миграция-с-v1)
- [Albato](#albato)
- [Последние обновления](#последние-обновления)
-- [Авторизация](#авторизация)
-- [Запросы и ответы](#запросы-и-ответы)
-- [Пагинация](#пагинация)
-- [Загрузка файлов](#загрузка-файлов)
-- [Ошибки и лимиты](#ошибки-и-лимиты)
-- [Модели](#модели)
+- [Основы API: Авторизация](#основы-api:-авторизация)
+- [Основы API: Запросы и ответы](#основы-api:-запросы-и-ответы)
+- [Основы API: Пагинация](#основы-api:-пагинация)
+- [Основы API: Загрузка файлов](#основы-api:-загрузка-файлов)
+- [Основы API: Ошибки и лимиты](#основы-api:-ошибки-и-лимиты)
+- [Основы API: Модели](#основы-api:-модели)
### API Методы
- [Common](#api-common)
@@ -142,6 +149,7 @@ REST API Пачки позволяет автоматизировать рабо
- [Модели API](/api/models)
- [CLI](/guides/cli)
- [SDK](/guides/sdk/overview)
+- [n8n](/guides/n8n/overview)
**Отправка сообщения**
@@ -202,7 +210,7 @@ curl "https://api.pachca.com/api/shared/v1/messages" \
Подключайте Пачку к внешним сервисам без написания кода.
-- [n8n](/guides/n8n) — Визуальные автоматизации
+- [n8n](/guides/n8n/overview) — Визуальные автоматизации
- [Albato](/guides/albato) — Подключение 250+ сервисов
@@ -464,7 +472,7 @@ npx skills add pachca/openapi
# CLI
-[@pachca/cli](https://www.npmjs.com/package/@pachca/cli) 2026.3.10 · 21 марта 2026
+[@pachca/cli](https://www.npmjs.com/package/@pachca/cli) 2026.4.0 · 7 апреля 2026
Официальный CLI для работы с Pachca API из терминала. Каждый API-метод доступен как команда с типизированными флагами, валидацией и интерактивными подсказками. Требуется Node.js 20 или новее.
@@ -595,15 +603,19 @@ dev.pachca.com/api/members/add → pachca members add
| `pachca profile get-info` | Информация о токене |
| `pachca profile get` | Информация о профиле |
| `pachca profile get-status` | Текущий статус |
+| `pachca profile update-avatar` | Загрузка аватара |
| `pachca profile update-status` | Новый статус |
+| `pachca profile delete-avatar` | Удаление аватара |
| `pachca profile delete-status` | Удаление статуса |
| `pachca users create` | Создать сотрудника |
| `pachca users list` | Список сотрудников |
| `pachca users get` | Информация о сотруднике |
| `pachca users get-status` | Статус сотрудника |
| `pachca users update` | Редактирование сотрудника |
+| `pachca users update-avatar` | Загрузка аватара сотрудника |
| `pachca users update-status` | Новый статус сотрудника |
| `pachca users delete` | Удаление сотрудника |
+| `pachca users remove-avatar` | Удаление аватара сотрудника |
| `pachca users remove-status` | Удаление статуса сотрудника |
| `pachca group-tags create` | Новый тег |
| `pachca group-tags list` | Список тегов сотрудников |
@@ -1136,7 +1148,7 @@ npx @pachca/generator --spec https://example.com/openapi.yaml --output ./generat
|-------------|----------|
| **16 сервисов** | Типизированные методы для каждого API-эндпоинта |
| **Автопагинация** | Методы `*All()` для автоматического обхода всех страниц |
-| **Повторные запросы** | Автоматический retry при `429` с экспоненциальным backoff |
+| **Повторные запросы** | Автоматический retry при `429` и `5xx` с экспоненциальным backoff |
| **Обработка ошибок** | Типизированные `ApiError` и `OAuthError` |
| **Сериализация** | Автоматическая конвертация между форматами (snake_case ↔ camelCase) |
| **Авторизация** | Bearer-токен передаётся один раз при создании клиента |
@@ -1235,15 +1247,19 @@ const client = new PachcaClient("YOUR_TOKEN", "https://custom-api.example.com/ap
| `client.profile.getTokenInfo()` | [Информация о токене](/api/profile/get-info) |
| `client.profile.getProfile()` | [Информация о профиле](/api/profile/get) |
| `client.profile.getStatus()` | [Текущий статус](/api/profile/get-status) |
+| `client.profile.updateProfileAvatar()` | [Загрузка аватара](/api/profile/update-avatar) |
| `client.profile.updateStatus()` | [Новый статус](/api/profile/update-status) |
+| `client.profile.deleteProfileAvatar()` | [Удаление аватара](/api/profile/delete-avatar) |
| `client.profile.deleteStatus()` | [Удаление статуса](/api/profile/delete-status) |
| `client.users.createUser()` | [Создать сотрудника](/api/users/create) |
| `client.users.listUsers()` | [Список сотрудников](/api/users/list) |
| `client.users.getUser()` | [Информация о сотруднике](/api/users/get) |
| `client.users.getUserStatus()` | [Статус сотрудника](/api/users/get-status) |
| `client.users.updateUser()` | [Редактирование сотрудника](/api/users/update) |
+| `client.users.updateUserAvatar()` | [Загрузка аватара сотрудника](/api/users/update-avatar) |
| `client.users.updateUserStatus()` | [Новый статус сотрудника](/api/users/update-status) |
| `client.users.deleteUser()` | [Удаление сотрудника](/api/users/delete) |
+| `client.users.deleteUserAvatar()` | [Удаление аватара сотрудника](/api/users/remove-avatar) |
| `client.users.deleteUserStatus()` | [Удаление статуса сотрудника](/api/users/remove-status) |
| `client.groupTags.createTag()` | [Новый тег](/api/group-tags/create) |
| `client.groupTags.listTags()` | [Список тегов сотрудников](/api/group-tags/list) |
@@ -1300,11 +1316,12 @@ const client = new PachcaClient("YOUR_TOKEN", "https://custom-api.example.com/ap
**GET с параметрами:**
```typescript
-import { ChatAvailability, SortOrder } from "@pachca/sdk"
+import { ChatAvailability, ChatSortField, SortOrder } from "@pachca/sdk"
// Список чатов
const response = client.chats.listChats({
- sortId: SortOrder.Desc,
+ sort: ChatSortField.Id,
+ order: SortOrder.Desc,
availability: ChatAvailability.IsMember,
lastMessageAtAfter: "2025-01-01T00:00:00.000Z",
lastMessageAtBefore: "2025-02-01T00:00:00.000Z",
@@ -1312,7 +1329,7 @@ const response = client.chats.listChats({
limit: 1,
cursor: "eyJpZCI6MTAsImRpciI6ImFzYyJ9"
})
-// → ListChatsResponse({ data: Chat[], meta?: PaginationMeta })
+// → ListChatsResponse({ data: Chat[], meta: PaginationMeta })
```
@@ -1347,20 +1364,21 @@ const response = client.chats.getChat(334)
## Пагинация
-Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит поле `meta.paginate.nextPage` — курсор для следующей страницы.
+Методы, возвращающие списки, используют cursor-based пагинацию. Ответ всегда содержит поле `meta.paginate.nextPage` — курсор для следующей страницы. Курсор никогда не бывает `null` — конец данных определяется по пустому массиву `data`.
### Ручная пагинация
```typescript
let cursor: string | undefined
-do {
+for (;;) {
const response = await client.users.listUsers({ limit: 50, cursor })
+ if (response.data.length === 0) break
for (const user of response.data) {
console.log(user.firstName, user.lastName)
}
- cursor = response.meta?.paginate?.nextPage
-} while (cursor)
+ cursor = response.meta.paginate.nextPage
+}
```
### Автопагинация
@@ -1442,12 +1460,12 @@ try {
## Повторные запросы
-SDK автоматически повторяет запрос при получении `429 Too Many Requests`:
+SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx` (`500`, `502`, `503`, `504`):
- До **3 повторов** на каждый запрос
-- Если сервер вернул заголовок `Retry-After` — ждёт указанное время
-- Иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
-- Все остальные ошибки возвращаются сразу без retry
+- **429:** если сервер вернул заголовок `Retry-After` — ждёт указанное время, иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **5xx:** экспоненциальный backoff с jitter: ~10 сек, ~20 сек, ~40 сек
+- Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов
## Сериализация
@@ -1534,7 +1552,7 @@ const response = client.users.listUsers({
limit: 1,
cursor: "eyJpZCI6MTAsImRpciI6ImFzYyJ9"
})
-// → ListUsersResponse({ data: User[], meta?: PaginationMeta })
+// → ListUsersResponse({ data: User[], meta: PaginationMeta })
// Создание задачи
const request: TaskCreateRequest = {
@@ -1591,7 +1609,7 @@ client = PachcaClient("YOUR_TOKEN")
```python
# Получение профиля
response = await client.profile.get_profile()
-# → User(id: int, first_name: str, last_name: str, nickname: str, email: str, phone_number: str, department: str, title: str, role: UserRole, suspended: bool, invite_status: InviteStatus, list_tags: list[str], custom_properties: list[CustomProperty(id: int, name: str, data_type: CustomPropertyDataType, value: str)], user_status: UserStatus(emoji: str, title: str, expires_at: str | None, is_away: bool, away_message: UserStatusAwayMessage(text: str) | None) | None, bot: bool, sso: bool, created_at: str, last_activity_at: str, time_zone: str, image_url: str | None)
+# → User(id: int, first_name: str, last_name: str, nickname: str, email: str, phone_number: str, department: str, title: str, role: UserRole, suspended: bool, invite_status: InviteStatus, list_tags: list[str], custom_properties: list[CustomProperty(id: int, name: str, data_type: CustomPropertyDataType, value: str)], user_status: UserStatus(emoji: str, title: str, expires_at: datetime | None, is_away: bool, away_message: UserStatusAwayMessage(text: str) | None) | None, bot: bool, sso: bool, created_at: datetime, last_activity_at: datetime, time_zone: str, image_url: str | None)
```
@@ -1633,15 +1651,19 @@ await client.close()
| `client.profile.get_token_info()` | [Информация о токене](/api/profile/get-info) |
| `client.profile.get_profile()` | [Информация о профиле](/api/profile/get) |
| `client.profile.get_status()` | [Текущий статус](/api/profile/get-status) |
+| `client.profile.update_profile_avatar()` | [Загрузка аватара](/api/profile/update-avatar) |
| `client.profile.update_status()` | [Новый статус](/api/profile/update-status) |
+| `client.profile.delete_profile_avatar()` | [Удаление аватара](/api/profile/delete-avatar) |
| `client.profile.delete_status()` | [Удаление статуса](/api/profile/delete-status) |
| `client.users.create_user()` | [Создать сотрудника](/api/users/create) |
| `client.users.list_users()` | [Список сотрудников](/api/users/list) |
| `client.users.get_user()` | [Информация о сотруднике](/api/users/get) |
| `client.users.get_user_status()` | [Статус сотрудника](/api/users/get-status) |
| `client.users.update_user()` | [Редактирование сотрудника](/api/users/update) |
+| `client.users.update_user_avatar()` | [Загрузка аватара сотрудника](/api/users/update-avatar) |
| `client.users.update_user_status()` | [Новый статус сотрудника](/api/users/update-status) |
| `client.users.delete_user()` | [Удаление сотрудника](/api/users/delete) |
+| `client.users.delete_user_avatar()` | [Удаление аватара сотрудника](/api/users/remove-avatar) |
| `client.users.delete_user_status()` | [Удаление статуса сотрудника](/api/users/remove-status) |
| `client.group_tags.create_tag()` | [Новый тег](/api/group-tags/create) |
| `client.group_tags.list_tags()` | [Список тегов сотрудников](/api/group-tags/list) |
@@ -1698,20 +1720,21 @@ await client.close()
**GET с параметрами:**
```python
-from pachca.models import ChatAvailability, ListChatsParams, SortOrder
+from pachca.models import ChatAvailability, ChatSortField, ListChatsParams, SortOrder
# Список чатов
params = ListChatsParams(
- sort_id=SortOrder.DESC,
+ sort=ChatSortField.ID,
+ order=SortOrder.DESC,
availability=ChatAvailability.IS_MEMBER,
- last_message_at_after="2025-01-01T00:00:00.000Z",
- last_message_at_before="2025-02-01T00:00:00.000Z",
+ last_message_at_after=datetime.fromisoformat("2025-01-01T00:00:00.000Z"),
+ last_message_at_before=datetime.fromisoformat("2025-02-01T00:00:00.000Z"),
personal=False,
limit=1,
cursor="eyJpZCI6MTAsImRpciI6ImFzYyJ9"
)
response = await client.chats.list_chats(params=params)
-# → ListChatsResponse(data: list[Chat], meta: PaginationMeta | None)
+# → ListChatsResponse(data: list[Chat], meta: PaginationMeta)
```
@@ -1731,7 +1754,7 @@ request = ChatCreateRequest(
)
)
response = await client.chats.create_chat(request=request)
-# → Chat(id: int, name: str, created_at: str, owner_id: int, member_ids: list[int], group_tag_ids: list[int], channel: bool, personal: bool, public: bool, last_message_at: str, meet_room_url: str)
+# → Chat(id: int, name: str, created_at: datetime, owner_id: int, member_ids: list[int], group_tag_ids: list[int], channel: bool, personal: bool, public: bool, last_message_at: datetime, meet_room_url: str)
```
@@ -1740,13 +1763,13 @@ response = await client.chats.create_chat(request=request)
```python
# Получение чата
response = await client.chats.get_chat(id=334)
-# → Chat(id: int, name: str, created_at: str, owner_id: int, member_ids: list[int], group_tag_ids: list[int], channel: bool, personal: bool, public: bool, last_message_at: str, meet_room_url: str)
+# → Chat(id: int, name: str, created_at: datetime, owner_id: int, member_ids: list[int], group_tag_ids: list[int], channel: bool, personal: bool, public: bool, last_message_at: datetime, meet_room_url: str)
```
## Пагинация
-Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит `meta.paginate.next_page` — курсор для следующей страницы.
+Методы, возвращающие списки, используют cursor-based пагинацию. Ответ всегда содержит `meta.paginate.next_page` — курсор для следующей страницы. Курсор никогда не бывает `None` — конец данных определяется по пустому списку `data`.
### Ручная пагинация
@@ -1756,10 +1779,10 @@ from pachca.models import ListUsersParams
cursor = None
while True:
response = await client.users.list_users(ListUsersParams(limit=50, cursor=cursor))
+ if not response.data:
+ break
for user in response.data:
print(user.first_name, user.last_name)
- if not response.meta or not response.meta.paginate or not response.meta.paginate.next_page:
- break
cursor = response.meta.paginate.next_page
```
@@ -1836,12 +1859,13 @@ except OAuthError as error:
## Повторные запросы
-SDK автоматически повторяет запрос при получении `429 Too Many Requests`:
+SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx` (`500`, `502`, `503`, `504`):
- До **3 повторов** на каждый запрос
-- Если сервер вернул заголовок `Retry-After` — ждёт указанное время
-- Иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **429:** если сервер вернул заголовок `Retry-After` — ждёт указанное время, иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **5xx:** экспоненциальный backoff с jitter: ~10 сек, ~20 сек, ~40 сек
- Реализовано через `RetryTransport` — обёртку над httpx-транспортом
+- Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов
## Типы
@@ -1911,7 +1935,7 @@ request = MessageCreateRequest(
link_preview=False
)
response = await client.messages.create_message(request=request)
-# → Message(id: int, entity_type: MessageEntityType, entity_id: int, chat_id: int, root_chat_id: int, content: str, user_id: int, created_at: str, url: str, files: list[File(id: int, key: str, name: str, file_type: FileType, url: str, width: int | None, height: int | None)], buttons: list[list[Button(text: str, url: str | None, data: str | None)]] | None, thread: MessageThread(id: int, chat_id: int) | None, forwarding: Forwarding(original_message_id: int, original_chat_id: int, author_id: int, original_created_at: str, original_thread_id: int | None, original_thread_message_id: int | None, original_thread_parent_chat_id: int | None) | None, parent_message_id: int | None, display_avatar_url: str | None, display_name: str | None, changed_at: str | None, deleted_at: str | None)
+# → Message(id: int, entity_type: MessageEntityType, entity_id: int, chat_id: int, root_chat_id: int, content: str, user_id: int, created_at: datetime, url: str, files: list[File(id: int, key: str, name: str, file_type: FileType, url: str, width: int | None, height: int | None)], buttons: list[list[Button(text: str, url: str | None, data: str | None)]] | None, thread: MessageThread(id: int, chat_id: int) | None, forwarding: Forwarding(original_message_id: int, original_chat_id: int, author_id: int, original_created_at: datetime, original_thread_id: int | None, original_thread_message_id: int | None, original_thread_parent_chat_id: int | None) | None, parent_message_id: int | None, display_avatar_url: str | None, display_name: str | None, changed_at: datetime | None, deleted_at: datetime | None)
# Список сотрудников
params = ListUsersParams(
@@ -1920,14 +1944,14 @@ params = ListUsersParams(
cursor="eyJpZCI6MTAsImRpciI6ImFzYyJ9"
)
response = await client.users.list_users(params=params)
-# → ListUsersResponse(data: list[User], meta: PaginationMeta | None)
+# → ListUsersResponse(data: list[User], meta: PaginationMeta)
# Создание задачи
request = TaskCreateRequest(
task=TaskCreateRequestTask(
kind=TaskKind.REMINDER,
content="Забрать со склада 21 заказ",
- due_at="2020-06-05T12:00:00.000+03:00",
+ due_at=datetime.fromisoformat("2020-06-05T12:00:00.000+03:00"),
priority=2,
performer_ids=[123],
chat_id=456,
@@ -1936,7 +1960,7 @@ request = TaskCreateRequest(
)
)
response = await client.tasks.create_task(request=request)
-# → Task(id: int, kind: TaskKind, content: str, due_at: str | None, priority: int, user_id: int, chat_id: int | None, status: TaskStatus, created_at: str, performer_ids: list[int], all_day: bool, custom_properties: list[CustomProperty(id: int, name: str, data_type: CustomPropertyDataType, value: str)])
+# → Task(id: int, kind: TaskKind, content: str, due_at: datetime | None, priority: int, user_id: int, chat_id: int | None, status: TaskStatus, created_at: datetime, performer_ids: list[int], all_day: bool, custom_properties: list[CustomProperty(id: int, name: str, data_type: CustomPropertyDataType, value: str)])
```
@@ -2021,15 +2045,19 @@ user, err := client.Profile.GetProfile(ctx)
| `client.Profile.GetTokenInfo()` | [Информация о токене](/api/profile/get-info) |
| `client.Profile.GetProfile()` | [Информация о профиле](/api/profile/get) |
| `client.Profile.GetStatus()` | [Текущий статус](/api/profile/get-status) |
+| `client.Profile.UpdateProfileAvatar()` | [Загрузка аватара](/api/profile/update-avatar) |
| `client.Profile.UpdateStatus()` | [Новый статус](/api/profile/update-status) |
+| `client.Profile.DeleteProfileAvatar()` | [Удаление аватара](/api/profile/delete-avatar) |
| `client.Profile.DeleteStatus()` | [Удаление статуса](/api/profile/delete-status) |
| `client.Users.CreateUser()` | [Создать сотрудника](/api/users/create) |
| `client.Users.ListUsers()` | [Список сотрудников](/api/users/list) |
| `client.Users.GetUser()` | [Информация о сотруднике](/api/users/get) |
| `client.Users.GetUserStatus()` | [Статус сотрудника](/api/users/get-status) |
| `client.Users.UpdateUser()` | [Редактирование сотрудника](/api/users/update) |
+| `client.Users.UpdateUserAvatar()` | [Загрузка аватара сотрудника](/api/users/update-avatar) |
| `client.Users.UpdateUserStatus()` | [Новый статус сотрудника](/api/users/update-status) |
| `client.Users.DeleteUser()` | [Удаление сотрудника](/api/users/delete) |
+| `client.Users.DeleteUserAvatar()` | [Удаление аватара сотрудника](/api/users/remove-avatar) |
| `client.Users.DeleteUserStatus()` | [Удаление статуса сотрудника](/api/users/remove-status) |
| `client.GroupTags.CreateTag()` | [Новый тег](/api/group-tags/create) |
| `client.GroupTags.ListTags()` | [Список тегов сотрудников](/api/group-tags/list) |
@@ -2090,7 +2118,8 @@ import pachca "github.com/pachca/openapi/sdk/go/generated"
// Список чатов
params := &ListChatsParams{
- SortID: Ptr(SortOrderDesc),
+ Sort: Ptr(ChatSortFieldID),
+ Order: Ptr(SortOrderDesc),
Availability: Ptr(ChatAvailabilityIsMember),
LastMessageAtAfter: Ptr("2025-01-01T00:00:00.000Z"),
LastMessageAtBefore: Ptr("2025-02-01T00:00:00.000Z"),
@@ -2099,7 +2128,7 @@ params := &ListChatsParams{
Cursor: Ptr("eyJpZCI6MTAsImRpciI6ImFzYyJ9"),
}
response, err := client.Chats.ListChats(ctx, params)
-// → ListChatsResponse{Data: []Chat, Meta: *PaginationMeta}
+// → ListChatsResponse{Data: []Chat, Meta: PaginationMeta}
```
@@ -2149,7 +2178,7 @@ request := pachca.ChatUpdateRequest{
## Пагинация
-Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит `Meta.Paginate.NextPage` — курсор для следующей страницы.
+Методы, возвращающие списки, используют cursor-based пагинацию. Ответ всегда содержит `Meta.Paginate.NextPage` — курсор для следующей страницы. Курсор никогда не бывает пустым — конец данных определяется по пустому слайсу `Data`.
### Ручная пагинация
@@ -2161,13 +2190,14 @@ for {
if err != nil {
log.Fatal(err)
}
+ if len(response.Data) == 0 {
+ break
+ }
for _, user := range response.Data {
fmt.Println(user.FirstName, user.LastName)
}
- if response.Meta == nil || response.Meta.Paginate == nil || response.Meta.Paginate.NextPage == nil {
- break
- }
- cursor = response.Meta.Paginate.NextPage
+ nextPage := response.Meta.Paginate.NextPage
+ cursor = &nextPage
}
```
@@ -2250,12 +2280,13 @@ if err != nil {
## Повторные запросы
-SDK автоматически повторяет запрос при получении `429 Too Many Requests`:
+SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx` (`500`, `502`, `503`, `504`):
- До **3 повторов** на каждый запрос
-- Если сервер вернул заголовок `Retry-After` — ждёт указанное время
-- Иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **429:** если сервер вернул заголовок `Retry-After` — ждёт указанное время, иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **5xx:** экспоненциальный backoff с jitter: ~10 сек, ~20 сек, ~40 сек
- Тело запроса пересоздаётся через `req.GetBody()` при каждой попытке
+- Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов
## Типы
@@ -2328,7 +2359,7 @@ params := &ListUsersParams{
Cursor: Ptr("eyJpZCI6MTAsImRpciI6ImFzYyJ9"),
}
response, err := client.Users.ListUsers(ctx, params)
-// → ListUsersResponse{Data: []User, Meta: *PaginationMeta}
+// → ListUsersResponse{Data: []User, Meta: PaginationMeta}
// Создание задачи
request := TaskCreateRequest{
@@ -2371,7 +2402,7 @@ response, err := client.Tasks.CreateTask(ctx, request)
```kotlin
dependencies {
- implementation("com.pachca:pachca-sdk:1.0.1")
+ implementation("com.pachca:pachca-sdk:latest.release")
}
```
@@ -2392,7 +2423,7 @@ val client = PachcaClient("YOUR_TOKEN")
```kotlin
// Получение профиля
val response = client.profile.getProfile()
-// → User(id: Int, firstName: String, lastName: String, nickname: String, email: String, phoneNumber: String, department: String, title: String, role: UserRole, suspended: Boolean, inviteStatus: InviteStatus, listTags: List, customProperties: List, userStatus: UserStatus(emoji: String, title: String, expiresAt: String?, isAway: Boolean, awayMessage: UserStatusAwayMessage(text: String)?)?, bot: Boolean, sso: Boolean, createdAt: String, lastActivityAt: String, timeZone: String, imageUrl: String?)
+// → User(id: Int, firstName: String, lastName: String, nickname: String, email: String, phoneNumber: String, department: String, title: String, role: UserRole, suspended: Boolean, inviteStatus: InviteStatus, listTags: List, customProperties: List, userStatus: UserStatus(emoji: String, title: String, expiresAt: OffsetDateTime?, isAway: Boolean, awayMessage: UserStatusAwayMessage(text: String)?)?, bot: Boolean, sso: Boolean, createdAt: OffsetDateTime, lastActivityAt: OffsetDateTime, timeZone: String, imageUrl: String?)
```
@@ -2440,15 +2471,19 @@ client.close()
| `client.profile.getTokenInfo()` | [Информация о токене](/api/profile/get-info) |
| `client.profile.getProfile()` | [Информация о профиле](/api/profile/get) |
| `client.profile.getStatus()` | [Текущий статус](/api/profile/get-status) |
+| `client.profile.updateProfileAvatar()` | [Загрузка аватара](/api/profile/update-avatar) |
| `client.profile.updateStatus()` | [Новый статус](/api/profile/update-status) |
+| `client.profile.deleteProfileAvatar()` | [Удаление аватара](/api/profile/delete-avatar) |
| `client.profile.deleteStatus()` | [Удаление статуса](/api/profile/delete-status) |
| `client.users.createUser()` | [Создать сотрудника](/api/users/create) |
| `client.users.listUsers()` | [Список сотрудников](/api/users/list) |
| `client.users.getUser()` | [Информация о сотруднике](/api/users/get) |
| `client.users.getUserStatus()` | [Статус сотрудника](/api/users/get-status) |
| `client.users.updateUser()` | [Редактирование сотрудника](/api/users/update) |
+| `client.users.updateUserAvatar()` | [Загрузка аватара сотрудника](/api/users/update-avatar) |
| `client.users.updateUserStatus()` | [Новый статус сотрудника](/api/users/update-status) |
| `client.users.deleteUser()` | [Удаление сотрудника](/api/users/delete) |
+| `client.users.deleteUserAvatar()` | [Удаление аватара сотрудника](/api/users/remove-avatar) |
| `client.users.deleteUserStatus()` | [Удаление статуса сотрудника](/api/users/remove-status) |
| `client.groupTags.createTag()` | [Новый тег](/api/group-tags/create) |
| `client.groupTags.listTags()` | [Список тегов сотрудников](/api/group-tags/list) |
@@ -2506,11 +2541,14 @@ client.close()
```kotlin
import com.pachca.sdk.ChatAvailability
+import com.pachca.sdk.ChatSortField
import com.pachca.sdk.SortOrder
// Список чатов
-val response = client.chats.listChats(sortId = SortOrder.DESC, availability = ChatAvailability.IS_MEMBER, lastMessageAtAfter = "2025-01-01T00:00:00.000Z", lastMessageAtBefore = "2025-02-01T00:00:00.000Z", personal = false, limit = 1, cursor = "eyJpZCI6MTAsImRpciI6ImFzYyJ9")
-// → ListChatsResponse(data: List, meta: PaginationMeta?)
+val lastMessageAtAfter = OffsetDateTime.parse("2025-01-01T00:00:00.000Z")
+val lastMessageAtBefore = OffsetDateTime.parse("2025-02-01T00:00:00.000Z")
+val response = client.chats.listChats(sort = ChatSortField.ID, order = SortOrder.DESC, availability = ChatAvailability.IS_MEMBER, lastMessageAtAfter = lastMessageAtAfter, lastMessageAtBefore = lastMessageAtBefore, personal = false, limit = 1, cursor = "eyJpZCI6MTAsImRpciI6ImFzYyJ9")
+// → ListChatsResponse(data: List, meta: PaginationMeta)
```
@@ -2531,7 +2569,7 @@ val request = ChatCreateRequest(
)
)
val response = client.chats.createChat(request = request)
-// → Chat(id: Int, name: String, createdAt: String, ownerId: Int, memberIds: List, groupTagIds: List, channel: Boolean, personal: Boolean, public: Boolean, lastMessageAt: String, meetRoomUrl: String)
+// → Chat(id: Int, name: String, createdAt: OffsetDateTime, ownerId: Int, memberIds: List, groupTagIds: List, channel: Boolean, personal: Boolean, public: Boolean, lastMessageAt: OffsetDateTime, meetRoomUrl: String)
```
@@ -2540,25 +2578,26 @@ val response = client.chats.createChat(request = request)
```kotlin
// Получение чата
val response = client.chats.getChat(id = 334)
-// → Chat(id: Int, name: String, createdAt: String, ownerId: Int, memberIds: List, groupTagIds: List, channel: Boolean, personal: Boolean, public: Boolean, lastMessageAt: String, meetRoomUrl: String)
+// → Chat(id: Int, name: String, createdAt: OffsetDateTime, ownerId: Int, memberIds: List, groupTagIds: List, channel: Boolean, personal: Boolean, public: Boolean, lastMessageAt: OffsetDateTime, meetRoomUrl: String)
```
## Пагинация
-Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит `meta?.paginate?.nextPage` — курсор для следующей страницы.
+Методы, возвращающие списки, используют cursor-based пагинацию. Ответ всегда содержит `meta.paginate.nextPage` — курсор для следующей страницы. Курсор никогда не бывает `null` — конец данных определяется по пустому массиву `data`.
### Ручная пагинация
```kotlin
var cursor: String? = null
-do {
+while (true) {
val response = client.users.listUsers(limit = 50, cursor = cursor)
+ if (response.data.isEmpty()) break
for (user in response.data) {
println("${user.firstName} ${user.lastName}")
}
- cursor = response.meta?.paginate?.nextPage
-} while (cursor != null)
+ cursor = response.meta.paginate.nextPage
+}
```
### Автопагинация
@@ -2637,12 +2676,13 @@ try {
## Повторные запросы
-SDK автоматически повторяет запрос при получении `429 Too Many Requests` или серверной ошибки (`5xx`):
+SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx` (`500`, `502`, `503`, `504`):
- До **3 повторов** на каждый запрос
-- Если сервер вернул заголовок `Retry-After` — ждёт указанное время
-- Иначе — линейный backoff: 1 сек, 2 сек, 3 сек
+- **429:** если сервер вернул заголовок `Retry-After` — ждёт указанное время, иначе — экспоненциальный backoff с jitter
+- **5xx:** экспоненциальный backoff с jitter: ~10 сек, ~20 сек, ~40 сек
- Реализовано через плагин Ktor `HttpRequestRetry`
+- Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов
## Типы
@@ -2726,18 +2766,18 @@ val request = MessageCreateRequest(
linkPreview = false
)
val response = client.messages.createMessage(request = request)
-// → Message(id: Int, entityType: MessageEntityType, entityId: Int, chatId: Int, rootChatId: Int, content: String, userId: Int, createdAt: String, url: String, files: List, buttons: List>?, thread: MessageThread(id: Long, chatId: Long)?, forwarding: Forwarding(originalMessageId: Int, originalChatId: Int, authorId: Int, originalCreatedAt: String, originalThreadId: Int?, originalThreadMessageId: Int?, originalThreadParentChatId: Int?)?, parentMessageId: Int?, displayAvatarUrl: String?, displayName: String?, changedAt: String?, deletedAt: String?)
+// → Message(id: Int, entityType: MessageEntityType, entityId: Int, chatId: Int, rootChatId: Int, content: String, userId: Int, createdAt: OffsetDateTime, url: String, files: List, buttons: List>?, thread: MessageThread(id: Long, chatId: Long)?, forwarding: Forwarding(originalMessageId: Int, originalChatId: Int, authorId: Int, originalCreatedAt: OffsetDateTime, originalThreadId: Int?, originalThreadMessageId: Int?, originalThreadParentChatId: Int?)?, parentMessageId: Int?, displayAvatarUrl: String?, displayName: String?, changedAt: OffsetDateTime?, deletedAt: OffsetDateTime?)
// Список сотрудников
val response = client.users.listUsers(query = "Олег", limit = 1, cursor = "eyJpZCI6MTAsImRpciI6ImFzYyJ9")
-// → ListUsersResponse(data: List, meta: PaginationMeta?)
+// → ListUsersResponse(data: List, meta: PaginationMeta)
// Создание задачи
val request = TaskCreateRequest(
task = TaskCreateRequestTask(
kind = TaskKind.REMINDER,
content = "Забрать со склада 21 заказ",
- dueAt = "2020-06-05T12:00:00.000+03:00",
+ dueAt = OffsetDateTime.parse("2020-06-05T12:00:00.000+03:00"),
priority = 2,
performerIds = listOf(123),
chatId = 456,
@@ -2746,7 +2786,7 @@ val request = TaskCreateRequest(
)
)
val response = client.tasks.createTask(request = request)
-// → Task(id: Int, kind: TaskKind, content: String, dueAt: String?, priority: Int, userId: Int, chatId: Int?, status: TaskStatus, createdAt: String, performerIds: List, allDay: Boolean, customProperties: List)
+// → Task(id: Int, kind: TaskKind, content: String, dueAt: OffsetDateTime?, priority: Int, userId: Int, chatId: Int?, status: TaskStatus, createdAt: OffsetDateTime, performerIds: List, allDay: Boolean, customProperties: List)
```
@@ -2770,7 +2810,7 @@ val response = client.tasks.createTask(request = request)
```swift
dependencies: [
- .package(url: "https://github.com/pachca/openapi", from: "1.0.1")
+ .package(url: "https://github.com/pachca/openapi", from: "1.0.0")
]
```
@@ -2830,15 +2870,19 @@ let client = PachcaClient(token: "YOUR_TOKEN", baseURL: "https://custom-api.exam
| `client.profile.getTokenInfo()` | [Информация о токене](/api/profile/get-info) |
| `client.profile.getProfile()` | [Информация о профиле](/api/profile/get) |
| `client.profile.getStatus()` | [Текущий статус](/api/profile/get-status) |
+| `client.profile.updateProfileAvatar()` | [Загрузка аватара](/api/profile/update-avatar) |
| `client.profile.updateStatus()` | [Новый статус](/api/profile/update-status) |
+| `client.profile.deleteProfileAvatar()` | [Удаление аватара](/api/profile/delete-avatar) |
| `client.profile.deleteStatus()` | [Удаление статуса](/api/profile/delete-status) |
| `client.users.createUser()` | [Создать сотрудника](/api/users/create) |
| `client.users.listUsers()` | [Список сотрудников](/api/users/list) |
| `client.users.getUser()` | [Информация о сотруднике](/api/users/get) |
| `client.users.getUserStatus()` | [Статус сотрудника](/api/users/get-status) |
| `client.users.updateUser()` | [Редактирование сотрудника](/api/users/update) |
+| `client.users.updateUserAvatar()` | [Загрузка аватара сотрудника](/api/users/update-avatar) |
| `client.users.updateUserStatus()` | [Новый статус сотрудника](/api/users/update-status) |
| `client.users.deleteUser()` | [Удаление сотрудника](/api/users/delete) |
+| `client.users.deleteUserAvatar()` | [Удаление аватара сотрудника](/api/users/remove-avatar) |
| `client.users.deleteUserStatus()` | [Удаление статуса сотрудника](/api/users/remove-status) |
| `client.groupTags.createTag()` | [Новый тег](/api/group-tags/create) |
| `client.groupTags.listTags()` | [Список тегов сотрудников](/api/group-tags/list) |
@@ -2898,8 +2942,8 @@ let client = PachcaClient(token: "YOUR_TOKEN", baseURL: "https://custom-api.exam
import PachcaSDK
// Список чатов
-let response = try await client.chats.listChats(sortId: .desc, availability: .isMember, lastMessageAtAfter: "2025-01-01T00:00:00.000Z", lastMessageAtBefore: "2025-02-01T00:00:00.000Z", personal: false, limit: 1, cursor: "eyJpZCI6MTAsImRpciI6ImFzYyJ9")
-// → ListChatsResponse(data: [Chat], meta: PaginationMeta?)
+let response = try await client.chats.listChats(sort: .id, order: .desc, availability: .isMember, lastMessageAtAfter: "2025-01-01T00:00:00.000Z", lastMessageAtBefore: "2025-02-01T00:00:00.000Z", personal: false, limit: 1, cursor: "eyJpZCI6MTAsImRpciI6ImFzYyJ9")
+// → ListChatsResponse(data: [Chat], meta: PaginationMeta)
```
@@ -2936,7 +2980,7 @@ let response = try await client.chats.getChat(id: 334)
## Пагинация
-Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит `meta?.paginate?.nextPage` — курсор для следующей страницы.
+Методы, возвращающие списки, используют cursor-based пагинацию. Ответ всегда содержит `meta.paginate.nextPage` — курсор для следующей страницы. Курсор никогда не бывает `nil` — конец данных определяется по пустому массиву `data`.
### Ручная пагинация
@@ -2944,11 +2988,12 @@ let response = try await client.chats.getChat(id: 334)
var cursor: String? = nil
repeat {
let response = try await client.users.listUsers(limit: 50, cursor: cursor)
+ if response.data.isEmpty { break }
for user in response.data {
print("\(user.firstName) \(user.lastName)")
}
- cursor = response.meta?.paginate?.nextPage
-} while cursor != nil
+ cursor = response.meta.paginate.nextPage
+} while true
```
### Автопагинация
@@ -3023,12 +3068,13 @@ do {
## Повторные запросы
-SDK автоматически повторяет запрос при получении `429 Too Many Requests`:
+SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx` (`500`, `502`, `503`, `504`):
- До **3 повторов** на каждый запрос
-- Если сервер вернул заголовок `Retry-After` — ждёт указанное время
-- Иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **429:** если сервер вернул заголовок `Retry-After` — ждёт указанное время, иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **5xx:** экспоненциальный backoff с jitter: ~10 сек, ~20 сек, ~40 сек
- Ожидание через `Task.sleep(nanoseconds:)` — не блокирует поток
+- Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов
## Типы
@@ -3101,7 +3147,7 @@ let response = try await client.messages.createMessage(body: body)
// Список сотрудников
let response = try await client.users.listUsers(query: "Олег", limit: 1, cursor: "eyJpZCI6MTAsImRpciI6ImFzYyJ9")
-// → ListUsersResponse(data: [User], meta: PaginationMeta?)
+// → ListUsersResponse(data: [User], meta: PaginationMeta)
// Создание задачи
let body = TaskCreateRequest(
@@ -3125,7 +3171,7 @@ let response = try await client.tasks.createTask(body: body)
---
-# CSharp
+# C#
[Pachca.Sdk](https://www.nuget.org/packages/Pachca.Sdk) NuGet
@@ -3195,15 +3241,19 @@ using var client = new PachcaClient("YOUR_TOKEN", "https://custom-api.example.co
| `client.Profile.GetTokenInfoAsync()` | [Информация о токене](/api/profile/get-info) |
| `client.Profile.GetProfileAsync()` | [Информация о профиле](/api/profile/get) |
| `client.Profile.GetStatusAsync()` | [Текущий статус](/api/profile/get-status) |
+| `client.Profile.UpdateProfileAvatarAsync()` | [Загрузка аватара](/api/profile/update-avatar) |
| `client.Profile.UpdateStatusAsync()` | [Новый статус](/api/profile/update-status) |
+| `client.Profile.DeleteProfileAvatarAsync()` | [Удаление аватара](/api/profile/delete-avatar) |
| `client.Profile.DeleteStatusAsync()` | [Удаление статуса](/api/profile/delete-status) |
| `client.Users.CreateUserAsync()` | [Создать сотрудника](/api/users/create) |
| `client.Users.ListUsersAsync()` | [Список сотрудников](/api/users/list) |
| `client.Users.GetUserAsync()` | [Информация о сотруднике](/api/users/get) |
| `client.Users.GetUserStatusAsync()` | [Статус сотрудника](/api/users/get-status) |
| `client.Users.UpdateUserAsync()` | [Редактирование сотрудника](/api/users/update) |
+| `client.Users.UpdateUserAvatarAsync()` | [Загрузка аватара сотрудника](/api/users/update-avatar) |
| `client.Users.UpdateUserStatusAsync()` | [Новый статус сотрудника](/api/users/update-status) |
| `client.Users.DeleteUserAsync()` | [Удаление сотрудника](/api/users/delete) |
+| `client.Users.DeleteUserAvatarAsync()` | [Удаление аватара сотрудника](/api/users/remove-avatar) |
| `client.Users.DeleteUserStatusAsync()` | [Удаление статуса сотрудника](/api/users/remove-status) |
| `client.GroupTags.CreateTagAsync()` | [Новый тег](/api/group-tags/create) |
| `client.GroupTags.ListTagsAsync()` | [Список тегов сотрудников](/api/group-tags/list) |
@@ -3263,8 +3313,8 @@ using var client = new PachcaClient("YOUR_TOKEN", "https://custom-api.example.co
using Pachca.Sdk;
// Список чатов
-var response = await client.Chats.ListChatsAsync(SortOrder.Desc, ChatAvailability.IsMember, "2025-01-01T00:00:00.000Z", "2025-02-01T00:00:00.000Z", false, 1, "eyJpZCI6MTAsImRpciI6ImFzYyJ9");
-// → ListChatsResponse(Data: List, Meta: PaginationMeta?)
+var response = await client.Chats.ListChatsAsync(ChatSortField.Id, SortOrder.Desc, ChatAvailability.IsMember, DateTimeOffset.Parse("2025-01-01T00:00:00.000Z"), DateTimeOffset.Parse("2025-02-01T00:00:00.000Z"), false, 1, "eyJpZCI6MTAsImRpciI6ImFzYyJ9");
+// → ListChatsResponse(Data: List, Meta: PaginationMeta)
```
@@ -3303,7 +3353,7 @@ var response = await client.Chats.GetChatAsync(334);
## Пагинация
-Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит поле `Meta.Paginate.NextPage` — курсор для следующей страницы.
+Методы, возвращающие списки, используют cursor-based пагинацию. Ответ всегда содержит поле `Meta.Paginate.NextPage` — курсор для следующей страницы. Курсор никогда не бывает `null` — конец данных определяется по пустому массиву `Data`.
### Ручная пагинация
@@ -3311,12 +3361,13 @@ var response = await client.Chats.GetChatAsync(334);
var chats = new List();
string? cursor = null;
-do
+while (true)
{
var response = await client.Chats.ListChatsAsync(cursor: cursor);
+ if (response.Data.Count == 0) break;
chats.AddRange(response.Data);
- cursor = response.Meta?.Paginate?.NextPage;
-} while (cursor != null);
+ cursor = response.Meta.Paginate.NextPage;
+}
```
### Автопагинация
@@ -3401,11 +3452,11 @@ catch (OAuthError e)
## Повторные запросы
-SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx`:
+SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx` (`500`, `502`, `503`, `504`):
- До **3 повторов** на каждый запрос
-- Если сервер вернул заголовок `Retry-After` — ждёт указанное время
-- Иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **429:** если сервер вернул заголовок `Retry-After` — ждёт указанное время, иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек
+- **5xx:** экспоненциальный backoff с jitter: ~10 сек, ~20 сек, ~40 сек
- Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов
## Отмена запросов
@@ -3532,7 +3583,7 @@ var response = await client.Messages.CreateMessageAsync(request);
// Список сотрудников
var response = await client.Users.ListUsersAsync("Олег", 1, "eyJpZCI6MTAsImRpciI6ImFzYyJ9");
-// → ListUsersResponse(Data: List, Meta: PaginationMeta?)
+// → ListUsersResponse(Data: List, Meta: PaginationMeta)
// Создание задачи
var request = new TaskCreateRequest
@@ -3541,7 +3592,7 @@ var request = new TaskCreateRequest
{
Kind = TaskKind.Reminder,
Content = "Забрать со склада 21 заказ",
- DueAt = "2020-06-05T12:00:00.000+03:00",
+ DueAt = DateTimeOffset.Parse("2020-06-05T12:00:00.000+03:00"),
Priority = 2,
PerformerIds = new List { 123 },
ChatId = 456,
@@ -3596,6 +3647,14 @@ var response = await client.Tasks.CreateTaskAsync(request);
> Кастомные поля настраиваются администратором пространства.
+**Загрузить аватар профиля**
+
+1. Загрузи аватар из файла
+
+**Удалить аватар профиля**
+
+1. Удали аватар
+
### pachca-users
**Получить сотрудника по ID**
@@ -3644,6 +3703,14 @@ var response = await client.Tasks.CreateTaskAsync(request);
2. Установить статус
3. Удалить статус
+**Загрузить аватар сотрудника**
+
+1. Загрузи аватар сотруднику
+
+**Удалить аватар сотрудника**
+
+1. Удали аватар сотрудника
+
### pachca-chats
**Создать канал и пригласить участников**
@@ -4430,6 +4497,25 @@ Liquid также поддерживает:
- `webhook_timestamp: integer, int32` (required) — Дата и время отправки вебхука (UTC+0) в формате UNIX
+### Заполнение формы
+
+Вебхук отправляется при отправке пользователем заполненной формы ([представления](/guides/forms/overview)). Подробнее об обработке результатов — в разделе [Обработка форм](/guides/forms/handling).
+
+#### ViewSubmitWebhookPayload
+
+- `type: string` (required) — Тип объекта
+ Значения: `view` — Для формы всегда view
+- `event: string` (required) — Тип события
+ Значения: `submit` — Отправка формы
+- `callback_id: string` (required) — Идентификатор обратного вызова, указанный при открытии представления
+- `private_metadata: string` (required) — Приватные метаданные, указанные при открытии представления
+- `user_id: integer, int32` (required) — Идентификатор пользователя, который отправил форму
+- `data: Record` (required) — Данные заполненных полей представления. Ключ — `action_id` поля, значение — введённые данные
+ **Структура значений Record:**
+ - Тип значения: `any`
+- `webhook_timestamp: integer, int32` (required) — Дата и время отправки вебхука (UTC+0) в формате UNIX
+
+
### Изменение участников чатов
Вебхук отправляется при изменении состава участников чатов, где состоит бот, и в тредах этих чатов.
@@ -4999,18 +5085,19 @@ sequenceDiagram
Каждый исходящий вебхук защищён с помощью подписи, основанной на хешировании содержимого. Подробнее об этом — в разделе [Безопасность](/guides/webhook#bezopasnost).
-#### Структура исходящего вебхука о заполнении формы
+#### ViewSubmitWebhookPayload
-- `type: string` — Тип объекта (для представлений всегда view)
- Значения: `view` — представление
-- `event: string` — Тип события (для отправки пользователем формы всегда submit)
- Значения: `submit` — отправка формы
-- `private_metadata: string` — Строка, заданная при отправке представления
-- `callback_id: string` — Идентификатор для распознавания этого представления, заданный при отправке представления
-- `user_id: integer` — Идентификатор пользователя, который заполнил форму
-- `data: object` — JSON карта заполненных полей представления, где каждый ключ - значение поля name.
- - `name: string | array of strings | array of objects | null | []` — Значение, которое указал пользователь в поле (или массив значений, если это был множественный выбор или загруженные пользователем файлы). Если пользователь не указал значение, тогда null (или пустой массив, если это поле файлов или чекбоксов)
-- `webhook_timestamp: integer` — Дата и время отправки вебхука (UTC+0) в формате UNIX
+- `type: string` (required) — Тип объекта
+ Значения: `view` — Для формы всегда view
+- `event: string` (required) — Тип события
+ Значения: `submit` — Отправка формы
+- `callback_id: string` (required) — Идентификатор обратного вызова, указанный при открытии представления
+- `private_metadata: string` (required) — Приватные метаданные, указанные при открытии представления
+- `user_id: integer, int32` (required) — Идентификатор пользователя, который отправил форму
+- `data: Record` (required) — Данные заполненных полей представления. Ключ — `action_id` поля, значение — введённые данные
+ **Структура значений Record:**
+ - Тип значения: `any`
+- `webhook_timestamp: integer, int32` (required) — Дата и время отправки вебхука (UTC+0) в формате UNIX
```json title="Пример вебхука о заполнении формы"
@@ -5507,31 +5594,72 @@ curl "https://api.pachca.com/api/shared/v1/audit_events?start_time=2025-05-01T00
## Что такое n8n
-n8n — платформа для автоматизации рабочих процессов с открытым исходным кодом. Можно развернуть на собственном сервере или использовать веб-версию. Платформа позволяет создавать интеграции с сервисами без написания кода, используя визуальный редактор с узлами (nodes).
+[n8n](https://n8n.io) — платформа для автоматизации рабочих процессов с открытым исходным кодом. Можно развернуть на собственном сервере или использовать веб-версию. Платформа позволяет создавать интеграции с сервисами без написания кода, используя визуальный редактор с узлами (nodes).
В n8n встроено более 400 готовых узлов для популярных сервисов. Узлы бывают двух типов:
- **Узел триггера** — событие, запускающее рабочий процесс: новое сообщение, нажатие кнопки, обновление статуса и др.
- **Узел действия** — логика после триггера: отправка сообщения в чат, создание задачи, добавление записи в БД и т.д.
-n8n автоматически выполняет каждое действие по триггеру в указанном порядке.
-

*Визуальный редактор n8n*
-## Настройка
+## Что можно автоматизировать
+- **Уведомления из внешних систем** — пересылайте алерты из мониторинга, CI/CD, CRM и других сервисов в чаты Пачки
+- **Бот-ассистент** — подключите AI-агент, который ищет по сообщениям и отвечает на вопросы команды
+- **Задачи по сообщениям** — автоматически создавайте задачи из сообщений с определённым тегом или реакцией
+- **Согласование** — отправляйте кнопки «Одобрить / Отклонить» и обрабатывайте ответ в workflow
+- **Онбординг** — приветствуйте новых сотрудников личным сообщением от бота
+- **Синхронизация данных** — экспортируйте сообщения, задачи и события в Google Sheets, Notion или другие сервисы
+- **Мониторинг и алерты** — проверяйте состояние сервисов по расписанию и отправляйте алерт в чат при сбое
- ### Шаг 1. Установка n8n
+## Расширение Пачки
+
+Расширение Пачки для n8n предоставляет два узла:
+
+| Узел | Тип | Описание |
+|------|-----|----------|
+| **Pachca** | Действие | 18 ресурсов и более 60 операций для работы с API |
+| **Pachca Trigger** | Триггер | 16 типов событий через вебхуки |
+
+- [18 ресурсов](/guides/n8n/resources) — Сообщения, чаты, пользователи, задачи, формы, поиск и другие
+- [Триггер с вебхуком](/guides/n8n/trigger) — 16 типов событий с авторегистрацией вебхука
+- [AI-агент](/guides/n8n/advanced) — Используйте Pachca как инструмент AI Agent
+- [Автопагинация](/guides/n8n/resources) — Cursor-based пагинация с Return All / Limit
+
+
+## Что нового в v2.0
-> **Внимание:** Расширение Пачки доступно пока только в Beta. Его нет в веб-версии n8n — для использования нужно развернуть коробочную версию на собственном сервере.
+Версия 2.0 — полностью автогенерируемая из OpenAPI-спецификации с сохранением 100% обратной совместимости с v1.
+- **7 новых ресурсов:** Chat Member, Custom Property, Read Member, Link Preview, Search, Chat Export, Security
+- **Курсорная пагинация** с Return All / Limit вместо ручного `per`/`page`
+- **AI Tool Use** — `usableAsTool: true` для использования с AI Agent
+- **Trigger-нода** с авторегистрацией вебхука через Bot ID и 16 типами событий
+- **Поисковые выпадающие списки** для Chat ID и User ID
+- **Загрузка файлов** через S3 с поддержкой URL и Binary Data
+- **Полнотекстовый поиск** по сообщениям, чатам и пользователям
+- **Журнал безопасности** — аудит действий пользователей
- Два способа установки:
+> Все существующие workflow на v1 продолжают работать без изменений. Подробнее — в разделе [Миграция с v1](/guides/n8n/migration).
- **С помощью команды** (требуется Node.js):
+
+---
+
+
+# Начало работы
+
+## Установка
+
+
+ ### Шаг 1. Установка n8n
+
+Два способа установки:
+
+ **С помощью команды** (требуется Node.js 22+):
```bash
npx n8n
@@ -5545,8 +5673,8 @@ n8n автоматически выполняет каждое действие
docker run -it --rm \
--name n8n \
-p 5678:5678 \
- -e GENERIC_TIMEZONE="" \
- -e TZ="" \
+ -e GENERIC_TIMEZONE="Europe/Moscow" \
+ -e TZ="Europe/Moscow" \
-e N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true \
-e N8N_RUNNERS_ENABLED=true \
-v n8n_data:/home/node/.n8n \
@@ -5555,468 +5683,1712 @@ n8n автоматически выполняет каждое действие
Подробные инструкции — в [официальной документации n8n](https://docs.n8n.io/hosting/) и на [GitHub](https://github.com/n8n-io/n8n).
- После запуска настройте аккаунт владельца (Owner Account), указав почту, имя и пароль.
+ После запуска откройте `http://localhost:5678` и настройте аккаунт владельца (Owner Account), указав почту, имя и пароль.

*Настройка Owner Account*
+ После входа вы увидите главный экран n8n с пустым списком workflow.
+
+ 
+
+*Главная страница n8n*
+
+
### Шаг 2. Установка расширения Пачки
-Расширение доступно на [GitHub](https://github.com/pachca/n8n-nodes-pachca) и [npmjs](https://www.npmjs.com/package/n8n-nodes-pachca).
+Расширение доступно на [npm](https://www.npmjs.com/package/n8n-nodes-pachca) и [GitHub](https://github.com/pachca/openapi/tree/main/integrations/n8n).
Три способа установки:
1. Зайти в **Settings** > **Community nodes** и добавить `n8n-nodes-pachca` (рекомендуется)
2. Выполнить команду `npm i n8n-nodes-pachca` в директории n8n
- 3. Следовать README-инструкции на [GitHub](https://github.com/pachca/n8n-nodes-pachca)
+ 3. Следовать README-инструкции на [GitHub](https://github.com/pachca/openapi/tree/main/integrations/n8n)
- 
-*Установка через Community nodes*
+## Настройка Credentials
- ### Шаг 3. Создание Credentials
+ ### Шаг 1. Создание Credentials
-Credentials — данные для авторизации.
+Credentials — данные для авторизации. Перейдите в **Credentials** и нажмите **Add Credential**.
- Нажмите **«Add Credential»**, найдите **Pachca API** в списке и заполните поля:
+ 
- - **Base URL:** `https://api.pachca.com/api/shared/v1`
- - **Access Token:** Токен доступа к API. В Пачке доступны два типа токенов:
- - Персональный токен — доступен в разделе **Автоматизации** > **Интеграции** > **API**
- - Токен бота — доступен в настройках бота на вкладке **API**
+*Список Credentials*
- Подробнее о токенах — в разделе [Авторизация](/api/authorization). Credentials можно создать несколько — для разных операций и токенов.
- 
+ Найдите **Pachca API** в списке и заполните поля:
-*Настройка Pachca API Credentials*
+ 
+*Поиск Pachca API в списке Credentials*
- ### Шаг 4. Создание Workflow
-Workflow — визуальный редактор, в котором выстраиваются цепочки триггеров и действий.
+ | Поле | Обязательное | Описание |
+ |------|:---:|----------|
+ | **Base URL** | нет | Базовый URL API. По умолчанию `https://api.pachca.com/api/shared/v1`. Менять только для on-premise |
+ | **Access Token** | да | Токен доступа к API |
+ | **Bot ID** | нет | ID бота — нужен для авторегистрации вебхука в [Pachca Trigger](/guides/n8n/trigger). Автоопределяется из токена бота |
+ | **Signing Secret** | нет | Секрет для верификации входящих webhook-запросов (HMAC-SHA256) |
+ | **Webhook Allowed IPs** | нет | Список IP-адресов через запятую, с которых разрешены входящие вебхуки. Пачка отправляет с `37.200.70.177`. Пустое поле — разрешить все |
- 
+ 
-*Редактор workflow*
+*Форма Pachca API Credentials*
- Пример: отправка сообщения от лица бота. Триггер — нажатие кнопки **«Execute Workflow»**, действие — **Send a message** в Пачке:
+ ### Шаг 2. Где получить токен
- - **Credential** — от чьего лица будет отправлено сообщение
- - **Entity ID** — ID чата
- - **Content** — содержание сообщения
+В Пачке доступны два типа токенов:
- Не забудьте добавить бота в чат.
+ - **Персональный токен** — доступен в разделе **Автоматизации** > **Интеграции** > **API**
+ - **Токен бота** — доступен в настройках бота на вкладке **API**
- 
+ Доступные операции зависят от [скоупов](/api/authorization#skoupy) токена, а не от его типа. Подробнее — в разделе [Авторизация](/api/authorization).
-*Пример отправки сообщения*
+ После заполнения полей нажмите **Test** — n8n проверит подключение вызовом [Информация о токене](GET /oauth/token/info). При успехе вы увидите подтверждение.
+ 
- В платформу встроено более 400 узлов для популярных сервисов, а дополнительные Community nodes можно установить из интерфейса. При необходимости доступны HTTP-запросы к любому API, условия, кастомный JavaScript- или Python-код.
+*Connection tested successfully*
-## Методы API в nodes Pachca (Beta)
+ > Если тест не проходит — проверьте правильность токена и доступность API. Подробнее — в разделе [Устранение ошибок](/guides/n8n/troubleshooting).
-Nodes (узлы) в расширении Пачки для n8n совпадают с методами API. Вот список доступных:
-### Действия с сообщениями
+## Первый workflow
-- **Send a message** — [Новое сообщение](POST /messages)
-- **Get a message** — [Информация о сообщении](GET /messages/{id})
-- **Get messages from the chat** — [Список сообщений чата](GET /messages)
-- **Update a message** — [Редактирование сообщения](PUT /messages/{id})
-- **Delete a message** — [Удаление сообщения](DELETE /messages/{id})
-- **Pin a message** — [Закрепление сообщения](POST /messages/{id}/pin)
-- **Unpin a message** — [Открепление сообщения](DELETE /messages/{id}/pin)
-- **Get message readers** — [Список прочитавших сообщение](GET /messages/{id}/read_member_ids)
-- **Unfurl message links** — [Unfurl (разворачивание ссылок)](POST /messages/{id}/link_previews)
-### Действия с тредами
+ ### Шаг 1. Создание workflow
-- **Create a thread** — [Новый тред](POST /messages/{id}/thread)
-- **Get a thread** — [Информация о треде](GET /threads/{id})
+Workflow — визуальный редактор, в котором выстраиваются цепочки триггеров и действий. Создайте новый workflow и добавьте триггер **Manual Trigger** для ручного запуска.
-### Действия с реакциями
+ Нажмите **+** на выходе триггера и найдите **Pachca** в списке узлов.
-- **Add a reaction** — [Новая реакция](POST /messages/{id}/reactions)
-- **Remove a reaction** — [Удаление реакции](DELETE /messages/{id}/reactions)
-- **Get message reactions** — [Список реакций на сообщение](GET /messages/{id}/reactions)
+ 
-### Действия с чатом
+*Поиск узла Pachca*
-- **Get all chats** — [Список чатов](GET /chats)
-- **Get a chat** — [Информация о чате](GET /chats/{id})
-- **Create a chat** — [Новый чат](POST /chats)
-- **Update a chat** — [Обновление чата](PUT /chats/{id})
-- **Archive a chat** — [Архивация чата](PUT /chats/{id}/archive)
-- **Unarchive a chat** — [Разархивация чата](PUT /chats/{id}/unarchive)
-- **Get chat members** — [Список участников чата](GET /chats/{id}/members)
-- **Add users to chat** — [Добавление пользователей](POST /chats/{id}/members)
-- **Remove user from chat** — [Исключение пользователя](DELETE /chats/{id}/members/{user_id})
-- **Update user role in chat** — [Редактирование роли](PUT /chats/{id}/members/{user_id})
-- **Leave a chat** — [Выход из чата](DELETE /chats/{id}/leave)
-### Действия с пользователями
+ После добавления узла на канвасе появится цепочка: Manual Trigger → Pachca.
-- **Get all users** — [Список сотрудников](GET /users)
-- **Get a user** — [Информация о сотруднике](GET /users/{id})
-- **Create a user** — [Новый сотрудник](POST /users)
-- **Update a user** — [Редактирование сотрудника](PUT /users/{id})
-- **Delete a user** — [Удаление сотрудника](DELETE /users/{id})
+ 
-### Действия с тегами пользователей
+*Pachca на канвасе*
-- **Get all group tags** — [Список тегов сотрудников](GET /group_tags)
-- **Get a group tag** — [Информация о теге](GET /group_tags/{id})
-- **Create a group tag** — [Новый тег](POST /group_tags)
-- **Update a group tag** — [Редактирование тега](PUT /group_tags/{id})
-- **Delete a group tag** — [Удаление тега](DELETE /group_tags/{id})
-- **Get users in group tag** — [Список сотрудников тега](GET /group_tags/{id}/users)
-- **Add tags to chat** — [Добавление тегов](POST /chats/{id}/group_tags)
-- **Remove tag from chat** — [Исключение тега](DELETE /chats/{id}/group_tags/{tag_id})
-### Действия со статусом и профилем
+ ### Шаг 2. Настройка и запуск
-- **Get my profile** — [Информация о профиле](GET /profile)
-- **Get my status** — [Текущий статус](GET /profile/status)
-- **Set my status** — [Новый статус](PUT /profile/status)
-- **Clear my status** — [Удаление статуса](DELETE /profile/status)
+Дважды кликните на узел **Pachca** и настройте:
-### Действия с формами
+ - **Credential:** выберите созданный Pachca API
+ - **Resource:** Message
+ - **Operation:** Create
+ - **Entity ID:** ID чата (число)
+ - **Content:** текст сообщения
-- **Create a form** — [Открытие представления](POST /views/open)
-- **Process form submission** — закрытие и отображение ошибок
-- **Get form templates**
+ 
-### Другие действия
+*Настройка Message → Create*
-- **Get custom properties** — [Список дополнительных полей](GET /custom_properties)
-- **Create a task** — [Новое напоминание](POST /tasks)
-- **Update a bot** — [Редактирование бота](PUT /bots/{id})
-- **Upload a file** — [Загрузка файла](POST /direct_url)
-> **Внимание:** Некоторые действия доступны только с персональным токеном (владельца или участника с ролью «Администратор»).
+ > **Внимание:** Перед отправкой сообщения [добавьте бота в чат](/guides/bots#dostupy-bota-k-chatam-i-soobscheniyam).
----
-
+ Закройте панель настроек и нажмите **Execute Workflow**. При успехе узлы подсветятся зелёным и покажут количество обработанных элементов.
-# Интеграция с Albato
+ 
-Albato — платформа для интеграции различных сервисов (CRM, системы аналитики, мессенджеры и т.д.). Через Albato к Пачке можно подключить более 250 сервисов и получать уведомления прямо в чаты и каналы Пачки.
+*Workflow выполнен*
-Уведомления приходят в виде сообщения от того, кто подключил интеграцию. Количество интеграций в одном чате не ограничено.
-> Пачка пока способна обрабатывать только входящие события. Отправить информацию из Пачки другим сервисам через Albato нельзя.
+ Откройте узел Pachca, чтобы увидеть ответ API с данными созданного сообщения.
+ 
-> **Внимание:** Albato работает на коммерческой основе. [Тарифы](https://albato.ru/pricing) можно найти на сайте сервиса, первые 14 дней — бесплатно.
+*Ответ API в панели Output*
-## Какие сервисы можно подключить
+ Больше примеров — в разделе [Примеры workflow](/guides/n8n/workflows).
-Через Albato к Пачке можно подключить более 250 сервисов: CRM (Битрикс24, AmoCRM, СБИС CRM), системы управления проектами (Jira, Trello), а также МойСклад, CloudPayments, YClients и другие. Полный список доступен при создании связки в личном кабинете Albato.
-## Какие уведомления будут приходить
-Через Albato можно настроить текстовые уведомления о различных событиях. Набор событий зависит от конкретного сервиса. Например, при интеграции с Битрикс24 — уведомления об изменении в сделке, создании счёта, новом лиде. При интеграции с Telegram — о входящем сообщении или новом участнике группы.
+---
-Текст сообщения можно настроить: отправлять только содержимое события или добавлять пояснения и дополнительные поля.
-## Кто может создавать интеграции
+# Ресурсы и операции
-Интеграцию через Albato может создавать только участник с ролью «Администратор».
+В расширении Пачки каждый узел **Pachca** работает по модели **Resource → Operation**: вы выбираете ресурс (например, Message) и операцию над ним (например, Create).
-## Как настроить интеграцию
+
+*Выбор ресурса в узле Pachca*
- ### Шаг 1. Зарегистрируйтесь в Albato
-
+Для каждого ресурса доступен свой набор операций.
-*Форма регистрации на сайте Albato*
+
+*Операции для ресурса Message*
- 1. Откройте [сайт Albato](https://albato.ru/) и нажмите **«Регистрация»** в верхнем правом углу
- 2. Введите свои данные и нажмите **«Зарегистрироваться»**
- 3. Проверьте почту — вам должен прийти код подтверждения
- 4. Введите код
+## Список ресурсов
- ### Шаг 2. Подключите Пачку к Albato
+| # | Ресурс | Операций | Описание | Только v2 |
+|---|--------|:---:|----------|:---:|
+| 1 | [Message](#message) | 7 | Сообщения: создание, редактирование, удаление, закрепление | |
+| 2 | [Chat](#chat) | 6 | Чаты: создание, обновление, архивация | |
+| 3 | [Chat Member](#chat-member) | 7 | Участники чата: добавление, удаление, роли, теги | да |
+| 4 | [User](#user) | 10 | Сотрудники: CRUD, аватар, статус | |
+| 5 | [Group Tag](#group-tag) | 6 | Теги сотрудников: CRUD, список пользователей | |
+| 6 | [Thread](#thread) | 2 | Треды: создание, получение | |
+| 7 | [Reaction](#reaction) | 3 | Реакции: создание, удаление, список | |
+| 8 | [Profile](#profile) | 7 | Мой профиль: информация, аватар, статус | |
+| 9 | [Task](#task) | 5 | Задачи: полный CRUD | |
+| 10 | [Bot](#bot) | 3 | Боты: обновление, события, удаление событий | |
+| 11 | [File](#file) | 1 | Загрузка файлов через S3 | |
+| 12 | [Form](#form) | 1 | Модальные формы | |
+| 13 | [Custom Property](#custom-property) | 1 | Дополнительные поля | да |
+| 14 | [Read Member](#read-member) | 1 | Список прочитавших сообщение | да |
+| 15 | [Link Preview](#link-preview) | 1 | Разворачивание ссылок | да |
+| 16 | [Search](#search) | 3 | Полнотекстовый поиск | да |
+| 17 | [Chat Export](#chat-export) | 2 | Экспорт сообщений из чатов | да |
+| 18 | [Security](#security) | 1 | Журнал безопасности | да |
-
+> **Внимание:** Для некоторых операций требуются скоупы, которые доступны только определённым ролям (администратор, владелец). При создании персонального токена отображаются только скоупы, доступные вашей роли. Подробнее — в разделе [Авторизация](/api/authorization).
-*Раздел подключений в личном кабинете Albato*
+---
- 
+## Message
-*Выбор Пачки в списке сервисов Albato*
+Сообщения: создание, получение, редактирование, удаление, закрепление и открепление.
+| Операция | API |
+|----------|-----|
+| Create | [Создание сообщения](POST /messages) |
+| Get Many | [Список сообщений чата](GET /messages) |
+| Get | [Информация о сообщении](GET /messages/{id}) |
+| Update | [Редактирование сообщения](PUT /messages/{id}) |
+| Delete | [Удаление сообщения](DELETE /messages/{id}) |
+| Pin | [Закрепление сообщения](POST /messages/{id}/pin) |
+| Unpin | [Открепление сообщения](DELETE /messages/{id}/pin) |
- 1. Зайдите в свой аккаунт в Пачке в том браузере, в котором вы настраиваете интеграцию
- 2. Вернитесь на сайт Albato и в личном кабинете откройте раздел **Подключения**
- 3. Нажмите **«Добавить подключение»** в верхнем левом углу
- 4. Выберите в списке сервисов Пачку
- 5. Добавьте название подключения или оставьте базовое и нажмите **«Далее»**
- 6. Нажмите **«Предоставить доступ Albato»**
- 7. В открывшемся окне нажмите **«Продолжить»**
+**Ключевые параметры Create:** `entityId` (ID чата или пользователя), `content` (текст, Markdown), `entityType` (discussion, user, thread), `files`, `buttons`, `parentMessageId`.
+**Сортировка в Get Many:** параметры `sort` (по умолчанию `id`) и `order` (`asc` / `desc`) определяют порядок выдачи сообщений.
- ### Шаг 3. Подключите внешний сервис
+
-
+*Настройка Message → Get Many*
-*Добавление нового подключения к сервису*
+---
- 
+## Chat
-*Настройка подключения внешнего сервиса*
+Чаты: создание, получение, обновление, архивация и разархивация.
+| Операция | API |
+|----------|-----|
+| Create | [Создание чата](POST /chats) |
+| Get Many | [Список чатов](GET /chats) |
+| Get | [Информация о чате](GET /chats/{id}) |
+| Update | [Обновление чата](PUT /chats/{id}) |
+| Archive | [Архивация чата](PUT /chats/{id}/archive) |
+| Unarchive | [Разархивация чата](PUT /chats/{id}/unarchive) |
- 1. В личном кабинете откройте раздел **Подключения** и нажмите **«Добавить подключение»**
- 2. Выберите в списке сервис, который хотите интегрировать с Пачкой, и нажмите **«Предоставить доступ»**
- 3. Добавьте название подключения или оставьте базовое, введите необходимые данные и нажмите **«Далее»**
+**Сортировка в Get Many:** параметры `sort` (`id` или `last_message_at`) и `order` (`asc` / `desc`). Также доступны фильтры `availability`, `lastMessageAtAfter`, `lastMessageAtBefore`.
- Для подключения разных сервисов нужны разные данные. В базе знаний Albato есть [инструкции](https://blog.albato.ru/category/instrukczii/) для основных сервисов.
+---
+## Chat Member
- ### Шаг 4. Добавьте триггер
+Управление участниками чата: добавление, удаление, изменение ролей, управление тегами.
-
+> В v1 эти операции были частью ресурса Chat. В v2 они выделены в отдельный ресурс Chat Member.
-*Создание триггера для связки в Albato*
+| Операция | API |
+|----------|-----|
+| Get Many | [Список участников чата](GET /chats/{id}/members) |
+| Create | [Добавление пользователей в чат](POST /chats/{id}/members) |
+| Delete | [Удаление пользователя из чата](DELETE /chats/{id}/members/{user_id}) |
+| Update | [Изменение роли участника](PUT /chats/{id}/members/{user_id}) |
+| Leave | [Выход из чата](DELETE /chats/{id}/leave) |
+| Add Group Tags | [Добавление тегов к чату](POST /chats/{id}/group_tags) |
+| Remove Group Tags | [Удаление тегов из чата](DELETE /chats/{id}/group_tags/{tag_id}) |
- 1. В личном кабинете перейдите в раздел **Мои связки** и нажмите **«Создать новую связку»**
- 2. Нажмите **«Добавить триггер, который будет запускать связку»**
- 3. Выберите сервис, из которого будут передаваться данные в Пачку
- 4. Выберите событие, которое будет запускать передачу данных
- 5. Выберите подключённый аккаунт
- 6. Нажмите **«Добавить триггер»**
- 7. При необходимости укажите дополнительные настройки (для каждого сервиса они свои)
+---
- > В Albato можно добавлять условия для прерывания или продолжения работы связки через фильтр входящих данных внутри триггера. Подробнее — в [документации Albato](https://blog.albato.ru/instrument-filtr-vhodyashhih-dannyh/).
+## User
+Сотрудники: полный CRUD, получение и управление статусом.
- ### Шаг 5. Добавьте действие
+| Операция | API |
+|----------|-----|
+| Create | [Создание сотрудника](POST /users) |
+| Get Many | [Список сотрудников](GET /users) |
+| Get | [Информация о сотруднике](GET /users/{id}) |
+| Update | [Обновление сотрудника](PUT /users/{id}) |
+| Delete | [Удаление сотрудника](DELETE /users/{id}) |
+| Update Avatar | [Обновление аватара](PUT /users/{user_id}/avatar) |
+| Delete Avatar | [Удаление аватара](DELETE /users/{user_id}/avatar) |
+| Get Status | [Получение статуса](GET /users/{user_id}/status) |
+| Update Status | [Обновление статуса](PUT /users/{user_id}/status) |
+| Delete Status | [Удаление статуса](DELETE /users/{user_id}/status) |
-
+---
-*Добавление действия после триггера*
+## Group Tag
+Теги (группы) сотрудников: создание, обновление, удаление, список пользователей.
- 
+| Операция | API |
+|----------|-----|
+| Create | [Создание тега](POST /group_tags) |
+| Get Many | [Список тегов](GET /group_tags) |
+| Get | [Информация о теге](GET /group_tags/{id}) |
+| Update | [Обновление тега](PUT /group_tags/{id}) |
+| Delete | [Удаление тега](DELETE /group_tags/{id}) |
+| Get Many Users | [Список пользователей тега](GET /group_tags/{id}/users) |
-*Настройка отправки сообщения в Пачку*
+---
+## Thread
- 1. Нажмите **«Добавьте действие, которое будет происходить после старта связки»**
- 2. Выберите Пачку в разделе **Сервис**
- 3. Выберите **Отправить сообщение** в разделе **Действие**
- 4. Выберите подключённый аккаунт
- 5. Введите ID чата, в который хотите отправлять уведомления. ID чата можно найти в адресной строке браузерной версии Пачки — он состоит из семи цифр и располагается после `chats/`
- 6. В разделе **Тип чата** выберите **Беседа**
- 7. Создайте текст сообщения из данных внешнего сервиса
- 8. Нажмите **«Сохранить»**
+Треды (комментарии к сообщениям): создание и получение.
+| Операция | API |
+|----------|-----|
+| Create | [Создание треда](POST /messages/{id}/thread) |
+| Get | [Информация о треде](GET /threads/{id}) |
- ### Шаг 6. Запустите связку
+---
-Нажмите кнопку запуска в нижнем правом углу. Статус работы связки можно отслеживать в журнале связок в личном кабинете.
+## Reaction
+Реакции на сообщения: создание, удаление, список.
+| Операция | API |
+|----------|-----|
+| Create | [Добавление реакции](POST /messages/{id}/reactions) |
+| Delete | [Удаление реакции](DELETE /messages/{id}/reactions) |
+| Get Many | [Список реакций](GET /messages/{id}/reactions) |
---
+## Profile
-# Последние обновления
+Профиль текущего пользователя: информация, статус, информация о токене.
-> Автоматически отслеживайте обновления: подпишитесь на [RSS-ленту](/feed.xml) или используйте [markdown-версию этой страницы](/updates.md) для интеграции с инструментами и AI-агентами.
+| Операция | API |
+|----------|-----|
+| Get | [Информация о профиле](GET /profile) |
+| Get Info | [Информация о токене](GET /oauth/token/info) |
+| Update Avatar | [Обновление аватара](PUT /profile/avatar) |
+| Delete Avatar | [Удаление аватара](DELETE /profile/avatar) |
+| Get Status | [Получение статуса](GET /profile/status) |
+| Update Status | [Обновление статуса](PUT /profile/status) |
+| Delete Status | [Удаление статуса](DELETE /profile/status) |
+**Загрузка аватара:** операция Update Avatar принимает бинарные данные из предыдущего узла (например, HTTP Request или Read Binary File). В поле **Input Binary Field** укажите имя бинарного свойства (по умолчанию `data`).
-
+---
-## C# SDK
+## Task
-Добавлен официальный [C# SDK](/guides/sdk/csharp) для .NET 8+. Полная поддержка async/await, CancellationToken, автопагинация через `*AllAsync()` методы и автоматические повторы при `429`. Установка: `dotnet add package Pachca.Sdk`.
+Задачи (напоминания): полный CRUD.
-
+| Операция | API |
+|----------|-----|
+| Create | [Создание задачи](POST /tasks) |
+| Get Many | [Список задач](GET /tasks) |
+| Get | [Информация о задаче](GET /tasks/{id}) |
+| Update | [Обновление задачи](PUT /tasks/{id}) |
+| Delete | [Удаление задачи](DELETE /tasks/{id}) |
-## Конструктор форм
+**Типы задач:** `call`, `email`, `event`, `meeting`, `reminder`.
-На странице [Формы](/guides/forms/overview) появился интерактивный конструктор представлений. Добавляйте блоки из палитры, настраивайте все параметры через визуальный редактор и сразу видите результат — включая календарь, время, выпадающие списки, радиокнопки и чекбоксы.
+---
-
+## Bot
-## Интерактивный playground сообщений и вебхуков
+Управление ботами: обновление настроек, получение и удаление событий.
-На страницах [Кнопки в сообщениях](/guides/buttons) и [Входящие вебхуки](/guides/incoming-webhooks) появились интерактивные playground — редактируйте JSON слева и сразу видите результат справа. Превью сообщения отображает все поля: текст с markdown-разметкой, кнопки, файлы, превью ссылок, кастомные аватар и имя бота.
+| Операция | API |
+|----------|-----|
+| Update | [Обновление бота](PUT /bots/{id}) |
+| Get Many Events | [Список событий бота](GET /webhooks/events) |
+| Remove Events | [Удаление событий](DELETE /webhooks/events/{id}) |
-
+---
-## Ошибки оплаты, роли сотрудников, закрепление сообщений
+## File
-Во все методы, требующие оплаченного тарифа, добавлен ответ `402 Payment Required`.
+Загрузка файлов через двухшаговый S3 upload.
-В методах [Новый сотрудник](POST /users) и [Редактирование сотрудника](PUT /users/{id}) уточнены допустимые значения поля `role`: `admin`, `user` и `multi_guest`. Значение `guest` недоступно для установки через API — оно присутствует только в ответах.
+| Операция | API |
+|----------|-----|
+| Create | [Загрузка файла](POST /uploads) |
-Поле `link_preview` в запросе [Отправить сообщение](POST /messages) перенесено на верхний уровень тела запроса — отдельно от объекта `message`.
+Подробнее — в разделе [Продвинутые функции](/guides/n8n/advanced#zagruzka-fajlov).
-Метод [Закрепление сообщения](POST /messages/{id}/pin) при попытке закрепить уже закреплённое сообщение возвращает `422 Unprocessable Entity`.
+---
-Параметры `start_time` и `end_time` в методе [Журнал аудита событий](GET /audit_events) стали необязательными — без них возвращаются все события.
+## Form
-Уточнён тип поля `name` в модели [реакции](/api/models#reaktsiya-na-soobschenie): `string | null` вместо опционального.
+Модальные формы (представления).
-В [CLI](/guides/cli) параметры сортировки в командах `chats list` и `messages list` заменены на `--sort` и `--order` — вместо прежних `--sort-id` и `--sort-last-message-at`.
+| Операция | API |
+|----------|-----|
+| Create | [Открытие представления](POST /views/open) |
-Были обновлены следующие методы:
+Подробнее — в разделе [Продвинутые функции](/guides/n8n/advanced#formy) и в [документации форм](/guides/forms/overview).
-- [Отправить сообщение](POST /messages)
-- [Новый сотрудник](POST /users)
-- [Редактирование сотрудника](PUT /users/{id})
-- [Закрепление сообщения](POST /messages/{id}/pin)
-- [Журнал аудита событий](GET /audit_events)
+---
-
+## Custom Property
-## Редактирование скоупов, исправления в моделях
+Дополнительные поля пространства.
-Скоупы персональных токенов теперь можно изменять после создания — в настройках токена. Раньше для изменения набора разрешений токен нужно было пересоздавать. Подробнее в разделе [Авторизация](/api/authorization#skoupy).
+| Операция | API |
+|----------|-----|
+| Get | [Список дополнительных полей](GET /custom_properties) |
-Метод [Закрепление сообщения](POST /messages/{id}/pin) теперь возвращает `204 No Content` вместо `201 Created`.
+---
-Поле `thread` в модели [сообщения](/api/models#message) теперь содержит только `id` и `chat_id`. Полная модель треда (с `message_id`, `message_chat_id`, `updated_at`) возвращается в методах [Создание треда](POST /messages/{id}/thread) и [Информация о треде](GET /threads/{id}).
+## Read Member
-Поле `payload` в модели ошибки валидации теперь имеет тип `object | null` вместо `string | null` и содержит структурированные данные — идентификатор кастомного свойства при ошибке поля или параметры авторизации.
+Список пользователей, прочитавших сообщение.
-В [CLI](/guides/cli) убрана клиентская валидация скоупов — проверка разрешений теперь происходит только на стороне API. Команда `auth refresh` удалена, профиль больше не сохраняет список скоупов.
+| Операция | API |
+|----------|-----|
+| Get Many | [Список прочитавших](GET /messages/{id}/read_member_ids) |
-Опубликован [генератор SDK](/guides/sdk/overview#generator) — генерирует типизированный клиент из OpenAPI-спецификации прямо в вашем проекте для TypeScript, Python, Go, Kotlin и Swift.
+---
-На странице Авторизация добавлена таблица [доступных скоупов](/api/authorization#dostupnye-skoupy) с указанием ролей для каждого скоупа.
+## Link Preview
-Были обновлены следующие методы:
+Разворачивание ссылок в сообщениях.
-- [Закрепление сообщения](POST /messages/{id}/pin)
-- [Отправить сообщение](POST /messages)
-- [Информация о сообщении](GET /messages/{id})
-- [Список сообщений чата](GET /messages)
+| Операция | API |
+|----------|-----|
+| Create | [Создание превью ссылки](POST /messages/{id}/link_previews) |
-
+Подробнее — в [документации разворачивания ссылок](/guides/link-previews).
-## Справочник моделей данных
+---
-Появилась новая страница [Модели](/api/models) — справочник всех моделей данных API. На одной странице собраны 14 моделей с таблицами свойств и ссылками на связанные методы: сотрудник, чат, тред, сообщение, реакция, напоминание, бот, тег и другие.
+## Search
-
+Полнотекстовый поиск по сообщениям, чатам и пользователям.
-## SDK, новые руководства и реструктуризация документации
+| Операция | API |
+|----------|-----|
+| Get Many Chats | [Поиск чатов](GET /search/chats) |
+| Get Many Messages | [Поиск сообщений](GET /search/messages) |
+| Get Many Users | [Поиск пользователей](GET /search/users) |
-Добавлены официальные SDK для 5 языков — [TypeScript](/guides/sdk/typescript), [Python](/guides/sdk/python), [Go](/guides/sdk/go), [Kotlin](/guides/sdk/kotlin) и [Swift](/guides/sdk/swift). Все SDK автоматически генерируются из OpenAPI-спецификации и включают типизированные клиенты, автопагинацию и автоматические повторные запросы при `429`.
+**Обязательный параметр:** `query` — строка поиска.
-Появились новые руководства с пошаговыми инструкциями и скриншотами:
+---
-- [Боты](/guides/bots) — создание и настройка ботов
-- [Кнопки](/guides/buttons) — интерактивные кнопки в сообщениях
-- [Входящие вебхуки](/guides/incoming-webhooks) — отправка сообщений через URL
-- [Разворачивание ссылок](/guides/link-previews) — unfurl-боты для предпросмотра ссылок
-- [Albato](/guides/albato) и [n8n](/guides/n8n) — интеграции с no-code платформами
+## Chat Export
-Документация реструктурирована: [Быстрый старт](/guides/quickstart), [Пагинация](/api/pagination), [Загрузка файлов](/api/file-uploads), [Запросы и ответы](/api/requests-responses) вынесены в отдельные страницы, раздел форм разбит на три страницы.
+Экспорт сообщений из чатов: запрос экспорта и скачивание архива.
-В вебхук [Отправка ссылок](/guides/link-previews#vebhuk-o-ssylke) добавлено поле `user_id` — идентификатор отправителя сообщения со ссылкой.
+| Операция | API |
+|----------|-----|
+| Create | [Запрос экспорта](POST /chats/exports) |
+| Get | [Скачивание архива](GET /chats/exports/{id}) |
-
+**Ключевые параметры Create:** `startAt` (дата начала, YYYY-MM-DD), `endAt` (дата окончания), `webhookUrl` (URL для уведомления о готовности).
-## Сценарии
+**Дополнительные параметры:** `chatIds` (экспорт конкретных чатов, до 50), `skipChatsFile` (не создавать chats.json).
-Появился новый раздел [Сценарии](/guides/workflows) — пошаговые инструкции для типичных задач с API. Каждый сценарий описывает, какие методы вызывать и в каком порядке, с учётом неочевидных ограничений.
+Экспорт выполняется асинхронно. После завершения Пачка отправит вебхук на указанный `webhookUrl` с `export_id`. Используйте операцию **Get** для скачивания готового архива.
-Доступны в виде справочника с поиском, входят в Agent Skills для AI-агентов и встроены в CLI.
+Подробнее — в разделе [Продвинутые функции](/guides/n8n/advanced#eksport-soobshhenij) и в [документации экспорта](/guides/export).
-
+---
-## CLI для работы с API
+## Security
-Появился официальный CLI — все методы API доступны как команды в терминале с типизированными флагами, валидацией и интерактивными подсказками.
+Журнал безопасности: отслеживание действий пользователей.
-- Готовые сценарии для типичных задач — те же, что используют [AI-агенты](/guides/ai-agents#agent-skills-skillmd)
-- Несколько профилей авторизации с безопасным хранением токенов
-- Четыре формата вывода: таблица, JSON, YAML, CSV
-- Автоматический неинтерактивный режим для CI и AI-агентов
-- Курсорная пагинация с автозагрузкой всех страниц
-- Прямые API-запросы для нестандартных сценариев
-- Автодополнение для bash, zsh и fish
+| Операция | API |
+|----------|-----|
+| Get Many | [Список событий аудита](GET /audit_events) |
-Подробнее в разделе [CLI](/guides/cli).
+**Фильтры:** `eventKey`, `actorId`, `actorType`, `entityId`, `entityType`, `startTime`, `endTime`.
-
+Подробнее — в [документации журнала аудита](/guides/audit-events).
-## Полнотекстовый поиск, сообщение о недоступности и новые поля сообщений
+---
-Были добавлены новые методы для полнотекстового поиска по сотрудникам, чатам и сообщениям:
+## Пагинация
-- [Поиск сотрудников](GET /search/users)
-- [Поиск чатов](GET /search/chats)
-- [Поиск сообщений](GET /search/messages)
+Все операции Get Many поддерживают автоматическую курсорную пагинацию:
-С помощью этих методов вы можете искать по текстовому запросу с поддержкой фильтрации и курсорной пагинации.
+- **Return All** = `true` — получить все результаты автоматически, переключаясь между страницами
+- **Return All** = `false` — получить не более **Limit** результатов (по умолчанию 50)
-В модель статуса добавлено новое поле `away_message` — сообщение при режиме «Нет на месте», которое отображается в профиле пользователя и при отправке ему личного сообщения или упоминании в чате.
+
-В модель сообщения добавлены поля `root_chat_id` (идентификатор корневого чата для сообщений в тредах), `changed_at` (дата редактирования) и `deleted_at` (дата удаления).
+*Return All и Limit для операции Get Many*
-Были обновлены следующие методы:
-- [Новый статус](PUT /profile/status)
-- [Новый статус сотрудника](PUT /users/{user_id}/status)
-- [Отправить сообщение](POST /messages)
-- [Информация о сообщении](GET /messages/{id})
-- [Редактирование сообщения](PUT /messages/{id})
-- [Список сообщений чата](GET /messages)
+n8n автоматически отправляет повторные запросы с курсором до получения всех данных.
-
+> Для операций со списками (Get Many) рекомендуется использовать **Return All = false** с разумным **Limit**, чтобы избежать долгих запросов при большом объёме данных.
-## Режим «Нет на месте» и управление статусами
-В модель статуса добавлено новое поле `is_away` — режим «Нет на месте». Также добавлены новые методы для администраторов и владельцев, позволяющие просматривать, устанавливать и удалять статус любого сотрудника.
+---
-Были добавлены новые методы:
+## Simplify
-- [Статус сотрудника](GET /users/{user_id}/status)
-- [Новый статус сотрудника](PUT /users/{user_id}/status)
-- [Удаление статуса сотрудника](DELETE /users/{user_id}/status)
+Операции получения данных (Get, Get Many) поддерживают переключатель **Simplify** (включён по умолчанию). Когда Simplify включён, из ответа API возвращаются только ключевые поля — остальные отбрасываются.
-Были обновлены следующие методы:
+| Ресурс | Ключевые поля |
+|--------|---------------|
+| Message | `id`, `entity_id`, `chat_id`, `content`, `user_id`, `created_at` |
+| Chat | `id`, `name`, `channel`, `public`, `members_count`, `created_at` |
+| User | `id`, `first_name`, `last_name`, `nickname`, `email`, `role`, `suspended` |
+| Task | `id`, `content`, `kind`, `status`, `priority`, `due_at`, `created_at` |
+| Bot | `id`, `name`, `created_at` |
+| Group Tag | `id`, `name`, `users_count` |
+| Reaction | `id`, `code`, `user_id`, `created_at` |
+| Chat Export | `id`, `status`, `created_at` |
-- [Текущий статус](GET /profile/status)
-- [Новый статус](PUT /profile/status)
+Чтобы получить все поля ответа — выключите **Simplify**.
-
+> Simplify доступен только в v2. В v1 workflow всегда возвращают полный ответ API.
-## Привязка напоминаний к чатам
-Напоминания теперь можно привязывать к чатам, в которых вы состоите — при создании напоминания укажите `chat_id`. В ответе всех методов напоминаний добавлено новое поле `chat_id`.
+---
-Были обновлены следующие методы:
+## Поисковые выпадающие списки
-- [Новое напоминание](POST /tasks)
-- [Список напоминаний](GET /tasks)
-- [Информация о напоминании](GET /tasks/{id})
-- [Редактирование напоминания](PUT /tasks/{id})
+
-
+*Поиск чата по имени в поле Chat ID*
-## Авторизация и скоупы
+
+Для поля **Chat ID** доступен поиск по имени: начните вводить текст, и n8n покажет подходящие результаты из вашего пространства Пачки.
+
+Поиск вызывает API-эндпоинт [Поиск чатов](GET /search/chats) и работает только с валидным `Access Token` в Credentials.
+
+---
+
+
+# Триггер
+
+Узел **Pachca Trigger** запускает workflow при наступлении события в Пачке — новое сообщение, нажатие кнопки, отправка формы, изменение состава команды и др.
+
+## Поддерживаемые события
+
+### Сообщения и чаты
+
+| Событие | Значение | Описание |
+|---------|----------|----------|
+| Новое сообщение | `new_message` | Создание сообщения в чате |
+| Сообщение изменено | `message_updated` | Редактирование существующего сообщения |
+| Сообщение удалено | `message_deleted` | Удаление сообщения |
+| Новая реакция | `new_reaction` | Добавление реакции к сообщению |
+| Реакция удалена | `reaction_deleted` | Удаление реакции с сообщения |
+| Участник добавлен | `chat_member_added` | Добавление участника в чат |
+| Участник удалён | `chat_member_removed` | Удаление участника из чата |
+
+### Интерактивные элементы
+
+| Событие | Значение | Описание |
+|---------|----------|----------|
+| Нажатие кнопки | `button_pressed` | Клик по Data-кнопке в сообщении |
+| Отправка формы | `form_submitted` | Отправка модальной формы |
+| Ссылка отправлена | `link_shared` | Бот может развернуть превью ссылки |
+
+### Сотрудники
+
+| Событие | Значение | Описание |
+|---------|----------|----------|
+| Приглашение сотрудника | `company_member_invite` | Отправлено приглашение новому сотруднику |
+| Подтверждение регистрации | `company_member_confirm` | Сотрудник подтвердил регистрацию |
+| Активация сотрудника | `company_member_activate` | Сотрудник активирован |
+| Обновление сотрудника | `company_member_update` | Изменение данных сотрудника |
+| Приостановка сотрудника | `company_member_suspend` | Сотрудник приостановлен |
+| Удаление сотрудника | `company_member_delete` | Сотрудник удалён |
+
+### Wildcard
+
+| Событие | Значение | Описание |
+|---------|----------|----------|
+| Все события | `*` | Получать все типы событий |
+
+
+
+*16 типов событий в Pachca Trigger*
+
+
+> **Внимание:** Бот получает события только из чатов, в которых он состоит. Убедитесь, что бот добавлен в нужные чаты.
+
+
+## Настройка
+
+Добавьте узел **Pachca Trigger** в workflow — найдите его через поиск в панели узлов.
+
+
+
+*Поиск Pachca Trigger*
+
+
+### Автоматический режим (рекомендуется)
+
+
+
+*Настройка Pachca Trigger*
+
+
+При наличии **Bot ID** в [Credentials](/guides/n8n/setup#sozdanie-credentials) вебхук регистрируется автоматически:
+
+
+ ### Шаг 1. Укажите Bot ID в Credentials
+
+Откройте Pachca API Credentials и заполните поле **Bot ID** — это ID вашего бота в Пачке.
+
+
+ ### Шаг 2. Добавьте Pachca Trigger
+
+Создайте новый workflow и добавьте узел **Pachca Trigger**. Выберите нужный тип события.
+
+
+ ### Шаг 3. Активируйте workflow
+
+Нажмите **Activate**. n8n автоматически вызовет [Обновление бота](PUT /bots/{id}) и зарегистрирует webhook URL в настройках бота.
+
+
+При деактивации workflow вебхук автоматически удаляется.
+
+**Автоматическая регистрация вебхука**
+
+```mermaid
+sequenceDiagram
+ participant n8n
+ participant Pachca as Pachca API
+ participant Bot as Бот в Пачке
+
+ Note over n8n: Активация workflow
+ n8n->>Pachca: PUT /bots/{id}
webhook_url=n8n_url
+ Pachca->>Bot: Webhook зарегистрирован
+
+ Note over Bot: Событие в чате
+ Bot->>Pachca: Новое сообщение
+ Pachca->>n8n: POST webhook_url
+ payload + signature
+ Note over n8n: Проверка подписи
+ n8n->>n8n: Запуск workflow
+
+ Note over n8n: Деактивация workflow
+ n8n->>Pachca: PUT /bots/{id}
webhook_url=null
+ Pachca->>Bot: Webhook удалён
+```
+
+
+### Ручной режим
+
+Если **Bot ID** не указан в Credentials:
+
+1. Добавьте узел **Pachca Trigger** в workflow
+2. Скопируйте сгенерированный **Webhook URL** из настроек узла
+3. Вставьте URL в настройки бота в Пачке (раздел **Webhook URL**)
+4. Активируйте workflow
+
+## Безопасность
+
+### Проверка подписи
+
+Для защиты от поддельных запросов добавьте **Signing Secret** бота в [Credentials](/guides/n8n/setup#sozdanie-credentials). Trigger автоматически проверяет HMAC-SHA256 подпись каждого входящего запроса через заголовок `pachca-signature` и отклоняет невалидные.
+
+Подробнее о механизме подписи — в разделе [Исходящие вебхуки](/guides/webhook#bezopasnost).
+
+> Рекомендуется всегда использовать Signing Secret в продакшене для защиты от несанкционированных запросов.
+
+
+### Ограничение по IP
+
+Укажите **Webhook Allowed IPs** в [Credentials](/guides/n8n/setup#sozdanie-credentials) — через запятую список IP-адресов, с которых принимаются вебхуки. Пачка отправляет вебхуки с IP `37.200.70.177`.
+
+Если поле пустое — проверка IP отключена и запросы принимаются с любого адреса.
+
+> **Внимание:** Ограничение по IP — дополнительная мера. Заголовок `x-forwarded-for` может быть подменён, если n8n не стоит за доверенным reverse proxy. Используйте вместе с Signing Secret.
+
+
+### Защита от повторов
+
+Trigger автоматически отклоняет события старше **5 минут** (по полю `webhook_timestamp` в теле запроса). Это защищает от replay-атак — повторной отправки перехваченного запроса.
+
+## Фильтрация событий
+
+Выберите конкретный тип события для фильтрации — workflow будет запускаться только при совпадении. Можно выбрать только один тип события на один узел Trigger.
+
+> Используйте **All Events** (`*`) и фильтруйте в последующих узлах (например, через IF или Switch), если нужна сложная логика маршрутизации по типу события или обработка нескольких типов в одном workflow.
+
+
+## Пример: бот-эхо
+
+
+
+*Workflow с триггером и действием Pachca*
+
+
+Простой workflow, который отвечает на каждое новое сообщение:
+
+1. **Pachca Trigger** — событие `New Message`
+2. **IF** — условие: `message.user_id` не равен ID бота (чтобы бот не отвечал сам себе)
+3. **Pachca** — операция `Message > Create`, `entityId` = ID чата из триггера, `content` = текст ответа
+
+> ID бота — это `user_id` из ответа [Информация о профиле](GET /profile) при авторизации токеном бота.
+
+
+Больше готовых сценариев — в разделе [Примеры workflow](/guides/n8n/workflows).
+
+---
+
+
+# Примеры workflow
+
+Ниже — готовые сценарии, которые можно воспроизвести в n8n. Каждый пример использует узлы **Pachca** и **Pachca Trigger** из расширения.
+
+## Приветствие нового сотрудника
+
+Автоматическое приветственное сообщение при добавлении сотрудника в канал.
+
+
+
+*Приветствие нового сотрудника*
+
+
+**Как работает:**
+
+1. **Pachca Trigger** — событие `New Message`
+2. **IF** — проверка: сообщение системное (тип `user_joined`)
+3. **Pachca** (Message > Create) — отправка приветствия в тот же чат
+
+**Что можно добавить:** отправка личного сообщения с полезными ссылками, добавление сотрудника в рабочие каналы.
+
+
+ Готовый workflow для импорта в n8n
+
+
+> После импорта замените Credentials на свои во всех узлах Pachca.
+
+
+---
+
+## Пересылка сообщений между каналами
+
+Автоматическая пересылка сообщений из одного чата в другой.
+
+
+
+*Пересылка сообщений между каналами*
+
+
+**Как работает:**
+
+1. **Pachca Trigger** — событие `New Message` (в исходном канале)
+2. **IF** — фильтрация: пропускать сервисные сообщения, пересылать только пользовательские
+3. **Pachca** (Message > Create) — отправка в целевой канал с указанием автора
+
+**Когда полезно:** дублирование важных новостей в общие каналы, агрегация обсуждений.
+
+
+ Готовый workflow для импорта в n8n
+
+
+> После импорта замените Credentials, `CHAT_ID_ИСТОЧНИКА` и `CHAT_ID_ЦЕЛЕВОГО` на свои значения.
+
+
+---
+
+## Напоминание о задачах
+
+Ежедневная проверка просроченных задач с уведомлением в чат.
+
+
+
+*Напоминание о просроченных задачах*
+
+
+**Как работает:**
+
+1. **Schedule Trigger** — ежедневный запуск (например, в 10:00)
+2. **Pachca** (Task > Get Many) — получение списка задач
+3. **IF** — фильтр: только просроченные (дедлайн < сегодня)
+4. **Pachca** (Message > Create) — уведомление в чат со списком задач
+
+**Что можно добавить:** личные уведомления ответственным, группировка по проектам.
+
+
+ Готовый workflow для импорта в n8n
+
+
+> После импорта замените Credentials и `CHAT_ID` во всех узлах Pachca.
+
+
+---
+
+## Согласование с кнопками
+
+Запрос на согласование через сообщение с кнопками и обработка ответа.
+
+
+
+*Согласование с Data-кнопками*
+
+
+**Как работает:**
+
+1. **Manual Trigger** или **Webhook** — инициация запроса
+2. **Pachca** (Message > Create) — отправка сообщения с кнопками «Согласовать» / «Отклонить»
+3. **Pachca Trigger** (в отдельном workflow) — событие `Button Pressed`
+4. **Switch** — маршрутизация по `data` из нажатой кнопки
+5. **Pachca** (Message > Update) — обновление исходного сообщения с результатом
+
+**Пример кнопок:**
+
+```json
+[
+ [
+ { "text": "Согласовать", "data": "approve" },
+ { "text": "Отклонить", "data": "reject" }
+ ]
+]
+```
+
+Подробнее о кнопках — в разделе [Продвинутые функции](/guides/n8n/advanced#knopki-v-soobshheniyax).
+
+- [approval.json](/workflows/n8n/approval.json) — Отправка запроса с кнопками
+- [approval-handler.json](/workflows/n8n/approval-handler.json) — Обработка нажатий кнопок
+
+
+> После импорта замените Credentials и `CHAT_ID` во всех узлах Pachca.
+
+
+---
+
+## Мониторинг и алерты
+
+Периодическая проверка состояния и отправка алерта при аномалиях.
+
+
+
+*Мониторинг с отправкой алертов в Пачку*
+
+
+**Как работает:**
+
+1. **Schedule Trigger** — проверка каждые 5 минут
+2. **HTTP Request** — запрос к внешнему API или сервису
+3. **IF** — проверка: статус не 200 или метрика выше порога
+4. **Pachca** (Message > Create) — отправка алерта в канал мониторинга
+
+**Что можно добавить:** проверка нескольких сервисов, графики через разворачивание ссылок, эскалация в личные сообщения.
+
+
+ Готовый workflow для импорта в n8n
+
+
+> После импорта замените Credentials, `CHAT_ID_МОНИТОРИНГА` и URL сервиса во всех узлах Pachca.
+
+
+---
+
+## Заявка на отпуск
+
+Полноценный сценарий с кнопками, формой и согласованием в треде — бот принимает заявку через модальную форму и отправляет на согласование руководителю.
+
+
+
+*Заявка на отпуск: триггер → кнопка → форма → тред → согласование*
+
+
+**Как работает (два workflow):**
+
+**Workflow 1 — Приём заявки:**
+
+1. **Pachca Trigger** — событие `New Message`, фильтр по команде `/отпуск`
+2. **Pachca** (Message > Create) — отправка сообщения с Data-кнопкой «Создать заявку»
+3. **Pachca Trigger** (событие `Button Pressed`) — пользователь нажимает кнопку
+4. **Pachca** (Form > Create) — открытие модальной формы с полями «Дата начала», «Дата окончания», «Комментарий»
+
+**Workflow 2 — Обработка и согласование:**
+
+1. **Pachca Trigger** — событие `Form Submitted`
+2. **Pachca** (Thread > Create) — создание треда с деталями заявки
+3. **Pachca** (Message > Create) — отправка в тред кнопок «Согласовать» / «Отклонить»
+4. **Pachca Trigger** (событие `Button Pressed`) — руководитель нажимает кнопку
+5. **Pachca** (Message > Create) — уведомление сотрудника о результате
+
+**Что задействовано:** триггер, кнопки, формы, треды, условная логика.
+
+> Подробнее о кнопках — в разделе [Кнопки в сообщениях](/guides/n8n/advanced#knopki-v-soobshheniyax), о формах — в разделе [Формы](/guides/n8n/advanced#formy).
+
+
+- [vacation.json](/workflows/n8n/vacation.json) — Приём команды и кнопка заявки
+- [vacation-handler.json](/workflows/n8n/vacation-handler.json) — Форма, тред и согласование
+
+
+> После импорта замените Credentials и `CHAT_ID_HR` во всех узлах Pachca.
+
+
+---
+
+## AI-ассистент
+
+Бот, использующий AI для ответов на вопросы на основе истории чата.
+
+
+
+*AI Agent с инструментами Pachca*
+
+
+**Как работает:**
+
+1. **Pachca Trigger** — событие `New Message`
+2. **AI Agent** — обработка запроса с LLM
+3. **Pachca** (Search > Get Many Messages) — как Tool для поиска информации
+4. **Pachca** (Message > Create) — как Tool для отправки ответа
+
+> Для использования AI Agent необходимо настроить LLM-провайдер (OpenAI, Anthropic и др.) в n8n. Подробнее — в разделе [Продвинутые функции](/guides/n8n/advanced#ai-agent).
+
+
+---
+
+
+# Продвинутые функции
+
+## Загрузка файлов
+
+
+
+*Ресурс File с параметрами загрузки*
+
+
+Ресурс **File** позволяет загружать файлы через двухшаговый S3 upload. n8n автоматически выполняет оба этапа: запрашивает presigned URL через API и загружает файл на S3.
+
+**Два источника файлов:**
+
+| Источник | Описание |
+|----------|----------|
+| **URL** | Файл загружается по ссылке. Укажите `fileUrl`, `fileName` и `contentType` |
+| **Binary Data** | Файл из предыдущего узла workflow (например, из HTTP Request). Укажите `binaryProperty` |
+
+**Пример workflow: загрузка PDF и отправка в чат**
+
+1. **HTTP Request** — скачать файл по URL
+2. **Pachca** (File > Create) — загрузить файл, получить `key`
+3. **Pachca** (Message > Create) — отправить сообщение с прикреплённым файлом, указав `key` в поле `files`
+
+> Загруженные файлы привязываются к сообщениям через массив `files` при создании или обновлении сообщения. Каждый файл описывается объектом с полями: `key`, `name`, `file_type`, `size`.
+
+
+Подробнее — в [документации загрузки файлов](/api/file-uploads).
+
+---
+
+## Загрузка аватара
+
+Операции **Update Avatar** для ресурсов **Profile** и **User** позволяют загружать аватар через `multipart/form-data`.
+
+**Как использовать:**
+
+1. **HTTP Request** или **Read Binary File** — загрузите изображение в бинарное свойство (по умолчанию `data`)
+2. **Pachca** (Profile > Update Avatar или User > Update Avatar) — в поле **Input Binary Field** укажите имя бинарного свойства
+
+Для удаления аватара используйте операцию **Delete Avatar** — она не требует параметров (для User — только `userId`).
+
+> Операции с аватарами сотрудников (User > Update/Delete Avatar) требуют прав администратора.
+
+
+---
+
+## Экспорт сообщений
+
+Ресурс **Chat Export** позволяет выгружать сообщения из чатов. Экспорт выполняется асинхронно: вы запрашиваете экспорт, а Пачка присылает уведомление на вебхук, когда архив готов.
+
+**Пример workflow:**
+
+
+ ### Шаг 1. Настройте приём вебхука
+
+Создайте отдельный workflow с узлом **Webhook** (встроенный в n8n). Он создаст URL, на который Пачка отправит уведомление о готовности экспорта. Скопируйте этот URL — он понадобится на следующем шаге.
+
+
+ ### Шаг 2. Запросите экспорт
+
+В основном workflow добавьте узел **Pachca** с ресурсом **Chat Export** и операцией **Create**. Укажите период (`startAt`, `endAt`) и вставьте URL вебхука из первого шага в поле `webhookUrl`.
+
+
+ ### Шаг 3. Обработайте уведомление
+
+Когда экспорт будет готов, Пачка отправит JSON на ваш вебхук:
+ ```json
+ {
+ "type": "export",
+ "event": "ready",
+ "export_id": 22322,
+ "created_at": "2025-03-20T12:33:58Z"
+ }
+ ```
+
+
+ ### Шаг 4. Скачайте архив
+
+В workflow с Webhook-узлом добавьте узел **Pachca** с ресурсом **Chat Export** и операцией **Get**. Передайте `export_id` из данных вебхука в поле **ID**. Архив будет скачан автоматически.
+
+
+**Ограничения:**
+- Максимальный период одной выгрузки: 45 дней (366 дней при указании конкретных чатов)
+- Максимум 50 чатов при фильтрации по `chatIds`
+- Новый запрос можно сделать только после завершения текущего
+
+Подробнее — в [документации экспорта](/guides/export).
+
+---
+
+## Кнопки в сообщениях
+
+
+
+*Кнопки в параметрах сообщения*
+
+
+При создании или обновлении сообщения можно добавить интерактивные кнопки через поле **Buttons**. Кнопки передаются как JSON-строка.
+
+Два типа кнопок:
+
+| Тип | Описание |
+|-----|----------|
+| **URL-кнопка** | Открывает ссылку в браузере |
+| **Data-кнопка** | Отправляет вебхук с `button_pressed` событием |
+
+Пример JSON для одной строки кнопок:
+
+```json
+[
+ [
+ { "text": "Открыть сайт", "url": "https://example.com" },
+ { "text": "Подтвердить", "data": "confirm_action" }
+ ]
+]
+```
+
+Максимум 100 кнопок на сообщение, до 8 в одной строке.
+
+Подробнее о кнопках, их внешнем виде в чате и обработке нажатий — в [документации кнопок](/guides/buttons).
+
+---
+
+## Формы
+
+Ресурс **Form** позволяет открывать модальные формы (представления) для пользователей.
+
+**Как это работает:**
+
+1. Пользователь нажимает Data-кнопку в сообщении бота
+2. Бот получает вебхук с `trigger_id`
+3. Бот вызывает [Открытие представления](POST /views/open) с `trigger_id` и описанием формы
+4. Пользователь видит модальное окно с полями
+
+**Ключевые параметры:**
+
+| Параметр | Описание |
+|----------|----------|
+| `triggerId` | Уникальный ID из вебхука кнопки (действителен 3 секунды) |
+| `title` | Заголовок модального окна |
+| `type` | Тип представления (по умолчанию `modal`) |
+| `blocks` | JSON-массив блоков формы |
+| `submitText` | Текст кнопки отправки |
+| `closeText` | Текст кнопки закрытия |
+
+**Пример workflow:**
+
+1. **Pachca Trigger** — событие `Button Pressed`
+2. **Pachca** (Form > Create) — открыть форму с `trigger_id` из триггера
+3. **Pachca Trigger** — событие `Form Submitted` (в отдельном workflow)
+4. Обработка данных формы
+
+Подробнее о формах, типах полей и внешнем виде модального окна в интерфейсе Пачки — в [документации форм](/guides/forms/overview).
+
+---
+
+## AI-агент
+
+
+
+*Pachca Tool в панели инструментов AI Agent*
+
+
+Оба узла (Pachca и Pachca Trigger) поддерживают `usableAsTool: true` — их можно использовать как инструменты для **AI Agent** в n8n.
+
+**Что это значит:**
+
+- AI Agent может вызывать операции Pachca для выполнения задач
+- Примеры: поиск сообщений, отправка ответов, создание задач
+- Agent автоматически выбирает подходящую операцию на основе запроса
+
+**Пример: AI-помощник для команды**
+
+
+ ### Шаг 1. Добавьте AI Agent
+
+Создайте новый workflow. Добавьте узел **AI Agent** и подключите LLM-модель (OpenAI, Anthropic и др.) через соответствующие Credentials.
+
+
+ ### Шаг 2. Подключите инструменты Pachca
+
+Нажмите **+** на входе **Tool** узла AI Agent и добавьте узел **Pachca**. Выберите нужную операцию — например, **Search > Get Many Messages** для поиска по сообщениям. Добавьте ещё один узел Pachca для **Message > Create** — отправки ответов.
+
+
+ ### Шаг 3. Настройте триггер
+
+Добавьте **Pachca Trigger** с событием `New Message` на вход workflow. AI Agent будет автоматически отвечать на сообщения пользователей, используя поиск по истории чатов.
+
+
+AI Agent самостоятельно выбирает подходящий инструмент на основе запроса пользователя — ищет информацию, создаёт задачи или отправляет сообщения.
+
+> Для использования AI Agent необходимо настроить LLM-провайдер (OpenAI, Anthropic и др.) в Credentials n8n.
+
+
+---
+
+## Разворачивание ссылок
+
+Ресурс **Link Preview** позволяет формировать кастомные превью для ссылок в сообщениях бота.
+
+Когда бот отправляет сообщение со ссылкой, Пачка может запросить у бота данные для превью. Бот может ответить через [Создание превью ссылки](POST /messages/{id}/link_previews) с заголовком, описанием и изображением.
+
+Подробнее — в [документации разворачивания ссылок](/guides/link-previews).
+
+---
+
+## Журнал безопасности
+
+Ресурс **Security** предоставляет доступ к журналу аудита — списку событий безопасности в пространстве.
+
+**Доступные фильтры:**
+
+| Фильтр | Описание |
+|--------|----------|
+| `eventKey` | Тип события (login, message_created, user_deleted и др.) |
+| `actorId` | ID пользователя, совершившего действие |
+| `actorType` | Тип актора (user или bot) |
+| `entityId` | ID сущности, над которой совершено действие |
+| `entityType` | Тип сущности |
+| `startTime` | Начало временного диапазона |
+| `endTime` | Конец временного диапазона |
+
+Подробнее — в [документации журнала аудита](/guides/audit-events).
+
+---
+
+
+# Устранение ошибок
+
+## Ошибки авторизации
+
+### 401 Unauthorized — неверный токен
+
+
+
+*Ошибка авторизации*
+
+
+**Причина:** указан некорректный или просроченный Access Token.
+
+**Решение:**
+
+1. Откройте **Credentials** и проверьте значение **Access Token**
+2. Убедитесь, что токен скопирован целиком, без лишних пробелов
+3. Нажмите **Test** — при ошибке создайте новый токен в Пачке
+4. Для бот-токена: **Настройки бота** → вкладка **API** → скопируйте токен
+5. Для персонального токена: **Автоматизации** → **Интеграции** → **API**
+
+### 403 Forbidden — недостаточно прав
+
+**Причина:** операция требует более высокий уровень доступа, чем предоставляет текущий токен.
+
+**Решение:**
+
+Доступ к операциям определяется [скоупами](/api/authorization#skoupy) токена, а не его типом. Убедитесь, что ваш токен включает нужные скоупы:
+
+| Операция | Требуемые скоупы |
+|----------|-----------------|
+| Управление сотрудниками (User > Create/Update/Delete) | `users:write` (доступен администраторам и владельцам) |
+| Журнал безопасности (Security > Get Many) | `audit_events:read` (доступен администраторам и владельцам) |
+| Управление тегами (Group Tag > Create/Update/Delete) | `group_tags:write` (доступен администраторам и владельцам) |
+| Отправка сообщений, чаты, задачи | `messages:write`, `chats:write`, `tasks:write` |
+
+Подробнее — в разделе [Авторизация](/api/authorization).
+
+---
+
+## Ошибки лимитов
+
+### 429 Too Many Requests — превышение лимита
+
+**Причина:** слишком много запросов за единицу времени.
+
+**Лимиты API:**
+
+| Тип операции | Лимит |
+|-------------|-------|
+| Отправка сообщений | ~4 запроса/сек на чат |
+| Остальные операции | ~50 запросов/сек |
+
+**Решение:**
+
+1. Добавьте узел **Wait** между операциями для замедления
+2. Используйте **Batching** в настройках узла Pachca (Additional Fields → Request Options → Batching)
+3. Для массовых операций используйте **Return All** = `false` с ограниченным **Limit**
+
+> При получении 429 или 5xx расширение автоматически повторяет запросы с экспоненциальной задержкой и jitter (до 5 попыток). Учитывается заголовок `Retry-After` из ответа API.
+
+
+---
+
+## Ошибки триггера
+
+### Вебхук не приходит
+
+**Возможные причины и решения:**
+
+1. **Бот не добавлен в чат** — бот получает события только из чатов, в которых он состоит. Добавьте бота в нужный канал
+2. **Workflow не активирован** — нажмите **Activate** в правом верхнем углу. Неактивные workflow не принимают вебхуки
+3. **Bot ID не указан** — при отсутствии Bot ID в Credentials авторегистрация не работает. Укажите Bot ID или настройте вебхук вручную
+4. **n8n недоступен извне** — при локальной установке Пачка не может отправить вебхук на `localhost`. Используйте туннель (ngrok, Cloudflare Tunnel) или разверните n8n на сервере с публичным IP
+
+### Ошибка подписи (Signature Mismatch)
+
+**Причина:** Signing Secret в Credentials не совпадает с секретом бота в Пачке.
+
+**Решение:** скопируйте Signing Secret из настроек бота в Пачке (вкладка **API**) и вставьте в Credentials.
+
+---
+
+## Ошибки данных
+
+### Entity ID не найден
+
+**Причина:** указан несуществующий ID чата, пользователя или сообщения.
+
+**Решение:**
+
+- Для чатов: используйте **Search** (Search > Get Many Chats) для поиска по имени
+- Для пользователей: используйте **Search** (Search > Get Many Users) для поиска
+- Для сообщений: убедитесь, что сообщение не было удалено
+
+### Бот не может отправить сообщение
+
+**Причина:** бот не является участником целевого чата.
+
+**Решение:** добавьте бота в чат. Бот может отправлять сообщения только в те чаты, в которых он состоит.
+
+---
+
+## Ошибки форм
+
+### Форма не открывается
+
+**Причина:** `trigger_id` из события `button_pressed` действителен только **3 секунды**. Если между получением вебхука и вызовом [Открытие представления](POST /views/open) проходит больше времени — форма не откроется.
+
+**Решение:**
+
+1. Убедитесь, что узел **Form > Create** стоит сразу после триггера, без долгих операций между ними
+2. Не используйте узел **Wait** между получением `trigger_id` и открытием формы
+3. Если нужна дополнительная логика — выполняйте её после отправки формы, а не до
+
+Подробнее о формах — в [документации форм](/guides/forms/overview).
+
+---
+
+## Ошибки пагинации
+
+### Возвращаются не все данные
+
+**Причина:** API возвращает данные постранично. Если **Return All** выключен, вы получаете только первую страницу (по умолчанию до 50 записей).
+
+**Решение:**
+
+1. Включите **Return All** = `true` в настройках узла — n8n автоматически пройдёт по всем страницам через cursor-based пагинацию
+2. Если нужен ограниченный набор — используйте **Return All** = `false` и укажите нужное число в поле **Limit**
+3. Для больших объёмов данных учитывайте [лимиты API](#oshibki-limitov) — при Return All n8n делает несколько запросов последовательно
+
+---
+
+## Общие рекомендации
+
+1. **Включите Retry On Fail** в настройках узла (Settings → Retry On Fail) для автоматического повтора при временных ошибках
+2. **Используйте Error Trigger** для обработки ошибок в отдельном workflow — отправляйте уведомление об ошибке в специальный канал
+3. **Проверяйте Credentials** кнопкой **Test** перед запуском workflow
+4. **Используйте Execute Step** для отладки узлов по одному, не запуская весь workflow
+
+---
+
+
+# Миграция с v1
+
+> Обновление необязательно. Все существующие workflow на v1 продолжают работать без изменений.
+
+
+## Полная обратная совместимость
+
+Версия 2.0 на 100% совместима с v1. При обновлении расширения:
+
+- Существующие workflow остаются на v1 и работают как прежде
+- Новые workflow по умолчанию создаются на v2 с обновлёнными именами
+- Переход с v1 на v2 — опциональный
+
+## Переименованные ресурсы
+
+| v1 | v2 | Изменение |
+|----|-----|-----------|
+| `reactions` | `reaction` | Единственное число |
+| `status` | `profile` | Более точное имя |
+| `customFields` | `customProperty` | Более точное имя |
+
+## Переименованные операции
+
+| Ресурс | v1 | v2 |
+|--------|-----|-----|
+| Message | `send` | `create` |
+| Message | `getById` | `get` |
+| Chat | `getById` | `get` |
+| User | `getById` | `get` |
+| Group Tag | `getById` | `get` |
+| Group Tag | `getUsers` | `getAllUsers` |
+| Reaction | `addReaction` | `create` |
+| Reaction | `deleteReaction` | `delete` |
+| Reaction | `getReactions` | `getAll` |
+| Custom Property | `getCustomProperties` | `get` |
+| Profile | `getProfile` | `get` |
+| Thread | `createThread` | `create` |
+| Thread | `getThread` | `get` |
+| Form | `createView` | `create` |
+| File | `upload` | `create` |
+
+## Перенесённые операции
+
+Некоторые операции из v1 ресурсов были перенесены в новые v2 ресурсы:
+
+| v1 ресурс | v1 операция | v2 ресурс | v2 операция |
+|-----------|-------------|-----------|-------------|
+| Chat | `getMembers` | Chat Member | `getAll` |
+| Chat | `addUsers` | Chat Member | `create` |
+| Chat | `removeUser` | Chat Member | `delete` |
+| Chat | `updateRole` | Chat Member | `update` |
+| Chat | `leaveChat` | Chat Member | `leave` |
+| Group Tag | `addTags` | Chat Member | `addGroupTags` |
+| Group Tag | `removeTag` | Chat Member | `removeGroupTags` |
+| Message | `getReadMembers` | Read Member | `getAll` |
+| Message | `unfurl` | Link Preview | `create` |
+
+> Все перенесённые операции продолжают работать в v1 workflow без изменений. Маршрутизатор автоматически транслирует v1 имена в v2.
+
+
+## Новые ресурсы (только v2)
+
+| Ресурс | Описание |
+|--------|----------|
+| **Chat Member** | Управление участниками чата: добавление, удаление, роли, теги |
+| **Custom Property** | Дополнительные поля пространства |
+| **Read Member** | Список прочитавших сообщение |
+| **Link Preview** | Разворачивание ссылок в сообщениях |
+| **Search** | Полнотекстовый поиск по чатам, сообщениям, пользователям |
+| **Chat Export** | Экспорт сообщений из чатов |
+| **Security** | Журнал безопасности |
+
+## Новые функции
+
+| Функция | Описание |
+|---------|----------|
+| **Return All / Limit** | Курсорная автопагинация вместо ручного `per`/`page` |
+| **Simplify** | Переключатель для получения только ключевых полей из ответа API ([подробнее](/guides/n8n/resources#simplify)) |
+| **Pachca Trigger** | Webhook-нода с авторегистрацией вебхука через Bot ID, 16 типов событий |
+| **AI Tool Use** | Использование узлов как инструментов AI Agent |
+| **Searchable Dropdowns** | Поиск по чатам и пользователям в выпадающих списках |
+| **File Upload** | Загрузка файлов через S3 с поддержкой URL и Binary Data |
+| **Task CRUD** | Полный CRUD для задач (было только создание) |
+
+## Как работает совместимость
+
+Расширение использует паттерн **VersionedNodeType** с `defaultVersion: 2`:
+
+- **V1** и **V2** — отдельные классы с собственными описаниями ресурсов и операций
+- Общий **SharedRouter** обрабатывает запросы обеих версий, транслируя v1 имена ресурсов и операций в v2 на лету
+- При обновлении расширения существующие ноды сохраняют `typeVersion: 1` и используют V1 класс — все параметры и поведение остаются прежними
+- Новые ноды по умолчанию создаются с `typeVersion: 2` и используют чистый V2 класс
+- В Node Creator отображаются только v2 операции — без дубликатов
+- V1 ноды в существующих workflow показывают жёлтый баннер «New node version available» с предложением обновиться
+
+## Как обновить workflow (необязательно)
+
+При открытии существующего workflow вы увидите жёлтый баннер в настройках v1 нод — это информационное уведомление, менять ничего не нужно.
+
+Если вы хотите перевести ноду на v2:
+
+1. Откройте workflow в n8n
+2. Удалите v1 ноду Pachca
+3. Добавьте новую ноду Pachca (по умолчанию v2)
+4. Перенастройте с v2 именами ресурсов и операций
+5. API-вызовы идентичны — изменились только имена в UI
+
+> При удалении ноды и добавлении новой вы получите v2 с обновлёнными именами и новыми ресурсами. Все параметры и API endpoint-ы остались прежними.
+
+
+---
+
+
+# Интеграция с Albato
+
+Albato — платформа для интеграции различных сервисов (CRM, системы аналитики, мессенджеры и т.д.). Через Albato к Пачке можно подключить более 250 сервисов и получать уведомления прямо в чаты и каналы Пачки.
+
+Уведомления приходят в виде сообщения от того, кто подключил интеграцию. Количество интеграций в одном чате не ограничено.
+
+> Пачка пока способна обрабатывать только входящие события. Отправить информацию из Пачки другим сервисам через Albato нельзя.
+
+
+> **Внимание:** Albato работает на коммерческой основе. [Тарифы](https://albato.ru/pricing) можно найти на сайте сервиса, первые 14 дней — бесплатно.
+
+
+## Какие сервисы можно подключить
+
+Через Albato к Пачке можно подключить более 250 сервисов: CRM (Битрикс24, AmoCRM, СБИС CRM), системы управления проектами (Jira, Trello), а также МойСклад, CloudPayments, YClients и другие. Полный список доступен при создании связки в личном кабинете Albato.
+
+## Какие уведомления будут приходить
+
+Через Albato можно настроить текстовые уведомления о различных событиях. Набор событий зависит от конкретного сервиса. Например, при интеграции с Битрикс24 — уведомления об изменении в сделке, создании счёта, новом лиде. При интеграции с Telegram — о входящем сообщении или новом участнике группы.
+
+Текст сообщения можно настроить: отправлять только содержимое события или добавлять пояснения и дополнительные поля.
+
+## Кто может создавать интеграции
+
+Интеграцию через Albato может создавать только участник с ролью «Администратор».
+
+## Как настроить интеграцию
+
+
+ ### Шаг 1. Зарегистрируйтесь в Albato
+
+
+
+*Форма регистрации на сайте Albato*
+
+
+ 1. Откройте [сайт Albato](https://albato.ru/) и нажмите **«Регистрация»** в верхнем правом углу
+ 2. Введите свои данные и нажмите **«Зарегистрироваться»**
+ 3. Проверьте почту — вам должен прийти код подтверждения
+ 4. Введите код
+
+
+ ### Шаг 2. Подключите Пачку к Albato
+
+
+
+*Раздел подключений в личном кабинете Albato*
+
+
+ 
+
+*Выбор Пачки в списке сервисов Albato*
+
+
+ 1. Зайдите в свой аккаунт в Пачке в том браузере, в котором вы настраиваете интеграцию
+ 2. Вернитесь на сайт Albato и в личном кабинете откройте раздел **Подключения**
+ 3. Нажмите **«Добавить подключение»** в верхнем левом углу
+ 4. Выберите в списке сервисов Пачку
+ 5. Добавьте название подключения или оставьте базовое и нажмите **«Далее»**
+ 6. Нажмите **«Предоставить доступ Albato»**
+ 7. В открывшемся окне нажмите **«Продолжить»**
+
+
+ ### Шаг 3. Подключите внешний сервис
+
+
+
+*Добавление нового подключения к сервису*
+
+
+ 
+
+*Настройка подключения внешнего сервиса*
+
+
+ 1. В личном кабинете откройте раздел **Подключения** и нажмите **«Добавить подключение»**
+ 2. Выберите в списке сервис, который хотите интегрировать с Пачкой, и нажмите **«Предоставить доступ»**
+ 3. Добавьте название подключения или оставьте базовое, введите необходимые данные и нажмите **«Далее»**
+
+ Для подключения разных сервисов нужны разные данные. В базе знаний Albato есть [инструкции](https://blog.albato.ru/category/instrukczii/) для основных сервисов.
+
+
+ ### Шаг 4. Добавьте триггер
+
+
+
+*Создание триггера для связки в Albato*
+
+
+ 1. В личном кабинете перейдите в раздел **Мои связки** и нажмите **«Создать новую связку»**
+ 2. Нажмите **«Добавить триггер, который будет запускать связку»**
+ 3. Выберите сервис, из которого будут передаваться данные в Пачку
+ 4. Выберите событие, которое будет запускать передачу данных
+ 5. Выберите подключённый аккаунт
+ 6. Нажмите **«Добавить триггер»**
+ 7. При необходимости укажите дополнительные настройки (для каждого сервиса они свои)
+
+ > В Albato можно добавлять условия для прерывания или продолжения работы связки через фильтр входящих данных внутри триггера. Подробнее — в [документации Albato](https://blog.albato.ru/instrument-filtr-vhodyashhih-dannyh/).
+
+
+ ### Шаг 5. Добавьте действие
+
+
+
+*Добавление действия после триггера*
+
+
+ 
+
+*Настройка отправки сообщения в Пачку*
+
+
+ 1. Нажмите **«Добавьте действие, которое будет происходить после старта связки»**
+ 2. Выберите Пачку в разделе **Сервис**
+ 3. Выберите **Отправить сообщение** в разделе **Действие**
+ 4. Выберите подключённый аккаунт
+ 5. Введите ID чата, в который хотите отправлять уведомления. ID чата можно найти в адресной строке браузерной версии Пачки — он состоит из семи цифр и располагается после `chats/`
+ 6. В разделе **Тип чата** выберите **Беседа**
+ 7. Создайте текст сообщения из данных внешнего сервиса
+ 8. Нажмите **«Сохранить»**
+
+
+ ### Шаг 6. Запустите связку
+
+Нажмите кнопку запуска в нижнем правом углу. Статус работы связки можно отслеживать в журнале связок в личном кабинете.
+
+
+
+---
+
+
+# Последние обновления
+
+> Автоматически отслеживайте обновления: подпишитесь на [RSS-ленту](/feed.xml) или используйте [markdown-версию этой страницы](/updates.md) для интеграции с инструментами и AI-агентами.
+
+
+
+
+## Аватары, сортировка и n8n Node v2
+
+Были добавлены новые методы для управления аватарами:
+
+- [Загрузка аватара](PUT /profile/avatar)
+- [Удаление аватара](DELETE /profile/avatar)
+- [Загрузка аватара сотрудника](PUT /users/{user_id}/avatar)
+- [Удаление аватара сотрудника](DELETE /users/{user_id}/avatar)
+
+С помощью этих методов вы можете загружать и удалять аватары для своего профиля и для сотрудников вашей компании.
+
+В методах [Список чатов](GET /chats) и [Список сообщений чата](GET /messages) добавлен новый формат параметров сортировки: `sort` (поле сортировки) и `order` (направление: `asc` или `desc`).
+
+Расширение [n8n](/guides/n8n/overview) для Пачки обновлено до версии 2.0.0. Нода автоматически генерируется из OpenAPI-спецификации и всегда синхронизирована с актуальным API.
+
+- **18 ресурсов и 60+ операций** — полное покрытие API, включая задачи, поиск, экспорт чатов и журнал безопасности
+- **[Pachca Trigger](/guides/n8n/trigger)** — webhook-нода с авторегистрацией вебхука и 16 типами событий
+- **Курсорная автопагинация** — Return All / Limit вместо ручного per/page
+- **[Simplify](/guides/n8n/resources#simplify)** — переключатель для получения только ключевых полей
+- **AI Tool Use** — использование узлов как инструментов AI Agent
+- **Полная [обратная совместимость](/guides/n8n/migration)** — все существующие workflow на v1 продолжают работать без изменений
+
+В [CLI](/guides/cli) добавлены команды для управления аватарами и обновлены параметры сортировки (`--sort` и `--order`). Все 6 SDK обновлены: [TypeScript](/guides/sdk/typescript), [Python](/guides/sdk/python), [Go](/guides/sdk/go), [Kotlin](/guides/sdk/kotlin), [Swift](/guides/sdk/swift) и [C#](/guides/sdk/csharp).
+
+
+
+## C# SDK
+
+Добавлен официальный [C# SDK](/guides/sdk/csharp) для .NET 8+. Типизированный клиент с поддержкой async/await, автопагинацией и автоматическими повторными запросами при ошибках сервера. Все примеры кода в документации API теперь доступны на C#.
+
+
+
+## Конструктор форм
+
+На странице [Формы](/guides/forms/overview) появился интерактивный конструктор представлений. Добавляйте блоки из палитры, настраивайте все параметры через визуальный редактор и сразу видите результат — включая календарь, время, выпадающие списки, радиокнопки и чекбоксы.
+
+
+
+## Интерактивный playground сообщений и вебхуков
+
+На страницах [Кнопки в сообщениях](/guides/buttons) и [Входящие вебхуки](/guides/incoming-webhooks) появились интерактивные playground — редактируйте JSON слева и сразу видите результат справа. Превью сообщения отображает все поля: текст с markdown-разметкой, кнопки, файлы, превью ссылок, кастомные аватар и имя бота.
+
+
+
+## Ошибки оплаты, роли сотрудников, закрепление сообщений
+
+Во все методы, требующие оплаченного тарифа, добавлен ответ `402 Payment Required`.
+
+В методах [Новый сотрудник](POST /users) и [Редактирование сотрудника](PUT /users/{id}) уточнены допустимые значения поля `role`: `admin`, `user` и `multi_guest`. Значение `guest` недоступно для установки через API — оно присутствует только в ответах.
+
+Поле `link_preview` в запросе [Отправить сообщение](POST /messages) перенесено на верхний уровень тела запроса — отдельно от объекта `message`.
+
+Метод [Закрепление сообщения](POST /messages/{id}/pin) при попытке закрепить уже закреплённое сообщение возвращает `422 Unprocessable Entity`.
+
+Параметры `start_time` и `end_time` в методе [Журнал аудита событий](GET /audit_events) стали необязательными — без них возвращаются все события.
+
+Уточнён тип поля `name` в модели [реакции](/api/models#reaktsiya-na-soobschenie): `string | null` вместо опционального.
+
+В [CLI](/guides/cli) параметры сортировки в командах `chats list` и `messages list` заменены на `--sort` и `--order` — вместо прежних `--sort-id` и `--sort-last-message-at`.
+
+Были обновлены следующие методы:
+
+- [Отправить сообщение](POST /messages)
+- [Новый сотрудник](POST /users)
+- [Редактирование сотрудника](PUT /users/{id})
+- [Закрепление сообщения](POST /messages/{id}/pin)
+- [Журнал аудита событий](GET /audit_events)
+
+
+
+## Редактирование скоупов, исправления в моделях
+
+Скоупы персональных токенов теперь можно изменять после создания — в настройках токена. Раньше для изменения набора разрешений токен нужно было пересоздавать. Подробнее в разделе [Авторизация](/api/authorization#skoupy).
+
+Метод [Закрепление сообщения](POST /messages/{id}/pin) теперь возвращает `204 No Content` вместо `201 Created`.
+
+Поле `thread` в модели [сообщения](/api/models#message) теперь содержит только `id` и `chat_id`. Полная модель треда (с `message_id`, `message_chat_id`, `updated_at`) возвращается в методах [Создание треда](POST /messages/{id}/thread) и [Информация о треде](GET /threads/{id}).
+
+Поле `payload` в модели ошибки валидации теперь имеет тип `object | null` вместо `string | null` и содержит структурированные данные — идентификатор кастомного свойства при ошибке поля или параметры авторизации.
+
+В [CLI](/guides/cli) убрана клиентская валидация скоупов — проверка разрешений теперь происходит только на стороне API. Команда `auth refresh` удалена, профиль больше не сохраняет список скоупов.
+
+Опубликован [генератор SDK](/guides/sdk/overview#generator) — генерирует типизированный клиент из OpenAPI-спецификации прямо в вашем проекте для TypeScript, Python, Go, Kotlin и Swift.
+
+На странице Авторизация добавлена таблица [доступных скоупов](/api/authorization#dostupnye-skoupy) с указанием ролей для каждого скоупа.
+
+Были обновлены следующие методы:
+
+- [Закрепление сообщения](POST /messages/{id}/pin)
+- [Отправить сообщение](POST /messages)
+- [Информация о сообщении](GET /messages/{id})
+- [Список сообщений чата](GET /messages)
+
+
+
+## Справочник моделей данных
+
+Появилась новая страница [Модели](/api/models) — справочник всех моделей данных API. На одной странице собраны 14 моделей с таблицами свойств и ссылками на связанные методы: сотрудник, чат, тред, сообщение, реакция, напоминание, бот, тег и другие.
+
+
+
+## SDK, новые руководства и реструктуризация документации
+
+Добавлены официальные SDK для 5 языков — [TypeScript](/guides/sdk/typescript), [Python](/guides/sdk/python), [Go](/guides/sdk/go), [Kotlin](/guides/sdk/kotlin) и [Swift](/guides/sdk/swift). Все SDK автоматически генерируются из OpenAPI-спецификации и включают типизированные клиенты, автопагинацию и автоматические повторные запросы при `429`.
+
+Появились новые руководства с пошаговыми инструкциями и скриншотами:
+
+- [Боты](/guides/bots) — создание и настройка ботов
+- [Кнопки](/guides/buttons) — интерактивные кнопки в сообщениях
+- [Входящие вебхуки](/guides/incoming-webhooks) — отправка сообщений через URL
+- [Разворачивание ссылок](/guides/link-previews) — unfurl-боты для предпросмотра ссылок
+- [Albato](/guides/albato) и [n8n](/guides/n8n/overview) — интеграции с no-code платформами
+
+Документация реструктурирована: [Быстрый старт](/guides/quickstart), [Пагинация](/api/pagination), [Загрузка файлов](/api/file-uploads), [Запросы и ответы](/api/requests-responses) вынесены в отдельные страницы, раздел форм разбит на три страницы.
+
+В вебхук [Отправка ссылок](/guides/link-previews#vebhuk-o-ssylke) добавлено поле `user_id` — идентификатор отправителя сообщения со ссылкой.
+
+
+
+## Сценарии
+
+Появился новый раздел [Сценарии](/guides/workflows) — пошаговые инструкции для типичных задач с API. Каждый сценарий описывает, какие методы вызывать и в каком порядке, с учётом неочевидных ограничений.
+
+Доступны в виде справочника с поиском, входят в Agent Skills для AI-агентов и встроены в CLI.
+
+
+
+## CLI для работы с API
+
+Появился официальный CLI — все методы API доступны как команды в терминале с типизированными флагами, валидацией и интерактивными подсказками.
+
+- Готовые сценарии для типичных задач — те же, что используют [AI-агенты](/guides/ai-agents#agent-skills-skillmd)
+- Несколько профилей авторизации с безопасным хранением токенов
+- Четыре формата вывода: таблица, JSON, YAML, CSV
+- Автоматический неинтерактивный режим для CI и AI-агентов
+- Курсорная пагинация с автозагрузкой всех страниц
+- Прямые API-запросы для нестандартных сценариев
+- Автодополнение для bash, zsh и fish
+
+Подробнее в разделе [CLI](/guides/cli).
+
+
+
+## Полнотекстовый поиск, сообщение о недоступности и новые поля сообщений
+
+Были добавлены новые методы для полнотекстового поиска по сотрудникам, чатам и сообщениям:
+
+- [Поиск сотрудников](GET /search/users)
+- [Поиск чатов](GET /search/chats)
+- [Поиск сообщений](GET /search/messages)
+
+С помощью этих методов вы можете искать по текстовому запросу с поддержкой фильтрации и курсорной пагинации.
+
+В модель статуса добавлено новое поле `away_message` — сообщение при режиме «Нет на месте», которое отображается в профиле пользователя и при отправке ему личного сообщения или упоминании в чате.
+
+В модель сообщения добавлены поля `root_chat_id` (идентификатор корневого чата для сообщений в тредах), `changed_at` (дата редактирования) и `deleted_at` (дата удаления).
+
+Были обновлены следующие методы:
+
+- [Новый статус](PUT /profile/status)
+- [Новый статус сотрудника](PUT /users/{user_id}/status)
+- [Отправить сообщение](POST /messages)
+- [Информация о сообщении](GET /messages/{id})
+- [Редактирование сообщения](PUT /messages/{id})
+- [Список сообщений чата](GET /messages)
+
+
+
+## Режим «Нет на месте» и управление статусами
+
+В модель статуса добавлено новое поле `is_away` — режим «Нет на месте». Также добавлены новые методы для администраторов и владельцев, позволяющие просматривать, устанавливать и удалять статус любого сотрудника.
+
+Были добавлены новые методы:
+
+- [Статус сотрудника](GET /users/{user_id}/status)
+- [Новый статус сотрудника](PUT /users/{user_id}/status)
+- [Удаление статуса сотрудника](DELETE /users/{user_id}/status)
+
+Были обновлены следующие методы:
+
+- [Текущий статус](GET /profile/status)
+- [Новый статус](PUT /profile/status)
+
+
+
+## Привязка напоминаний к чатам
+
+Напоминания теперь можно привязывать к чатам, в которых вы состоите — при создании напоминания укажите `chat_id`. В ответе всех методов напоминаний добавлено новое поле `chat_id`.
+
+Были обновлены следующие методы:
+
+- [Новое напоминание](POST /tasks)
+- [Список напоминаний](GET /tasks)
+- [Информация о напоминании](GET /tasks/{id})
+- [Редактирование напоминания](PUT /tasks/{id})
+
+
+
+## Авторизация и скоупы
Добавлена поддержка OAuth 2.0 скоупов — теперь каждый метод API указывает, какой скоуп токена необходим для его вызова. Скоупы отображаются на странице каждого метода в виде бейджа.
@@ -6469,8 +7841,10 @@ Authorization: Bearer
| `profile:read` | Просмотр информации о своем профиле | Все |
| `profile_status:read` | Просмотр статуса профиля | Все |
| `profile_status:write` | Изменение и удаление статуса профиля | Все |
+| `profile_avatar:write` | Изменение и удаление аватара профиля | Все |
| `user_status:read` | Просмотр статуса сотрудника | Владелец, Администратор |
| `user_status:write` | Изменение и удаление статуса сотрудника | Владелец, Администратор |
+| `user_avatar:write` | Изменение и удаление аватара сотрудника | Владелец, Администратор |
| `custom_properties:read` | Просмотр дополнительных полей | Все |
| `audit_events:read` | Просмотр журнала аудита | Владелец |
| `tasks:read` | Просмотр задач | Все |
@@ -6700,8 +8074,8 @@ API Пачки использует **cursor-based** пагинацию для
#### PaginationMeta
-- `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
```json title="Пример ответа"
@@ -6717,7 +8091,7 @@ API Пачки использует **cursor-based** пагинацию для
Для перехода на следующую страницу передайте значение `next_page` в параметр `cursor` следующего запроса. Последняя страница достигнута, когда массив `data` вернулся пустым.
-> Не определяйте последнюю страницу по количеству записей в ответе — оно может быть меньше `limit` и на промежуточных страницах. Проверяйте пустой массив `data`. Курсор — непрозрачный токен: не парсите и не сохраняйте его между сессиями. Всегда явно указывайте `limit` — не полагайтесь на значение по умолчанию.
+> Поле `next_page` **всегда присутствует** в ответе и никогда не бывает `null` — даже на последней странице. Не используйте `next_page == null` как признак конца данных. Единственный надёжный способ — проверять, что массив `data` пуст. Количество записей в ответе может быть меньше `limit` и на промежуточных страницах — не полагайтесь на него. Курсор — непрозрачный токен: не парсите и не сохраняйте его между сессиями. Всегда явно указывайте `limit` — не полагайтесь на значение по умолчанию.
### Методы поиска
@@ -7023,7 +8397,7 @@ await fetch(`${BASE}/messages`, {
Все модели данных, возвращаемые в ответах API. Каждая модель содержит связанные методы и таблицу свойств.
-> Методы [Получение подписи](POST /uploads) и [Загрузка файла](POST /direct_url) не возвращают модели данных.
+> Методы [Получение подписи](POST /uploads), [Загрузка файла](POST /direct_url), [Загрузка аватара](PUT /profile/avatar), [Удаление аватара](DELETE /profile/avatar), [Загрузка аватара сотрудника](PUT /users/{user_id}/avatar) и [Удаление аватара сотрудника](DELETE /users/{user_id}/avatar) не возвращают модели данных.
## Дополнительное поле
@@ -7455,6 +8829,18 @@ await fetch(`${BASE}/messages`, {
- `user_id: integer, int32` (required) — Идентификатор пользователя, который нажал кнопку. Пример: `2345`
- `chat_id: integer, int32` (required) — Идентификатор чата, в котором была нажата кнопка. Пример: `9012`
- `webhook_timestamp: integer, int32` (required) — Дата и время отправки вебхука (UTC+0) в формате UNIX. Пример: `1747574400`
+ - **ViewSubmitWebhookPayload**: Структура исходящего вебхука о заполнении формы
+ - `type: string` (required) — Тип объекта. Пример: `"view"`
+ Значения: `view` — Для формы всегда view
+ - `event: string` (required) — Тип события. Пример: `"submit"`
+ Значения: `submit` — Отправка формы
+ - `callback_id: string` (required) — Идентификатор обратного вызова, указанный при открытии представления. Пример: `"timeoff_request_form"`
+ - `private_metadata: string` (required) — Приватные метаданные, указанные при открытии представления. Пример: `"{'timeoff_id':4378}"`
+ - `user_id: integer, int32` (required) — Идентификатор пользователя, который отправил форму. Пример: `1235523`
+ - `data: Record` (required) — Данные заполненных полей представления. Ключ — `action_id` поля, значение — введённые данные
+ **Структура значений Record:**
+ - Тип значения: `any`
+ - `webhook_timestamp: integer, int32` (required) — Дата и время отправки вебхука (UTC+0) в формате UNIX. Пример: `1755075544`
- **ChatMemberWebhookPayload**: Структура исходящего вебхука об участниках чата
- `type: string` (required) — Тип объекта. Пример: `"chat_member"`
Значения: `chat_member` — Для участника чата всегда chat_member
@@ -8371,15 +9757,276 @@ curl "https://api.pachca.com/api/shared/v1/profile" \
**Схема ответа при ошибке:**
-- `error: string` (required) — Код ошибки
-- `error_description: string` (required) — Описание ошибки
+- `error: string` (required) — Код ошибки
+- `error_description: string` (required) — Описание ошибки
+
+**Пример ответа:**
+
+```json
+{
+ "error": "invalid_token",
+ "error_description": "Access token is missing"
+}
+```
+
+
+---
+
+# Загрузка аватара
+
+**Метод**: `PUT`
+
+**Путь**: `/profile/avatar`
+
+> **Скоуп:** `profile_avatar:write`
+
+Метод для загрузки или обновления аватара своего профиля. Файл передается в формате `multipart/form-data`.
+
+## Тело запроса
+
+
+## Пример запроса
+
+```bash
+curl -X PUT "https://api.pachca.com/api/shared/v1/profile/avatar" \
+ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
+ -F "image=@filename.png"
+```
+
+## Ответы
+
+### 200: The request has succeeded.
+
+**Схема ответа:**
+
+- `data: object` (required) — Данные аватара
+ - `image_url: string` (required) — URL аватара
+
+**Пример ответа:**
+
+```json
+{
+ "data": {
+ "image_url": "https://pachca-prod.s3.amazonaws.com/uploads/0001/0001/image.jpg"
+ }
+}
+```
+
+### 401: Access is unauthorized.
+
+**Схема ответа при ошибке:**
+
+- `error: string` (required) — Код ошибки
+- `error_description: string` (required) — Описание ошибки
+
+**Пример ответа:**
+
+```json
+{
+ "error": "invalid_token",
+ "error_description": "Access token is missing"
+}
+```
+
+### 402: Client error
+
+**Схема ответа при ошибке:**
+
+- `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
+### 403: Access is forbidden.
+
+**Схема ответа при ошибке:**
+
+**anyOf** - один из вариантов:
+
+- **ApiError**: Ошибка API (используется для 400, 402, 403, 404, 409, 410, 422)
+ - `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+- **OAuthError**: Ошибка OAuth авторизации (используется для 401 и 403)
+ - `error: string` (required) — Код ошибки
+ - `error_description: string` (required) — Описание ошибки
+
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
+### 422: Client error
+
+**Схема ответа при ошибке:**
+
+- `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
+
+---
+
+# Удаление аватара
+
+**Метод**: `DELETE`
+
+**Путь**: `/profile/avatar`
+
+> **Скоуп:** `profile_avatar:write`
+
+Метод для удаления аватара своего профиля.
+
+## Пример запроса
+
+```bash
+curl -X DELETE "https://api.pachca.com/api/shared/v1/profile/avatar" \
+ -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
+```
+
+## Ответы
+
+### 204: There is no content to send for this request, but the headers may be useful.
+
+### 401: Access is unauthorized.
+
+**Схема ответа при ошибке:**
+
+- `error: string` (required) — Код ошибки
+- `error_description: string` (required) — Описание ошибки
+
+**Пример ответа:**
+
+```json
+{
+ "error": "invalid_token",
+ "error_description": "Access token is missing"
+}
+```
+
+### 402: Client error
+
+**Схема ответа при ошибке:**
+
+- `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
+### 403: Access is forbidden.
+
+**Схема ответа при ошибке:**
+
+**anyOf** - один из вариантов:
+
+- **ApiError**: Ошибка API (используется для 400, 402, 403, 404, 409, 410, 422)
+ - `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+- **OAuthError**: Ошибка OAuth авторизации (используется для 401 и 403)
+ - `error: string` (required) — Код ошибки
+ - `error_description: string` (required) — Описание ошибки
**Пример ответа:**
```json
{
- "error": "invalid_token",
- "error_description": "Access token is missing"
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
}
```
@@ -9085,9 +10732,9 @@ curl "https://api.pachca.com/api/shared/v1/users?query=Олег&limit=1" \
- `last_activity_at: date-time` (required) — Дата последней активности пользователя (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
- `time_zone: string` (required) — Часовой пояс пользователя
- `image_url: string` (required) — Ссылка на скачивание аватарки пользователя
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
@@ -9235,7 +10882,216 @@ curl "https://api.pachca.com/api/shared/v1/users?query=Олег&limit=1" \
}
```
-### 422: Client error
+### 422: Client error
+
+**Схема ответа при ошибке:**
+
+- `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
+
+---
+
+# Информация о сотруднике
+
+**Метод**: `GET`
+
+**Путь**: `/users/{id}`
+
+> **Скоуп:** `users:read`
+
+Метод для получения информации о сотруднике.
+
+Для получения сотрудника вам необходимо знать его `id` и указать его в `URL` запроса.
+
+## Параметры
+
+### Path параметры
+
+- `id: integer, int32` (required) — Идентификатор пользователя
+
+
+## Пример запроса
+
+```bash
+curl "https://api.pachca.com/api/shared/v1/users/12" \
+ -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
+```
+
+## Ответы
+
+### 200: The request has succeeded.
+
+**Схема ответа:**
+
+- `data: object` (required) — Сотрудник
+ - `id: integer, int32` (required) — Идентификатор пользователя
+ - `first_name: string` (required) — Имя
+ - `last_name: string` (required) — Фамилия
+ - `nickname: string` (required) — Имя пользователя
+ - `email: string` (required) — Электронная почта
+ - `phone_number: string` (required) — Телефон
+ - `department: string` (required) — Департамент
+ - `title: string` (required) — Должность
+ - `role: string` (required) — Уровень доступа
+ Значения: `admin` — Администратор, `user` — Сотрудник, `multi_guest` — Мульти-гость, `guest` — Гость
+ - `suspended: boolean` (required) — Деактивация пользователя
+ - `invite_status: string` (required) — Статус приглашения
+ Значения: `confirmed` — Принято, `sent` — Отправлено
+ - `list_tags: array of string` (required) — Массив тегов, привязанных к сотруднику
+ - `custom_properties: array of object` (required) — Дополнительные поля сотрудника
+ - `id: integer, int32` (required) — Идентификатор поля
+ - `name: string` (required) — Название поля
+ - `data_type: string` (required) — Тип поля
+ Значения: `string` — Строковое значение, `number` — Числовое значение, `date` — Дата, `link` — Ссылка
+ - `value: string` (required) — Значение
+ - `user_status: object` (required) — Статус
+ - `emoji: string` (required) — Emoji символ статуса
+ - `title: string` (required) — Текст статуса
+ - `expires_at: date-time` (required) — Срок жизни статуса (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
+ - `is_away: boolean` (required) — Режим «Нет на месте»
+ - `away_message: object` (required) — Сообщение при режиме «Нет на месте». Отображается в профиле пользователя, а также при отправке ему личного сообщения или упоминании в чате.
+ - `text: string` (required) — Текст сообщения
+ - `bot: boolean` (required) — Является ботом
+ - `sso: boolean` (required) — Использует ли пользователь SSO
+ - `created_at: date-time` (required) — Дата создания (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
+ - `last_activity_at: date-time` (required) — Дата последней активности пользователя (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
+ - `time_zone: string` (required) — Часовой пояс пользователя
+ - `image_url: string` (required) — Ссылка на скачивание аватарки пользователя
+
+**Пример ответа:**
+
+```json
+{
+ "data": {
+ "id": 12,
+ "first_name": "Олег",
+ "last_name": "Петров",
+ "nickname": "",
+ "email": "olegp@example.com",
+ "phone_number": "",
+ "department": "Продукт",
+ "title": "CIO",
+ "role": "admin",
+ "suspended": false,
+ "invite_status": "confirmed",
+ "list_tags": [
+ "Product",
+ "Design"
+ ],
+ "custom_properties": [
+ {
+ "id": 1678,
+ "name": "Город",
+ "data_type": "string",
+ "value": "Санкт-Петербург"
+ }
+ ],
+ "user_status": {
+ "emoji": "🎮",
+ "title": "Очень занят",
+ "expires_at": "2024-04-08T10:00:00.000Z",
+ "is_away": false,
+ "away_message": {
+ "text": "Я в отпуске до 15 апреля. По срочным вопросам обращайтесь к @ivanov."
+ }
+ },
+ "bot": false,
+ "sso": false,
+ "created_at": "2020-06-08T09:32:57.000Z",
+ "last_activity_at": "2025-01-20T13:40:07.000Z",
+ "time_zone": "Europe/Moscow",
+ "image_url": "https://app.pachca.com/users/12/photo.jpg"
+ }
+}
+```
+
+### 401: Access is unauthorized.
+
+**Схема ответа при ошибке:**
+
+- `error: string` (required) — Код ошибки
+- `error_description: string` (required) — Описание ошибки
+
+**Пример ответа:**
+
+```json
+{
+ "error": "invalid_token",
+ "error_description": "Access token is missing"
+}
+```
+
+### 402: Client error
+
+**Схема ответа при ошибке:**
+
+- `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
+### 403: Access is forbidden.
+
+**Схема ответа при ошибке:**
+
+- `error: string` (required) — Код ошибки
+- `error_description: string` (required) — Описание ошибки
+
+**Пример ответа:**
+
+```json
+{
+ "error": "invalid_token",
+ "error_description": "Access token is missing"
+}
+```
+
+### 404: The server cannot find the requested resource.
**Схема ответа при ошибке:**
@@ -9268,17 +11124,17 @@ curl "https://api.pachca.com/api/shared/v1/users?query=Олег&limit=1" \
---
-# Информация о сотруднике
+# Редактирование сотрудника
-**Метод**: `GET`
+**Метод**: `PUT`
**Путь**: `/users/{id}`
-> **Скоуп:** `users:read`
+> **Скоуп:** `users:update`
-Метод для получения информации о сотруднике.
+Метод для редактирования сотрудника.
-Для получения сотрудника вам необходимо знать его `id` и указать его в `URL` запроса.
+Для редактирования сотрудника вам необходимо знать его `id` и указать его в `URL` запроса. Все редактируемые параметры сотрудника указываются в теле запроса. Получить актуальный список идентификаторов дополнительных полей сотрудника вы можете в методе [Список дополнительных полей](GET /custom_properties).
## Параметры
@@ -9287,11 +11143,85 @@ curl "https://api.pachca.com/api/shared/v1/users?query=Олег&limit=1" \
- `id: integer, int32` (required) — Идентификатор пользователя
+## Тело запроса
+
+**Обязательно**
+
+Формат: `application/json`
+
+### Схема
+
+- `user: object` (required) — Собранный объект параметров редактируемого сотрудника
+ - `first_name: string` — Имя
+ - `last_name: string` — Фамилия
+ - `email: string` — Электронная почта
+ - `phone_number: string` — Телефон
+ - `nickname: string` — Имя пользователя
+ - `department: string` — Департамент
+ - `title: string` — Должность
+ - `role: string` — Уровень доступа
+ Значения: `admin` — Администратор, `user` — Сотрудник, `multi_guest` — Мульти-гость
+ - `suspended: boolean` — Деактивация пользователя
+ - `list_tags: array of string` — Массив тегов, привязываемых к сотруднику
+ - `custom_properties: array of object` — Задаваемые дополнительные поля
+ - `id: integer, int32` (required) — Идентификатор поля
+ - `value: string` (required) — Устанавливаемое значение
+
+### Пример
+
+```json
+{
+ "user": {
+ "first_name": "Олег",
+ "last_name": "Петров",
+ "email": "olegpetrov@example.com",
+ "phone_number": "+79001234567",
+ "nickname": "olegpetrov",
+ "department": "Отдел разработки",
+ "title": "Старший разработчик",
+ "role": "user",
+ "suspended": false,
+ "list_tags": [
+ "Product"
+ ],
+ "custom_properties": [
+ {
+ "id": 1678,
+ "value": "Санкт-Петербург"
+ }
+ ]
+ }
+}
+```
+
## Пример запроса
```bash
-curl "https://api.pachca.com/api/shared/v1/users/12" \
- -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
+curl -X PUT "https://api.pachca.com/api/shared/v1/users/12" \
+ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "user": {
+ "first_name": "Олег",
+ "last_name": "Петров",
+ "email": "olegpetrov@example.com",
+ "phone_number": "+79001234567",
+ "nickname": "olegpetrov",
+ "department": "Отдел разработки",
+ "title": "Старший разработчик",
+ "role": "user",
+ "suspended": false,
+ "list_tags": [
+ "Product"
+ ],
+ "custom_properties": [
+ {
+ "id": 1678,
+ "value": "Санкт-Петербург"
+ }
+ ]
+ }
+}'
```
## Ответы
@@ -9382,6 +11312,36 @@ curl "https://api.pachca.com/api/shared/v1/users/12" \
}
```
+### 400: The server could not understand the request due to invalid syntax.
+
+**Схема ответа при ошибке:**
+
+- `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
### 401: Access is unauthorized.
**Схема ответа при ошибке:**
@@ -9474,20 +11434,50 @@ curl "https://api.pachca.com/api/shared/v1/users/12" \
}
```
+### 422: Client error
+
+**Схема ответа при ошибке:**
+
+- `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
---
-# Редактирование сотрудника
+# Удаление сотрудника
-**Метод**: `PUT`
+**Метод**: `DELETE`
**Путь**: `/users/{id}`
-> **Скоуп:** `users:update`
+> **Скоуп:** `users:delete`
-Метод для редактирования сотрудника.
+Метод для удаления сотрудника.
-Для редактирования сотрудника вам необходимо знать его `id` и указать его в `URL` запроса. Все редактируемые параметры сотрудника указываются в теле запроса. Получить актуальный список идентификаторов дополнительных полей сотрудника вы можете в методе [Список дополнительных полей](GET /custom_properties).
+Для удаления сотрудника вам необходимо знать его `id` и указать его в `URL` запроса.
## Параметры
@@ -9496,176 +11486,80 @@ curl "https://api.pachca.com/api/shared/v1/users/12" \
- `id: integer, int32` (required) — Идентификатор пользователя
-## Тело запроса
-
-**Обязательно**
-
-Формат: `application/json`
-
-### Схема
+## Пример запроса
-- `user: object` (required) — Собранный объект параметров редактируемого сотрудника
- - `first_name: string` — Имя
- - `last_name: string` — Фамилия
- - `email: string` — Электронная почта
- - `phone_number: string` — Телефон
- - `nickname: string` — Имя пользователя
- - `department: string` — Департамент
- - `title: string` — Должность
- - `role: string` — Уровень доступа
- Значения: `admin` — Администратор, `user` — Сотрудник, `multi_guest` — Мульти-гость
- - `suspended: boolean` — Деактивация пользователя
- - `list_tags: array of string` — Массив тегов, привязываемых к сотруднику
- - `custom_properties: array of object` — Задаваемые дополнительные поля
- - `id: integer, int32` (required) — Идентификатор поля
- - `value: string` (required) — Устанавливаемое значение
+```bash
+curl -X DELETE "https://api.pachca.com/api/shared/v1/users/12" \
+ -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
+```
-### Пример
+## Ответы
-```json
-{
- "user": {
- "first_name": "Олег",
- "last_name": "Петров",
- "email": "olegpetrov@example.com",
- "phone_number": "+79001234567",
- "nickname": "olegpetrov",
- "department": "Отдел разработки",
- "title": "Старший разработчик",
- "role": "user",
- "suspended": false,
- "list_tags": [
- "Product"
- ],
- "custom_properties": [
- {
- "id": 1678,
- "value": "Санкт-Петербург"
- }
- ]
- }
-}
-```
+### 204: There is no content to send for this request, but the headers may be useful.
-## Пример запроса
+### 401: Access is unauthorized.
-```bash
-curl -X PUT "https://api.pachca.com/api/shared/v1/users/12" \
- -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
- -H "Content-Type: application/json" \
- -d '{
- "user": {
- "first_name": "Олег",
- "last_name": "Петров",
- "email": "olegpetrov@example.com",
- "phone_number": "+79001234567",
- "nickname": "olegpetrov",
- "department": "Отдел разработки",
- "title": "Старший разработчик",
- "role": "user",
- "suspended": false,
- "list_tags": [
- "Product"
- ],
- "custom_properties": [
- {
- "id": 1678,
- "value": "Санкт-Петербург"
- }
- ]
- }
-}'
+**Схема ответа при ошибке:**
+
+- `error: string` (required) — Код ошибки
+- `error_description: string` (required) — Описание ошибки
+
+**Пример ответа:**
+
+```json
+{
+ "error": "invalid_token",
+ "error_description": "Access token is missing"
+}
```
-## Ответы
+### 402: Client error
-### 200: The request has succeeded.
+**Схема ответа при ошибке:**
-**Схема ответа:**
+- `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
-- `data: object` (required) — Сотрудник
- - `id: integer, int32` (required) — Идентификатор пользователя
- - `first_name: string` (required) — Имя
- - `last_name: string` (required) — Фамилия
- - `nickname: string` (required) — Имя пользователя
- - `email: string` (required) — Электронная почта
- - `phone_number: string` (required) — Телефон
- - `department: string` (required) — Департамент
- - `title: string` (required) — Должность
- - `role: string` (required) — Уровень доступа
- Значения: `admin` — Администратор, `user` — Сотрудник, `multi_guest` — Мульти-гость, `guest` — Гость
- - `suspended: boolean` (required) — Деактивация пользователя
- - `invite_status: string` (required) — Статус приглашения
- Значения: `confirmed` — Принято, `sent` — Отправлено
- - `list_tags: array of string` (required) — Массив тегов, привязанных к сотруднику
- - `custom_properties: array of object` (required) — Дополнительные поля сотрудника
- - `id: integer, int32` (required) — Идентификатор поля
- - `name: string` (required) — Название поля
- - `data_type: string` (required) — Тип поля
- Значения: `string` — Строковое значение, `number` — Числовое значение, `date` — Дата, `link` — Ссылка
- - `value: string` (required) — Значение
- - `user_status: object` (required) — Статус
- - `emoji: string` (required) — Emoji символ статуса
- - `title: string` (required) — Текст статуса
- - `expires_at: date-time` (required) — Срок жизни статуса (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
- - `is_away: boolean` (required) — Режим «Нет на месте»
- - `away_message: object` (required) — Сообщение при режиме «Нет на месте». Отображается в профиле пользователя, а также при отправке ему личного сообщения или упоминании в чате.
- - `text: string` (required) — Текст сообщения
- - `bot: boolean` (required) — Является ботом
- - `sso: boolean` (required) — Использует ли пользователь SSO
- - `created_at: date-time` (required) — Дата создания (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
- - `last_activity_at: date-time` (required) — Дата последней активности пользователя (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
- - `time_zone: string` (required) — Часовой пояс пользователя
- - `image_url: string` (required) — Ссылка на скачивание аватарки пользователя
+**Пример ответа:**
+
+```json
+{
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
+}
+```
+
+### 403: Access is forbidden.
+
+**Схема ответа при ошибке:**
+
+- `error: string` (required) — Код ошибки
+- `error_description: string` (required) — Описание ошибки
**Пример ответа:**
```json
{
- "data": {
- "id": 12,
- "first_name": "Олег",
- "last_name": "Петров",
- "nickname": "",
- "email": "olegp@example.com",
- "phone_number": "",
- "department": "Продукт",
- "title": "CIO",
- "role": "admin",
- "suspended": false,
- "invite_status": "confirmed",
- "list_tags": [
- "Product",
- "Design"
- ],
- "custom_properties": [
- {
- "id": 1678,
- "name": "Город",
- "data_type": "string",
- "value": "Санкт-Петербург"
- }
- ],
- "user_status": {
- "emoji": "🎮",
- "title": "Очень занят",
- "expires_at": "2024-04-08T10:00:00.000Z",
- "is_away": false,
- "away_message": {
- "text": "Я в отпуске до 15 апреля. По срочным вопросам обращайтесь к @ivanov."
- }
- },
- "bot": false,
- "sso": false,
- "created_at": "2020-06-08T09:32:57.000Z",
- "last_activity_at": "2025-01-20T13:40:07.000Z",
- "time_zone": "Europe/Moscow",
- "image_url": "https://app.pachca.com/users/12/photo.jpg"
- }
+ "error": "invalid_token",
+ "error_description": "Access token is missing"
}
```
-### 400: The server could not understand the request due to invalid syntax.
+### 404: The server cannot find the requested resource.
**Схема ответа при ошибке:**
@@ -9695,6 +11589,56 @@ curl -X PUT "https://api.pachca.com/api/shared/v1/users/12" \
}
```
+
+---
+
+# Загрузка аватара сотрудника
+
+**Метод**: `PUT`
+
+**Путь**: `/users/{user_id}/avatar`
+
+> **Скоуп:** `user_avatar:write`
+
+Метод для загрузки или обновления аватара сотрудника. Файл передается в формате `multipart/form-data`.
+
+## Параметры
+
+### Path параметры
+
+- `user_id: integer, int32` (required) — Идентификатор пользователя
+
+
+## Тело запроса
+
+
+## Пример запроса
+
+```bash
+curl -X PUT "https://api.pachca.com/api/shared/v1/users/12/avatar" \
+ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
+ -F "image=@filename.png"
+```
+
+## Ответы
+
+### 200: The request has succeeded.
+
+**Схема ответа:**
+
+- `data: object` (required) — Данные аватара
+ - `image_url: string` (required) — URL аватара
+
+**Пример ответа:**
+
+```json
+{
+ "data": {
+ "image_url": "https://pachca-prod.s3.amazonaws.com/uploads/0001/0001/image.jpg"
+ }
+}
+```
+
### 401: Access is unauthorized.
**Схема ответа при ошибке:**
@@ -9745,15 +11689,35 @@ curl -X PUT "https://api.pachca.com/api/shared/v1/users/12" \
**Схема ответа при ошибке:**
-- `error: string` (required) — Код ошибки
-- `error_description: string` (required) — Описание ошибки
+**anyOf** - один из вариантов:
+
+- **ApiError**: Ошибка API (используется для 400, 402, 403, 404, 409, 410, 422)
+ - `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+- **OAuthError**: Ошибка OAuth авторизации (используется для 401 и 403)
+ - `error: string` (required) — Код ошибки
+ - `error_description: string` (required) — Описание ошибки
**Пример ответа:**
```json
{
- "error": "invalid_token",
- "error_description": "Access token is missing"
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
}
```
@@ -9820,29 +11784,27 @@ curl -X PUT "https://api.pachca.com/api/shared/v1/users/12" \
---
-# Удаление сотрудника
+# Удаление аватара сотрудника
**Метод**: `DELETE`
-**Путь**: `/users/{id}`
-
-> **Скоуп:** `users:delete`
+**Путь**: `/users/{user_id}/avatar`
-Метод для удаления сотрудника.
+> **Скоуп:** `user_avatar:write`
-Для удаления сотрудника вам необходимо знать его `id` и указать его в `URL` запроса.
+Метод для удаления аватара сотрудника.
## Параметры
### Path параметры
-- `id: integer, int32` (required) — Идентификатор пользователя
+- `user_id: integer, int32` (required) — Идентификатор пользователя
## Пример запроса
```bash
-curl -X DELETE "https://api.pachca.com/api/shared/v1/users/12" \
+curl -X DELETE "https://api.pachca.com/api/shared/v1/users/12/avatar" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
@@ -9900,15 +11862,35 @@ curl -X DELETE "https://api.pachca.com/api/shared/v1/users/12" \
**Схема ответа при ошибке:**
-- `error: string` (required) — Код ошибки
-- `error_description: string` (required) — Описание ошибки
+**anyOf** - один из вариантов:
+
+- **ApiError**: Ошибка API (используется для 400, 402, 403, 404, 409, 410, 422)
+ - `errors: array of object` (required) — Массив ошибок
+ - `key: string` (required) — Ключ поля с ошибкой
+ - `value: string` (required) — Значение поля, которое вызвало ошибку
+ - `message: string` (required) — Сообщение об ошибке
+ - `code: string` (required) — Код ошибки
+ Значения: `blank` — Обязательное поле (не может быть пустым), `too_long` — Слишком длинное значение (пояснения вы получите в поле message), `invalid` — Поле не соответствует правилам (пояснения вы получите в поле message), `inclusion` — Поле имеет непредусмотренное значение, `exclusion` — Поле имеет недопустимое значение, `taken` — Название для этого поля уже существует, `wrong_emoji` — Emoji статуса не может содержать значения отличные от Emoji символа, `not_found` — Объект не найден, `already_exists` — Объект уже существует (пояснения вы получите в поле message), `personal_chat` — Ошибка личного чата (пояснения вы получите в поле message), `displayed_error` — Отображаемая ошибка (пояснения вы получите в поле message), `not_authorized` — Действие запрещено, `invalid_date_range` — Выбран слишком большой диапазон дат, `invalid_webhook_url` — Некорректный URL вебхука, `rate_limit` — Достигнут лимит запросов, `licenses_limit` — Превышен лимит активных сотрудников (пояснения вы получите в поле message), `user_limit` — Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций), `unique_limit` — Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций), `general_limit` — Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций), `unhandled` — Ошибка выполнения запроса (пояснения вы получите в поле message), `trigger_not_found` — Не удалось найти идентификатор события, `trigger_expired` — Время жизни идентификатора события истекло, `required` — Обязательный параметр не передан, `in` — Недопустимое значение (не входит в список допустимых), `not_applicable` — Значение неприменимо в данном контексте (пояснения вы получите в поле message), `self_update` — Нельзя изменить свои собственные данные, `owner_protected` — Нельзя изменить данные владельца, `already_assigned` — Значение уже назначено, `forbidden` — Недостаточно прав для выполнения действия (пояснения вы получите в поле message), `permission_denied` — Доступ запрещён (недостаточно прав), `access_denied` — Доступ запрещён, `wrong_params` — Некорректные параметры запроса (пояснения вы получите в поле message), `payment_required` — Требуется оплата, `min_length` — Значение слишком короткое (пояснения вы получите в поле message), `max_length` — Значение слишком длинное (пояснения вы получите в поле message), `use_of_system_words` — Использовано зарезервированное системное слово (here, all)
+ - `payload: Record` (required) — Дополнительные данные об ошибке. Содержимое зависит от кода ошибки: `{id: number}` — при ошибке кастомного свойства (идентификатор свойства), `{record: {type: string, id: number}, query: string}` — при ошибке авторизации. В большинстве случаев `null`
+ **Структура значений Record:**
+ - Тип значения: `any`
+- **OAuthError**: Ошибка OAuth авторизации (используется для 401 и 403)
+ - `error: string` (required) — Код ошибки
+ - `error_description: string` (required) — Описание ошибки
**Пример ответа:**
```json
{
- "error": "invalid_token",
- "error_description": "Access token is missing"
+ "errors": [
+ {
+ "key": "field.name",
+ "value": "invalid_value",
+ "message": "Поле не может быть пустым",
+ "code": "blank",
+ "payload": null
+ }
+ ]
}
```
@@ -10606,9 +12588,9 @@ curl "https://api.pachca.com/api/shared/v1/group_tags?names[]=Design&names[]=Pro
- `id: integer, int32` (required) — Идентификатор тега
- `name: string` (required) — Название тега
- `users_count: integer, int32` (required) — Количество сотрудников, которые имеют этот тег
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
@@ -11324,9 +13306,9 @@ curl "https://api.pachca.com/api/shared/v1/group_tags/9111/users?limit=1" \
- `last_activity_at: date-time` (required) — Дата последней активности пользователя (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
- `time_zone: string` (required) — Часовой пояс пользователя
- `image_url: string` (required) — Ссылка на скачивание аватарки пользователя
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
@@ -11795,7 +13777,8 @@ curl "https://api.pachca.com/api/shared/v1/chats" \
### Query параметры
-- `sort[{field}]: string` (default: desc) — Составной параметр сортировки сущностей выборки
+- `sort: string` (default: id) — Поле сортировки
+- `order: string` (default: desc) — Направление сортировки
- `availability: string` (default: is_member) — Параметр, который отвечает за доступность и выборку чатов для пользователя
- `last_message_at_after: date-time` — Фильтрация по времени создания последнего сообщения. Будут возвращены те чаты, время последнего созданного сообщения в которых не раньше чем указанное (в формате YYYY-MM-DDThh:mm:ss.sssZ).
- `last_message_at_before: date-time` — Фильтрация по времени создания последнего сообщения. Будут возвращены те чаты, время последнего созданного сообщения в которых не позже чем указанное (в формате YYYY-MM-DDThh:mm:ss.sssZ).
@@ -11808,7 +13791,7 @@ curl "https://api.pachca.com/api/shared/v1/chats" \
```bash
# Для получения следующей страницы используйте cursor из meta.paginate.next_page
-curl "https://api.pachca.com/api/shared/v1/chats?sort[id]=desc&availability=is_member&last_message_at_after=2025-01-01T00:00:00.000Z&last_message_at_before=2025-02-01T00:00:00.000Z&personal=false&limit=1" \
+curl "https://api.pachca.com/api/shared/v1/chats?sort=id&order=desc&availability=is_member&last_message_at_after=2025-01-01T00:00:00.000Z&last_message_at_before=2025-02-01T00:00:00.000Z&personal=false&limit=1" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
@@ -11830,9 +13813,9 @@ curl "https://api.pachca.com/api/shared/v1/chats?sort[id]=desc&availability=is_m
- `public: boolean` (required) — Открытый доступ
- `last_message_at: date-time` (required) — Дата и время создания последнего сообщения в чате (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
- `meet_room_url: string` (required) — Ссылка на Видеочат
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
@@ -13111,9 +15094,9 @@ curl "https://api.pachca.com/api/shared/v1/chats/334/members?role=all&limit=1" \
- `last_activity_at: date-time` (required) — Дата последней активности пользователя (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
- `time_zone: string` (required) — Часовой пояс пользователя
- `image_url: string` (required) — Ссылка на скачивание аватарки пользователя
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
@@ -14390,7 +16373,8 @@ curl "https://api.pachca.com/api/shared/v1/messages" \
### Query параметры
- `chat_id: integer, int32` (required) — Идентификатор чата (беседа, канал, диалог или чат треда)
-- `sort[{field}]: string` (default: desc) — Составной параметр сортировки сущностей выборки
+- `sort: string` (default: id) — Поле сортировки
+- `order: string` (default: desc) — Направление сортировки
- `limit: integer, int32` (default: 50) — Количество возвращаемых сущностей за один запрос
- `cursor: string` — Курсор для пагинации (из `meta.paginate.next_page`)
@@ -14399,7 +16383,7 @@ curl "https://api.pachca.com/api/shared/v1/messages" \
```bash
# Для получения следующей страницы используйте cursor из meta.paginate.next_page
-curl "https://api.pachca.com/api/shared/v1/messages?chat_id=198&sort[id]=desc&limit=1" \
+curl "https://api.pachca.com/api/shared/v1/messages?chat_id=198&sort=id&order=desc&limit=1" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
@@ -14446,9 +16430,9 @@ curl "https://api.pachca.com/api/shared/v1/messages?chat_id=198&sort[id]=desc&li
- `display_name: string` (required) — Полное имя отправителя сообщения
- `changed_at: date-time` (required) — Дата и время последнего редактирования сообщения (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
- `deleted_at: date-time` (required) — Дата и время удаления сообщения (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
@@ -15603,9 +17587,9 @@ curl "https://api.pachca.com/api/shared/v1/messages/194275/read_member_ids?limit
**Схема ответа:**
- `data: array of integer` (required)
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
@@ -16186,9 +18170,9 @@ curl "https://api.pachca.com/api/shared/v1/messages/194275/reactions?limit=1" \
- `created_at: date-time` (required) — Дата и время добавления реакции (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
- `code: string` (required) — Emoji символ реакции
- `name: string` (required) — Название emoji реакции
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
@@ -17557,9 +19541,9 @@ curl "https://api.pachca.com/api/shared/v1/tasks?limit=1" \
- `data_type: string` (required) — Тип поля
Значения: `string` — Строковое значение, `number` — Числовое значение, `date` — Дата, `link` — Ссылка
- `value: string` (required) — Значение
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
@@ -19150,6 +21134,18 @@ curl "https://api.pachca.com/api/shared/v1/webhooks/events?limit=1" \
- `user_id: integer, int32` (required) — Идентификатор пользователя, который нажал кнопку
- `chat_id: integer, int32` (required) — Идентификатор чата, в котором была нажата кнопка
- `webhook_timestamp: integer, int32` (required) — Дата и время отправки вебхука (UTC+0) в формате UNIX
+ - **ViewSubmitWebhookPayload**: Структура исходящего вебхука о заполнении формы
+ - `type: string` (required) — Тип объекта
+ Значения: `view` — Для формы всегда view
+ - `event: string` (required) — Тип события
+ Значения: `submit` — Отправка формы
+ - `callback_id: string` (required) — Идентификатор обратного вызова, указанный при открытии представления
+ - `private_metadata: string` (required) — Приватные метаданные, указанные при открытии представления
+ - `user_id: integer, int32` (required) — Идентификатор пользователя, который отправил форму
+ - `data: Record` (required) — Данные заполненных полей представления. Ключ — `action_id` поля, значение — введённые данные
+ **Структура значений Record:**
+ - Тип значения: `any`
+ - `webhook_timestamp: integer, int32` (required) — Дата и время отправки вебхука (UTC+0) в формате UNIX
- **ChatMemberWebhookPayload**: Структура исходящего вебхука об участниках чата
- `type: string` (required) — Тип объекта
Значения: `chat_member` — Для участника чата всегда chat_member
@@ -19182,9 +21178,9 @@ curl "https://api.pachca.com/api/shared/v1/webhooks/events?limit=1" \
- `created_at: date-time` (required) — Дата и время создания сообщения (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
- `webhook_timestamp: integer, int32` (required) — Дата и время отправки вебхука (UTC+0) в формате UNIX
- `created_at: date-time` (required) — Дата и время создания события (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
@@ -19578,9 +21574,9 @@ curl "https://api.pachca.com/api/shared/v1/audit_events?start_time=2025-05-01T09
- Тип значения: `any`
- `ip_address: string` (required) — IP-адрес, с которого было выполнено действие
- `user_agent: string` (required) — User agent клиента
-- `meta: object` — Метаданные пагинации
- - `paginate: object` — Вспомогательная информация
- - `next_page: string` — Курсор пагинации следующей страницы
+- `meta: object` (required) — Метаданные пагинации
+ - `paginate: object` (required) — Вспомогательная информация
+ - `next_page: string` (required) — Курсор пагинации следующей страницы
**Пример ответа:**
diff --git a/apps/docs/public/llms.txt b/apps/docs/public/llms.txt
index 1e178a60..4a39c6a5 100644
--- a/apps/docs/public/llms.txt
+++ b/apps/docs/public/llms.txt
@@ -22,34 +22,41 @@ pachca guide "отправить сообщение" # CLI guide for humans
- [Быстрый старт](https://dev.pachca.com/guides/quickstart.md): Первый запрос к API Пачки за 5 минут
- [AI агенты](https://dev.pachca.com/guides/ai-agents.md): Как Пачка работает с AI-агентами и какие ресурсы доступны для интеграции
- [CLI](https://dev.pachca.com/guides/cli.md): Управление Пачкой из терминала — все API-методы одной командой
-- [Обзор](https://dev.pachca.com/guides/sdk/overview.md): Типизированные клиентские библиотеки и генератор для Pachca API
-- [TypeScript](https://dev.pachca.com/guides/sdk/typescript.md): Типизированный клиент для Pachca API на TypeScript с автодополнением и автопагинацией
-- [Python](https://dev.pachca.com/guides/sdk/python.md): Асинхронный типизированный клиент для Pachca API на Python с httpx
-- [Go](https://dev.pachca.com/guides/sdk/go.md): Типизированный клиент для Pachca API на Go с контекстами и автопагинацией
-- [Kotlin](https://dev.pachca.com/guides/sdk/kotlin.md): Типизированный клиент для Pachca API на Kotlin с корутинами и Ktor
-- [Swift](https://dev.pachca.com/guides/sdk/swift.md): Типизированный клиент для Pachca API на Swift с async/await и Codable
-- [CSharp](https://dev.pachca.com/guides/sdk/csharp.md): Типизированный клиент для Pachca API на C# с async/await и автопагинацией
+- [SDK: Обзор](https://dev.pachca.com/guides/sdk/overview.md): Типизированные клиентские библиотеки и генератор для Pachca API
+- [SDK: TypeScript](https://dev.pachca.com/guides/sdk/typescript.md): Типизированный клиент для Pachca API на TypeScript с автодополнением и автопагинацией
+- [SDK: Python](https://dev.pachca.com/guides/sdk/python.md): Асинхронный типизированный клиент для Pachca API на Python с httpx
+- [SDK: Go](https://dev.pachca.com/guides/sdk/go.md): Типизированный клиент для Pachca API на Go с контекстами и автопагинацией
+- [SDK: Kotlin](https://dev.pachca.com/guides/sdk/kotlin.md): Типизированный клиент для Pachca API на Kotlin с корутинами и Ktor
+- [SDK: Swift](https://dev.pachca.com/guides/sdk/swift.md): Типизированный клиент для Pachca API на Swift с async/await и Codable
+- [SDK: C#](https://dev.pachca.com/guides/sdk/csharp.md): Типизированный клиент для Pachca API на C# с async/await и автопагинацией
- [Сценарии](https://dev.pachca.com/guides/workflows.md): Пошаговые сценарии для типичных задач с API
- [Боты](https://dev.pachca.com/guides/bots.md): Создание, настройка и возможности ботов в Пачке
- [Входящие вебхуки](https://dev.pachca.com/guides/incoming-webhooks.md): Отправка сообщений от имени бота без использования API
- [Исходящие вебхуки](https://dev.pachca.com/guides/webhook.md): Получение уведомлений о событиях в реальном времени
- [Кнопки в сообщениях](https://dev.pachca.com/guides/buttons.md): Интерактивные кнопки в сообщениях ботов для навигации и обработки действий
-- [Обзор](https://dev.pachca.com/guides/forms/overview.md): Модальные формы ботов: поля ввода, жизненный цикл и валидация
-- [Блоки представления](https://dev.pachca.com/guides/forms/blocks.md): 10 типов блоков для построения форм в Пачке
-- [Обработка форм](https://dev.pachca.com/guides/forms/handling.md): Открытие представлений, получение результатов и валидация
+- [Формы: Обзор](https://dev.pachca.com/guides/forms/overview.md): Модальные формы ботов: поля ввода, жизненный цикл и валидация
+- [Формы: Блоки представления](https://dev.pachca.com/guides/forms/blocks.md): 10 типов блоков для построения форм в Пачке
+- [Формы: Обработка форм](https://dev.pachca.com/guides/forms/handling.md): Открытие представлений, получение результатов и валидация
- [Разворачивание ссылок](https://dev.pachca.com/guides/link-previews.md): Создание превью ссылок из внутренних сервисов прямо в чате
- [Экспорт сообщений](https://dev.pachca.com/guides/export.md): Экспорт сообщений из чатов: запрос, скачивание и структура архива
- [DLP-система](https://dev.pachca.com/guides/dlp.md): Создание правил DLP: условия, контексты и действия
- [Журнал аудита событий](https://dev.pachca.com/guides/audit-events.md): Журнал аудита: типы событий, фильтрация и примеры запросов
-- [n8n](https://dev.pachca.com/guides/n8n.md): Автоматизации в Пачке через платформу n8n без программирования
+- [n8n: Обзор](https://dev.pachca.com/guides/n8n/overview.md): Автоматизации в Пачке через платформу n8n — 18 ресурсов, триггер, AI-агент
+- [n8n: Начало работы](https://dev.pachca.com/guides/n8n/setup.md): Установка n8n, расширения Пачки, настройка Credentials и первый workflow
+- [n8n: Ресурсы и операции](https://dev.pachca.com/guides/n8n/resources.md): Все 18 ресурсов и более 60 операций расширения Пачки для n8n
+- [n8n: Триггер](https://dev.pachca.com/guides/n8n/trigger.md): Pachca Trigger: 16 типов событий, авторегистрация вебхука, проверка подписи
+- [n8n: Примеры workflow](https://dev.pachca.com/guides/n8n/workflows.md): Готовые сценарии автоматизации Пачки в n8n: приветствие, пересылка, задачи, согласование, мониторинг, заявки на отпуск
+- [n8n: Продвинутые функции](https://dev.pachca.com/guides/n8n/advanced.md): Экспорт сообщений, загрузка файлов, кнопки, формы, AI-агент, разворачивание ссылок, журнал безопасности
+- [n8n: Устранение ошибок](https://dev.pachca.com/guides/n8n/troubleshooting.md): Частые ошибки при работе с Пачкой в n8n: неверный токен, 403, 429, вебхук не приходит
+- [n8n: Миграция с v1](https://dev.pachca.com/guides/n8n/migration.md): Обновление с v1 на v2: таблицы переименований, новые ресурсы, полная обратная совместимость
- [Albato](https://dev.pachca.com/guides/albato.md): Интеграция Пачки с Albato — подключение сотен сервисов без кода
- [Последние обновления](https://dev.pachca.com/updates.md): История изменений и новые возможности API
-- [Авторизация](https://dev.pachca.com/api/authorization.md): Типы токенов, скоупы и настройка доступа к API
-- [Запросы и ответы](https://dev.pachca.com/api/requests-responses.md): Формат запросов и ответов, тестирование API
-- [Пагинация](https://dev.pachca.com/api/pagination.md): Cursor-based pagination в API Пачки
-- [Загрузка файлов](https://dev.pachca.com/api/file-uploads.md): Трёхшаговый процесс загрузки файлов через presigned URL
-- [Ошибки и лимиты](https://dev.pachca.com/api/errors.md): Коды ошибок HTTP и rate limits
-- [Модели](https://dev.pachca.com/api/models.md): Справочник всех моделей данных Pachca API — объекты, возвращаемые в ответах методов.
+- [Основы API: Авторизация](https://dev.pachca.com/api/authorization.md): Типы токенов, скоупы и настройка доступа к API
+- [Основы API: Запросы и ответы](https://dev.pachca.com/api/requests-responses.md): Формат запросов и ответов, тестирование API
+- [Основы API: Пагинация](https://dev.pachca.com/api/pagination.md): Cursor-based pagination в API Пачки
+- [Основы API: Загрузка файлов](https://dev.pachca.com/api/file-uploads.md): Трёхшаговый процесс загрузки файлов через presigned URL
+- [Основы API: Ошибки и лимиты](https://dev.pachca.com/api/errors.md): Коды ошибок HTTP и rate limits
+- [Основы API: Модели](https://dev.pachca.com/api/models.md): Справочник всех моделей данных Pachca API — объекты, возвращаемые в ответах методов.
## Common
- [Экспорт сообщений](https://dev.pachca.com/api/common/request-export.md): POST /chats/exports
@@ -61,6 +68,8 @@ pachca guide "отправить сообщение" # CLI guide for humans
## Profile
- [Информация о токене](https://dev.pachca.com/api/profile/get-info.md): GET /oauth/token/info
- [Информация о профиле](https://dev.pachca.com/api/profile/get.md): GET /profile
+- [Загрузка аватара](https://dev.pachca.com/api/profile/update-avatar.md): PUT /profile/avatar
+- [Удаление аватара](https://dev.pachca.com/api/profile/delete-avatar.md): DELETE /profile/avatar
- [Текущий статус](https://dev.pachca.com/api/profile/get-status.md): GET /profile/status
- [Новый статус](https://dev.pachca.com/api/profile/update-status.md): PUT /profile/status
- [Удаление статуса](https://dev.pachca.com/api/profile/delete-status.md): DELETE /profile/status
@@ -71,6 +80,8 @@ pachca guide "отправить сообщение" # CLI guide for humans
- [Информация о сотруднике](https://dev.pachca.com/api/users/get.md): GET /users/{id}
- [Редактирование сотрудника](https://dev.pachca.com/api/users/update.md): PUT /users/{id}
- [Удаление сотрудника](https://dev.pachca.com/api/users/delete.md): DELETE /users/{id}
+- [Загрузка аватара сотрудника](https://dev.pachca.com/api/users/update-avatar.md): PUT /users/{user_id}/avatar
+- [Удаление аватара сотрудника](https://dev.pachca.com/api/users/remove-avatar.md): DELETE /users/{user_id}/avatar
- [Статус сотрудника](https://dev.pachca.com/api/users/get-status.md): GET /users/{user_id}/status
- [Новый статус сотрудника](https://dev.pachca.com/api/users/update-status.md): PUT /users/{user_id}/status
- [Удаление статуса сотрудника](https://dev.pachca.com/api/users/remove-status.md): DELETE /users/{user_id}/status
diff --git a/apps/docs/public/pachca.postman_collection.json b/apps/docs/public/pachca.postman_collection.json
index 848e2692..53d59b85 100644
--- a/apps/docs/public/pachca.postman_collection.json
+++ b/apps/docs/public/pachca.postman_collection.json
@@ -183,6 +183,54 @@
}
}
},
+ {
+ "name": "Загрузка аватара",
+ "request": {
+ "method": "PUT",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/profile/avatar",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "profile",
+ "avatar"
+ ]
+ },
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"image\": \"0101010101010101\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ }
+ }
+ },
+ {
+ "name": "Удаление аватара",
+ "request": {
+ "method": "DELETE",
+ "header": [],
+ "url": {
+ "raw": "{{baseUrl}}/profile/avatar",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "profile",
+ "avatar"
+ ]
+ }
+ }
+ },
{
"name": "Текущий статус",
"request": {
@@ -382,6 +430,68 @@
}
}
},
+ {
+ "name": "Загрузка аватара сотрудника",
+ "request": {
+ "method": "PUT",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/users/:user_id/avatar",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "users",
+ ":user_id",
+ "avatar"
+ ],
+ "variable": [
+ {
+ "key": "user_id",
+ "value": ""
+ }
+ ]
+ },
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"image\": \"0101010101010101\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ }
+ }
+ },
+ {
+ "name": "Удаление аватара сотрудника",
+ "request": {
+ "method": "DELETE",
+ "header": [],
+ "url": {
+ "raw": "{{baseUrl}}/users/:user_id/avatar",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "users",
+ ":user_id",
+ "avatar"
+ ],
+ "variable": [
+ {
+ "key": "user_id",
+ "value": ""
+ }
+ ]
+ }
+ }
+ },
{
"name": "Статус сотрудника",
"request": {
diff --git a/apps/docs/public/skill.md b/apps/docs/public/skill.md
index 37f40fa2..9a08ab1e 100644
--- a/apps/docs/public/skill.md
+++ b/apps/docs/public/skill.md
@@ -61,6 +61,8 @@ Tokens are long-lived and do not expire. They can be reset by the admin/owner in
### Profile
- `GET /oauth/token/info` — Get token info
- `GET /profile` — Get profile
+- `PUT /profile/avatar` — Update profile avatar
+- `DELETE /profile/avatar` — Delete profile avatar
- `GET /profile/status` — Get status
- `PUT /profile/status` — Update status
- `DELETE /profile/status` — Delete status
@@ -71,6 +73,8 @@ Tokens are long-lived and do not expire. They can be reset by the admin/owner in
- `GET /users/{id}` — Get user
- `PUT /users/{id}` — Update user
- `DELETE /users/{id}` — Delete user
+- `PUT /users/{user_id}/avatar` — Update user avatar
+- `DELETE /users/{user_id}/avatar` — Delete user avatar
- `GET /users/{user_id}/status` — Get user status
- `PUT /users/{user_id}/status` — Update user status
- `DELETE /users/{user_id}/status` — Delete user status
@@ -218,34 +222,41 @@ Detailed documentation on specific topics is available at:
- [Быстрый старт](https://dev.pachca.com/guides/quickstart) — Первый запрос к API Пачки за 5 минут
- [AI агенты](https://dev.pachca.com/guides/ai-agents) — Как Пачка работает с AI-агентами и какие ресурсы доступны для интеграции
- [CLI](https://dev.pachca.com/guides/cli) — Управление Пачкой из терминала — все API-методы одной командой
-- [Обзор](https://dev.pachca.com/guides/sdk/overview) — Типизированные клиентские библиотеки и генератор для Pachca API
-- [TypeScript](https://dev.pachca.com/guides/sdk/typescript) — Типизированный клиент для Pachca API на TypeScript с автодополнением и автопагинацией
-- [Python](https://dev.pachca.com/guides/sdk/python) — Асинхронный типизированный клиент для Pachca API на Python с httpx
-- [Go](https://dev.pachca.com/guides/sdk/go) — Типизированный клиент для Pachca API на Go с контекстами и автопагинацией
-- [Kotlin](https://dev.pachca.com/guides/sdk/kotlin) — Типизированный клиент для Pachca API на Kotlin с корутинами и Ktor
-- [Swift](https://dev.pachca.com/guides/sdk/swift) — Типизированный клиент для Pachca API на Swift с async/await и Codable
-- [CSharp](https://dev.pachca.com/guides/sdk/csharp) — Типизированный клиент для Pachca API на C# с async/await и автопагинацией
+- [SDK: Обзор](https://dev.pachca.com/guides/sdk/overview) — Типизированные клиентские библиотеки и генератор для Pachca API
+- [SDK: TypeScript](https://dev.pachca.com/guides/sdk/typescript) — Типизированный клиент для Pachca API на TypeScript с автодополнением и автопагинацией
+- [SDK: Python](https://dev.pachca.com/guides/sdk/python) — Асинхронный типизированный клиент для Pachca API на Python с httpx
+- [SDK: Go](https://dev.pachca.com/guides/sdk/go) — Типизированный клиент для Pachca API на Go с контекстами и автопагинацией
+- [SDK: Kotlin](https://dev.pachca.com/guides/sdk/kotlin) — Типизированный клиент для Pachca API на Kotlin с корутинами и Ktor
+- [SDK: Swift](https://dev.pachca.com/guides/sdk/swift) — Типизированный клиент для Pachca API на Swift с async/await и Codable
+- [SDK: C#](https://dev.pachca.com/guides/sdk/csharp) — Типизированный клиент для Pachca API на C# с async/await и автопагинацией
- [Сценарии](https://dev.pachca.com/guides/workflows) — Пошаговые сценарии для типичных задач с API
- [Боты](https://dev.pachca.com/guides/bots) — Создание, настройка и возможности ботов в Пачке
- [Входящие вебхуки](https://dev.pachca.com/guides/incoming-webhooks) — Отправка сообщений от имени бота без использования API
- [Исходящие вебхуки](https://dev.pachca.com/guides/webhook) — Получение уведомлений о событиях в реальном времени
- [Кнопки в сообщениях](https://dev.pachca.com/guides/buttons) — Интерактивные кнопки в сообщениях ботов для навигации и обработки действий
-- [Обзор](https://dev.pachca.com/guides/forms/overview) — Модальные формы ботов: поля ввода, жизненный цикл и валидация
-- [Блоки представления](https://dev.pachca.com/guides/forms/blocks) — 10 типов блоков для построения форм в Пачке
-- [Обработка форм](https://dev.pachca.com/guides/forms/handling) — Открытие представлений, получение результатов и валидация
+- [Формы: Обзор](https://dev.pachca.com/guides/forms/overview) — Модальные формы ботов: поля ввода, жизненный цикл и валидация
+- [Формы: Блоки представления](https://dev.pachca.com/guides/forms/blocks) — 10 типов блоков для построения форм в Пачке
+- [Формы: Обработка форм](https://dev.pachca.com/guides/forms/handling) — Открытие представлений, получение результатов и валидация
- [Разворачивание ссылок](https://dev.pachca.com/guides/link-previews) — Создание превью ссылок из внутренних сервисов прямо в чате
- [Экспорт сообщений](https://dev.pachca.com/guides/export) — Экспорт сообщений из чатов: запрос, скачивание и структура архива
- [DLP-система](https://dev.pachca.com/guides/dlp) — Создание правил DLP: условия, контексты и действия
- [Журнал аудита событий](https://dev.pachca.com/guides/audit-events) — Журнал аудита: типы событий, фильтрация и примеры запросов
-- [n8n](https://dev.pachca.com/guides/n8n) — Автоматизации в Пачке через платформу n8n без программирования
+- [n8n: Обзор](https://dev.pachca.com/guides/n8n/overview) — Автоматизации в Пачке через платформу n8n — 18 ресурсов, триггер, AI-агент
+- [n8n: Начало работы](https://dev.pachca.com/guides/n8n/setup) — Установка n8n, расширения Пачки, настройка Credentials и первый workflow
+- [n8n: Ресурсы и операции](https://dev.pachca.com/guides/n8n/resources) — Все 18 ресурсов и более 60 операций расширения Пачки для n8n
+- [n8n: Триггер](https://dev.pachca.com/guides/n8n/trigger) — Pachca Trigger: 16 типов событий, авторегистрация вебхука, проверка подписи
+- [n8n: Примеры workflow](https://dev.pachca.com/guides/n8n/workflows) — Готовые сценарии автоматизации Пачки в n8n: приветствие, пересылка, задачи, согласование, мониторинг, заявки на отпуск
+- [n8n: Продвинутые функции](https://dev.pachca.com/guides/n8n/advanced) — Экспорт сообщений, загрузка файлов, кнопки, формы, AI-агент, разворачивание ссылок, журнал безопасности
+- [n8n: Устранение ошибок](https://dev.pachca.com/guides/n8n/troubleshooting) — Частые ошибки при работе с Пачкой в n8n: неверный токен, 403, 429, вебхук не приходит
+- [n8n: Миграция с v1](https://dev.pachca.com/guides/n8n/migration) — Обновление с v1 на v2: таблицы переименований, новые ресурсы, полная обратная совместимость
- [Albato](https://dev.pachca.com/guides/albato) — Интеграция Пачки с Albato — подключение сотен сервисов без кода
- [Последние обновления](https://dev.pachca.com/updates) — История изменений и новые возможности API
-- [Авторизация](https://dev.pachca.com/api/authorization) — Типы токенов, скоупы и настройка доступа к API
-- [Запросы и ответы](https://dev.pachca.com/api/requests-responses) — Формат запросов и ответов, тестирование API
-- [Пагинация](https://dev.pachca.com/api/pagination) — Cursor-based pagination в API Пачки
-- [Загрузка файлов](https://dev.pachca.com/api/file-uploads) — Трёхшаговый процесс загрузки файлов через presigned URL
-- [Ошибки и лимиты](https://dev.pachca.com/api/errors) — Коды ошибок HTTP и rate limits
-- [Модели](https://dev.pachca.com/api/models) — Справочник всех моделей данных Pachca API — объекты, возвращаемые в ответах методов.
+- [Основы API: Авторизация](https://dev.pachca.com/api/authorization) — Типы токенов, скоупы и настройка доступа к API
+- [Основы API: Запросы и ответы](https://dev.pachca.com/api/requests-responses) — Формат запросов и ответов, тестирование API
+- [Основы API: Пагинация](https://dev.pachca.com/api/pagination) — Cursor-based pagination в API Пачки
+- [Основы API: Загрузка файлов](https://dev.pachca.com/api/file-uploads) — Трёхшаговый процесс загрузки файлов через presigned URL
+- [Основы API: Ошибки и лимиты](https://dev.pachca.com/api/errors) — Коды ошибок HTTP и rate limits
+- [Основы API: Модели](https://dev.pachca.com/api/models) — Справочник всех моделей данных Pachca API — объекты, возвращаемые в ответах методов.
## Modular Skills
diff --git a/apps/docs/public/updates.md b/apps/docs/public/updates.md
index 86a1d830..5226d6c2 100644
--- a/apps/docs/public/updates.md
+++ b/apps/docs/public/updates.md
@@ -4,11 +4,37 @@
> Автоматически отслеживайте обновления: подпишитесь на [RSS-ленту](/feed.xml) или используйте [markdown-версию этой страницы](/updates.md) для интеграции с инструментами и AI-агентами.
+
+
+## Аватары, сортировка и n8n Node v2
+
+Были добавлены новые методы для управления аватарами:
+
+- [Загрузка аватара](PUT /profile/avatar)
+- [Удаление аватара](DELETE /profile/avatar)
+- [Загрузка аватара сотрудника](PUT /users/{user_id}/avatar)
+- [Удаление аватара сотрудника](DELETE /users/{user_id}/avatar)
+
+С помощью этих методов вы можете загружать и удалять аватары для своего профиля и для сотрудников вашей компании.
+
+В методах [Список чатов](GET /chats) и [Список сообщений чата](GET /messages) добавлен новый формат параметров сортировки: `sort` (поле сортировки) и `order` (направление: `asc` или `desc`).
+
+Расширение [n8n](/guides/n8n/overview) для Пачки обновлено до версии 2.0.0. Нода автоматически генерируется из OpenAPI-спецификации и всегда синхронизирована с актуальным API.
+
+- **18 ресурсов и 60+ операций** — полное покрытие API, включая задачи, поиск, экспорт чатов и журнал безопасности
+- **[Pachca Trigger](/guides/n8n/trigger)** — webhook-нода с авторегистрацией вебхука и 16 типами событий
+- **Курсорная автопагинация** — Return All / Limit вместо ручного per/page
+- **[Simplify](/guides/n8n/resources#simplify)** — переключатель для получения только ключевых полей
+- **AI Tool Use** — использование узлов как инструментов AI Agent
+- **Полная [обратная совместимость](/guides/n8n/migration)** — все существующие workflow на v1 продолжают работать без изменений
+
+В [CLI](/guides/cli) добавлены команды для управления аватарами и обновлены параметры сортировки (`--sort` и `--order`). Все 6 SDK обновлены: [TypeScript](/guides/sdk/typescript), [Python](/guides/sdk/python), [Go](/guides/sdk/go), [Kotlin](/guides/sdk/kotlin), [Swift](/guides/sdk/swift) и [C#](/guides/sdk/csharp).
+
## C# SDK
-Добавлен официальный [C# SDK](/guides/sdk/csharp) для .NET 8+. Полная поддержка async/await, CancellationToken, автопагинация через `*AllAsync()` методы и автоматические повторы при `429`. Установка: `dotnet add package Pachca.Sdk`.
+Добавлен официальный [C# SDK](/guides/sdk/csharp) для .NET 8+. Типизированный клиент с поддержкой async/await, автопагинацией и автоматическими повторными запросами при ошибках сервера. Все примеры кода в документации API теперь доступны на C#.
@@ -91,7 +117,7 @@
- [Кнопки](/guides/buttons) — интерактивные кнопки в сообщениях
- [Входящие вебхуки](/guides/incoming-webhooks) — отправка сообщений через URL
- [Разворачивание ссылок](/guides/link-previews) — unfurl-боты для предпросмотра ссылок
-- [Albato](/guides/albato) и [n8n](/guides/n8n) — интеграции с no-code платформами
+- [Albato](/guides/albato) и [n8n](/guides/n8n/overview) — интеграции с no-code платформами
Документация реструктурирована: [Быстрый старт](/guides/quickstart), [Пагинация](/api/pagination), [Загрузка файлов](/api/file-uploads), [Запросы и ответы](/api/requests-responses) вынесены в отдельные страницы, раздел форм разбит на три страницы.
diff --git a/apps/docs/public/workflows/n8n/approval-handler.json b/apps/docs/public/workflows/n8n/approval-handler.json
new file mode 100644
index 00000000..d4c3ba4f
--- /dev/null
+++ b/apps/docs/public/workflows/n8n/approval-handler.json
@@ -0,0 +1,88 @@
+{
+ "name": "Обработка согласования",
+ "nodes": [
+ {
+ "parameters": {
+ "events": ["button_pressed"]
+ },
+ "name": "Pachca Trigger",
+ "type": "n8n-nodes-pachca.pachcaTrigger",
+ "typeVersion": 2,
+ "position": [0, 0]
+ },
+ {
+ "parameters": {
+ "mode": "rules",
+ "rules": {
+ "values": [
+ {
+ "conditions": {
+ "conditions": [
+ {
+ "leftValue": "={{ $json.data }}",
+ "rightValue": "approve",
+ "operator": { "type": "string", "operation": "equals" }
+ }
+ ]
+ },
+ "renameOutput": true,
+ "outputKey": "Одобрено"
+ },
+ {
+ "conditions": {
+ "conditions": [
+ {
+ "leftValue": "={{ $json.data }}",
+ "rightValue": "reject",
+ "operator": { "type": "string", "operation": "equals" }
+ }
+ ]
+ },
+ "renameOutput": true,
+ "outputKey": "Отклонено"
+ }
+ ]
+ }
+ },
+ "name": "Switch",
+ "type": "n8n-nodes-base.switch",
+ "typeVersion": 3.2,
+ "position": [220, 0]
+ },
+ {
+ "parameters": {
+ "resource": "message",
+ "operation": "update",
+ "messageId": "={{ $('Pachca Trigger').item.json.message_id }}",
+ "content": "Запрос **одобрен**"
+ },
+ "name": "Одобрено",
+ "type": "n8n-nodes-pachca.pachca",
+ "typeVersion": 2,
+ "position": [440, -100]
+ },
+ {
+ "parameters": {
+ "resource": "message",
+ "operation": "update",
+ "messageId": "={{ $('Pachca Trigger').item.json.message_id }}",
+ "content": "Запрос **отклонён**"
+ },
+ "name": "Отклонено",
+ "type": "n8n-nodes-pachca.pachca",
+ "typeVersion": 2,
+ "position": [440, 100]
+ }
+ ],
+ "connections": {
+ "Pachca Trigger": { "main": [[{ "node": "Switch", "type": "main", "index": 0 }]] },
+ "Switch": {
+ "main": [
+ [{ "node": "Одобрено", "type": "main", "index": 0 }],
+ [{ "node": "Отклонено", "type": "main", "index": 0 }]
+ ]
+ }
+ },
+ "settings": { "executionOrder": "v1" },
+ "pinData": {}
+}
diff --git a/apps/docs/public/workflows/n8n/approval.json b/apps/docs/public/workflows/n8n/approval.json
new file mode 100644
index 00000000..d06762de
--- /dev/null
+++ b/apps/docs/public/workflows/n8n/approval.json
@@ -0,0 +1,38 @@
+{
+ "name": "Согласование с кнопками",
+ "nodes": [
+ {
+ "parameters": {
+ "httpMethod": "POST",
+ "path": "approval",
+ "responseMode": "onReceived",
+ "responseData": "allEntries"
+ },
+ "name": "Webhook",
+ "type": "n8n-nodes-base.webhook",
+ "typeVersion": 2,
+ "position": [0, 0],
+ "webhookId": "approval-webhook"
+ },
+ {
+ "parameters": {
+ "resource": "message",
+ "operation": "create",
+ "entityId": "CHAT_ID",
+ "content": "Запрос на согласование:\n\n{{ $json.body.text || 'Новый запрос' }}",
+ "additionalFields": {
+ "buttons": "[[{\"text\": \"Согласовать\", \"data\": \"approve\"}, {\"text\": \"Отклонить\", \"data\": \"reject\"}]]"
+ }
+ },
+ "name": "Отправить с кнопками",
+ "type": "n8n-nodes-pachca.pachca",
+ "typeVersion": 2,
+ "position": [220, 0]
+ }
+ ],
+ "connections": {
+ "Webhook": { "main": [[{ "node": "Отправить с кнопками", "type": "main", "index": 0 }]] }
+ },
+ "settings": { "executionOrder": "v1" },
+ "pinData": {}
+}
diff --git a/apps/docs/public/workflows/n8n/forward.json b/apps/docs/public/workflows/n8n/forward.json
new file mode 100644
index 00000000..2e5df90f
--- /dev/null
+++ b/apps/docs/public/workflows/n8n/forward.json
@@ -0,0 +1,52 @@
+{
+ "name": "Пересылка сообщений",
+ "nodes": [
+ {
+ "parameters": {
+ "events": ["new_message"]
+ },
+ "name": "Pachca Trigger",
+ "type": "n8n-nodes-pachca.pachcaTrigger",
+ "typeVersion": 2,
+ "position": [0, 0]
+ },
+ {
+ "parameters": {
+ "conditions": {
+ "options": { "caseSensitive": false, "leftValue": "", "typeValidation": "strict" },
+ "conditions": [
+ {
+ "id": "1",
+ "leftValue": "={{ $json.chat_id }}",
+ "rightValue": "CHAT_ID_ИСТОЧНИКА",
+ "operator": { "type": "number", "operation": "equals" }
+ }
+ ],
+ "combinator": "and"
+ }
+ },
+ "name": "IF из нужного чата",
+ "type": "n8n-nodes-base.if",
+ "typeVersion": 2.2,
+ "position": [220, 0]
+ },
+ {
+ "parameters": {
+ "resource": "message",
+ "operation": "create",
+ "entityId": "CHAT_ID_ЦЕЛЕВОГО",
+ "content": "**Пересылка от {{ $('Pachca Trigger').item.json.user_id }}:**\n\n{{ $('Pachca Trigger').item.json.content }}"
+ },
+ "name": "Переслать",
+ "type": "n8n-nodes-pachca.pachca",
+ "typeVersion": 2,
+ "position": [440, 0]
+ }
+ ],
+ "connections": {
+ "Pachca Trigger": { "main": [[{ "node": "IF из нужного чата", "type": "main", "index": 0 }]] },
+ "IF из нужного чата": { "main": [[{ "node": "Переслать", "type": "main", "index": 0 }]] }
+ },
+ "settings": { "executionOrder": "v1" },
+ "pinData": {}
+}
diff --git a/apps/docs/public/workflows/n8n/monitoring.json b/apps/docs/public/workflows/n8n/monitoring.json
new file mode 100644
index 00000000..bb4306ba
--- /dev/null
+++ b/apps/docs/public/workflows/n8n/monitoring.json
@@ -0,0 +1,66 @@
+{
+ "name": "Мониторинг + алерт",
+ "nodes": [
+ {
+ "parameters": {
+ "rule": {
+ "interval": [{ "field": "minutes", "minutesInterval": 5 }]
+ }
+ },
+ "name": "Schedule Trigger",
+ "type": "n8n-nodes-base.scheduleTrigger",
+ "typeVersion": 1.2,
+ "position": [0, 0]
+ },
+ {
+ "parameters": {
+ "method": "GET",
+ "url": "https://YOUR_SERVICE/health",
+ "options": { "timeout": 10000 }
+ },
+ "name": "HTTP Request",
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.2,
+ "position": [220, 0]
+ },
+ {
+ "parameters": {
+ "conditions": {
+ "options": { "caseSensitive": false, "leftValue": "", "typeValidation": "strict" },
+ "conditions": [
+ {
+ "id": "1",
+ "leftValue": "={{ $response.statusCode }}",
+ "rightValue": "200",
+ "operator": { "type": "number", "operation": "notEquals" }
+ }
+ ],
+ "combinator": "and"
+ }
+ },
+ "name": "IF не 200",
+ "type": "n8n-nodes-base.if",
+ "typeVersion": 2.2,
+ "position": [440, 0]
+ },
+ {
+ "parameters": {
+ "resource": "message",
+ "operation": "create",
+ "entityId": "CHAT_ID_МОНИТОРИНГА",
+ "content": "**Алерт:** сервис недоступен!\n\nСтатус: {{ $('HTTP Request').item.json.statusCode }}\nВремя: {{ $now.format('HH:mm:ss') }}"
+ },
+ "name": "Алерт в Пачку",
+ "type": "n8n-nodes-pachca.pachca",
+ "typeVersion": 2,
+ "position": [660, 0]
+ }
+ ],
+ "connections": {
+ "Schedule Trigger": { "main": [[{ "node": "HTTP Request", "type": "main", "index": 0 }]] },
+ "HTTP Request": { "main": [[{ "node": "IF не 200", "type": "main", "index": 0 }]] },
+ "IF не 200": { "main": [[{ "node": "Алерт в Пачку", "type": "main", "index": 0 }]] }
+ },
+ "settings": { "executionOrder": "v1" },
+ "pinData": {}
+}
diff --git a/apps/docs/public/workflows/n8n/reminder.json b/apps/docs/public/workflows/n8n/reminder.json
new file mode 100644
index 00000000..066fb958
--- /dev/null
+++ b/apps/docs/public/workflows/n8n/reminder.json
@@ -0,0 +1,66 @@
+{
+ "name": "Напоминание о задачах",
+ "nodes": [
+ {
+ "parameters": {
+ "rule": {
+ "interval": [{ "field": "cronExpression", "expression": "0 10 * * 1-5" }]
+ }
+ },
+ "name": "Schedule Trigger",
+ "type": "n8n-nodes-base.scheduleTrigger",
+ "typeVersion": 1.2,
+ "position": [0, 0]
+ },
+ {
+ "parameters": {
+ "resource": "task",
+ "operation": "getMany",
+ "returnAll": true
+ },
+ "name": "Получить задачи",
+ "type": "n8n-nodes-pachca.pachca",
+ "typeVersion": 2,
+ "position": [220, 0]
+ },
+ {
+ "parameters": {
+ "conditions": {
+ "options": { "caseSensitive": false, "leftValue": "", "typeValidation": "strict" },
+ "conditions": [
+ {
+ "id": "1",
+ "leftValue": "={{ $json.due_at }}",
+ "rightValue": "={{ $now.toISO() }}",
+ "operator": { "type": "dateTime", "operation": "before" }
+ }
+ ],
+ "combinator": "and"
+ }
+ },
+ "name": "IF просрочена",
+ "type": "n8n-nodes-base.if",
+ "typeVersion": 2.2,
+ "position": [440, 0]
+ },
+ {
+ "parameters": {
+ "resource": "message",
+ "operation": "create",
+ "entityId": "CHAT_ID",
+ "content": "Просроченная задача: **{{ $json.content }}**\nДедлайн: {{ $json.due_at }}"
+ },
+ "name": "Уведомление",
+ "type": "n8n-nodes-pachca.pachca",
+ "typeVersion": 2,
+ "position": [660, 0]
+ }
+ ],
+ "connections": {
+ "Schedule Trigger": { "main": [[{ "node": "Получить задачи", "type": "main", "index": 0 }]] },
+ "Получить задачи": { "main": [[{ "node": "IF просрочена", "type": "main", "index": 0 }]] },
+ "IF просрочена": { "main": [[{ "node": "Уведомление", "type": "main", "index": 0 }]] }
+ },
+ "settings": { "executionOrder": "v1" },
+ "pinData": {}
+}
diff --git a/apps/docs/public/workflows/n8n/vacation-handler.json b/apps/docs/public/workflows/n8n/vacation-handler.json
new file mode 100644
index 00000000..1762832b
--- /dev/null
+++ b/apps/docs/public/workflows/n8n/vacation-handler.json
@@ -0,0 +1,71 @@
+{
+ "name": "Обработка заявки на отпуск",
+ "nodes": [
+ {
+ "parameters": {
+ "events": ["button_pressed"]
+ },
+ "name": "Button Pressed",
+ "type": "n8n-nodes-pachca.pachcaTrigger",
+ "typeVersion": 2,
+ "position": [0, 0]
+ },
+ {
+ "parameters": {
+ "resource": "form",
+ "operation": "create",
+ "triggerId": "={{ $json.trigger_id }}",
+ "title": "Заявка на отпуск",
+ "blocks": "[{\"type\":\"input\",\"element\":{\"type\":\"datepicker\",\"action_id\":\"date_from\"},\"label\":{\"text\":\"Дата начала\"}},{\"type\":\"input\",\"element\":{\"type\":\"datepicker\",\"action_id\":\"date_to\"},\"label\":{\"text\":\"Дата окончания\"}},{\"type\":\"input\",\"element\":{\"type\":\"plain_text_input\",\"multiline\":true,\"action_id\":\"comment\"},\"label\":{\"text\":\"Комментарий\"},\"optional\":true}]"
+ },
+ "name": "Форма отпуска",
+ "type": "n8n-nodes-pachca.pachca",
+ "typeVersion": 2,
+ "position": [260, 0]
+ },
+ {
+ "parameters": {
+ "events": ["form_submitted"]
+ },
+ "name": "Form Submitted",
+ "type": "n8n-nodes-pachca.pachcaTrigger",
+ "typeVersion": 2,
+ "position": [520, 0]
+ },
+ {
+ "parameters": {
+ "resource": "thread",
+ "operation": "create",
+ "messageId": "={{ $(\"Form Submitted\").item.json.message_id }}",
+ "content": "**Заявка на отпуск**\n\n**С:** {{ $(\"Form Submitted\").item.json.values.date_from }}\n**По:** {{ $(\"Form Submitted\").item.json.values.date_to }}\n**Комментарий:** {{ $(\"Form Submitted\").item.json.values.comment || '—' }}"
+ },
+ "name": "Тред с заявкой",
+ "type": "n8n-nodes-pachca.pachca",
+ "typeVersion": 2,
+ "position": [780, 0]
+ },
+ {
+ "parameters": {
+ "resource": "message",
+ "operation": "create",
+ "entityId": "CHAT_ID_HR",
+ "content": "Новая заявка на отпуск. Примите решение:",
+ "additionalFields": {
+ "buttons": "[[{\"text\": \"Согласовать\", \"data\": \"approve\"}, {\"text\": \"Отклонить\", \"data\": \"reject\"}]]"
+ }
+ },
+ "name": "Кнопки решения",
+ "type": "n8n-nodes-pachca.pachca",
+ "typeVersion": 2,
+ "position": [1040, 0]
+ }
+ ],
+ "connections": {
+ "Button Pressed": { "main": [[{ "node": "Форма отпуска", "type": "main", "index": 0 }]] },
+ "Форма отпуска": { "main": [[{ "node": "Form Submitted", "type": "main", "index": 0 }]] },
+ "Form Submitted": { "main": [[{ "node": "Тред с заявкой", "type": "main", "index": 0 }]] },
+ "Тред с заявкой": { "main": [[{ "node": "Кнопки решения", "type": "main", "index": 0 }]] }
+ },
+ "settings": { "executionOrder": "v1" },
+ "pinData": {}
+}
diff --git a/apps/docs/public/workflows/n8n/vacation.json b/apps/docs/public/workflows/n8n/vacation.json
new file mode 100644
index 00000000..95139550
--- /dev/null
+++ b/apps/docs/public/workflows/n8n/vacation.json
@@ -0,0 +1,55 @@
+{
+ "name": "Заявка на отпуск",
+ "nodes": [
+ {
+ "parameters": {
+ "events": ["new_message"]
+ },
+ "name": "Pachca Trigger",
+ "type": "n8n-nodes-pachca.pachcaTrigger",
+ "typeVersion": 2,
+ "position": [0, 0]
+ },
+ {
+ "parameters": {
+ "conditions": {
+ "options": { "caseSensitive": false, "leftValue": "", "typeValidation": "strict" },
+ "conditions": [
+ {
+ "id": "1",
+ "leftValue": "={{ $json.content }}",
+ "rightValue": "/отпуск",
+ "operator": { "type": "string", "operation": "contains" }
+ }
+ ],
+ "combinator": "and"
+ }
+ },
+ "name": "IF /отпуск",
+ "type": "n8n-nodes-base.if",
+ "typeVersion": 2.2,
+ "position": [260, 0]
+ },
+ {
+ "parameters": {
+ "resource": "message",
+ "operation": "create",
+ "entityId": "={{ $(\"Pachca Trigger\").item.json.chat_id }}",
+ "content": "Нажмите кнопку, чтобы оформить заявку на отпуск:",
+ "additionalFields": {
+ "buttons": "[[{\"text\": \"Создать заявку\", \"data\": \"vacation_request\"}]]"
+ }
+ },
+ "name": "Кнопка заявки",
+ "type": "n8n-nodes-pachca.pachca",
+ "typeVersion": 2,
+ "position": [520, 0]
+ }
+ ],
+ "connections": {
+ "Pachca Trigger": { "main": [[{ "node": "IF /отпуск", "type": "main", "index": 0 }]] },
+ "IF /отпуск": { "main": [[{ "node": "Кнопка заявки", "type": "main", "index": 0 }]] }
+ },
+ "settings": { "executionOrder": "v1" },
+ "pinData": {}
+}
diff --git a/apps/docs/public/workflows/n8n/welcome.json b/apps/docs/public/workflows/n8n/welcome.json
new file mode 100644
index 00000000..7d2e9e01
--- /dev/null
+++ b/apps/docs/public/workflows/n8n/welcome.json
@@ -0,0 +1,52 @@
+{
+ "name": "Приветствие нового сотрудника",
+ "nodes": [
+ {
+ "parameters": {
+ "events": ["new_message"]
+ },
+ "name": "Pachca Trigger",
+ "type": "n8n-nodes-pachca.pachcaTrigger",
+ "typeVersion": 2,
+ "position": [0, 0]
+ },
+ {
+ "parameters": {
+ "conditions": {
+ "options": { "caseSensitive": false, "leftValue": "", "typeValidation": "strict" },
+ "conditions": [
+ {
+ "id": "1",
+ "leftValue": "={{ $json.content }}",
+ "rightValue": "присоединил",
+ "operator": { "type": "string", "operation": "contains" }
+ }
+ ],
+ "combinator": "and"
+ }
+ },
+ "name": "IF системное",
+ "type": "n8n-nodes-base.if",
+ "typeVersion": 2.2,
+ "position": [220, 0]
+ },
+ {
+ "parameters": {
+ "resource": "message",
+ "operation": "create",
+ "entityId": "={{ $('Pachca Trigger').item.json.chat_id }}",
+ "content": "Добро пожаловать в команду! Если есть вопросы — пишите в этот чат."
+ },
+ "name": "Приветствие",
+ "type": "n8n-nodes-pachca.pachca",
+ "typeVersion": 2,
+ "position": [440, 0]
+ }
+ ],
+ "connections": {
+ "Pachca Trigger": { "main": [[{ "node": "IF системное", "type": "main", "index": 0 }]] },
+ "IF системное": { "main": [[{ "node": "Приветствие", "type": "main", "index": 0 }]] }
+ },
+ "settings": { "executionOrder": "v1" },
+ "pinData": {}
+}
diff --git a/apps/docs/redirects.ts b/apps/docs/redirects.ts
index 2a3a86c8..675aa407 100644
--- a/apps/docs/redirects.ts
+++ b/apps/docs/redirects.ts
@@ -13,6 +13,8 @@ const redirects: Redirect[] = [
// ===== Guides: accordion parent → first child =====
{ source: '/guides/forms', destination: '/guides/forms/overview' },
{ source: '/guides/forms.md', destination: '/guides/forms/overview.md' },
+ { source: '/guides/n8n', destination: '/guides/n8n/overview' },
+ { source: '/guides/n8n.md', destination: '/guides/n8n/overview.md' },
// ===== SDK → Guides =====
{ source: '/api/sdk', destination: '/guides/sdk/overview' },
diff --git a/apps/docs/scripts/generate-llms.ts b/apps/docs/scripts/generate-llms.ts
index 0b3f0258..4ab9d115 100644
--- a/apps/docs/scripts/generate-llms.ts
+++ b/apps/docs/scripts/generate-llms.ts
@@ -12,7 +12,6 @@ import type { Endpoint } from '../lib/openapi/types';
import { generateRequestExample, generateExample } from '../lib/openapi/example-generator';
import { generateAllSkills } from './skills/generate';
import { SKILL_TAG_MAP, ROUTER_SKILL_CONFIG } from './skills/config';
-import { WORKFLOWS } from '@pachca/spec/workflows';
const SITE_URL = 'https://dev.pachca.com';
@@ -51,7 +50,8 @@ function generateLlmsTxt(api: Awaited>) {
content += '## Руководства\n';
for (const guide of guidePages) {
const mdPath = guide.path === '/' ? '/.md' : `${guide.path}.md`;
- content += `- [${guide.title}](${SITE_URL}${mdPath}): ${guide.description}\n`;
+ const displayTitle = guide.sectionTitle ? `${guide.sectionTitle}: ${guide.title}` : guide.title;
+ content += `- [${displayTitle}](${SITE_URL}${mdPath}): ${guide.description}\n`;
}
content += '\n';
@@ -132,11 +132,12 @@ async function generateLlmsFullTxt(api: Awaited>
content += '## Содержание\n\n';
content += '### Руководства\n';
for (const guide of guidePages) {
- const anchor = guide.title
+ const displayTitle = guide.sectionTitle ? `${guide.sectionTitle}: ${guide.title}` : guide.title;
+ const anchor = displayTitle
.toLowerCase()
.replace(/[#?&=]/g, '')
.replace(/\s+/g, '-');
- content += `- [${guide.title}](#${anchor})\n`;
+ content += `- [${displayTitle}](#${anchor})\n`;
}
content += '\n';
@@ -398,7 +399,8 @@ Error response body: \`{ "errors": [{ "key": "field", "value": "description" }]
let guides = '## Guides\n\nDetailed documentation on specific topics is available at:\n\n';
for (const guide of guidePages) {
if (guide.path === '/') continue;
- guides += `- [${guide.title}](${SITE_URL}${guide.path}) — ${guide.description}\n`;
+ const displayTitle = guide.sectionTitle ? `${guide.sectionTitle}: ${guide.title}` : guide.title;
+ guides += `- [${displayTitle}](${SITE_URL}${guide.path}) — ${guide.description}\n`;
}
return [
@@ -433,52 +435,6 @@ Error response body: \`{ "errors": [{ "key": "field", "value": "description" }]
].join('\n');
}
-function generateScenariosJson() {
- const scenarios: {
- id: string;
- title: string;
- skill: string;
- steps: {
- description: string;
- command?: string;
- apiMethod?: string;
- apiPath?: string;
- notes?: string;
- }[];
- notes?: string;
- related?: string[];
- }[] = [];
- const skillNames = new Set(SKILL_TAG_MAP.map((c) => c.name));
-
- for (const [skillName, workflows] of Object.entries(WORKFLOWS)) {
- if (!skillNames.has(skillName)) continue;
- for (let i = 0; i < workflows.length; i++) {
- const wf = workflows[i];
- scenarios.push({
- id: `${skillName}-${i}`,
- title: wf.title,
- skill: skillName,
- steps: wf.steps.map((step) => {
- const s: Record = { description: step.description };
- if (step.command) s.command = step.command;
- if (step.apiMethod) s.apiMethod = step.apiMethod;
- if (step.apiPath) s.apiPath = step.apiPath;
- if (step.notes) s.notes = step.notes;
- return s as (typeof scenarios)[0]['steps'][0];
- }),
- ...(wf.notes ? { notes: wf.notes } : {}),
- ...(wf.related?.length ? { related: wf.related } : {}),
- });
- }
- }
-
- return {
- $schema: 'https://dev.pachca.com/scenarios.schema.json',
- version: '1.0',
- scenarios,
- };
-}
-
function generatePostmanCollection(api: Awaited>) {
const baseUrl = api.servers[0]?.url ?? 'https://api.pachca.com/api/shared/v1';
const grouped = groupByTag(api.endpoints);
@@ -679,9 +635,7 @@ async function main() {
}
console.log(`✓ ${skillFiles.length} skill files`);
- const scenarios = generateScenariosJson();
- writeFile('public/scenarios.json', JSON.stringify(scenarios, null, 2) + '\n');
- console.log(`✓ public/scenarios.json (${scenarios.scenarios.length} scenarios)`);
+ // scenarios.json removed — its role is covered by the n8n node (n8n-nodes-pachca)
const postmanCollection = generatePostmanCollection(api);
writeFile('public/pachca.postman_collection.json', JSON.stringify(postmanCollection, null, 2));
diff --git a/bun.lock b/bun.lock
index c4efd1f1..ad0b7b54 100644
--- a/bun.lock
+++ b/bun.lock
@@ -5,6 +5,7 @@
"": {
"name": "pachca-api",
"devDependencies": {
+ "@playwright/test": "^1.50.0",
"turbo": "^2.8.1",
},
},
@@ -48,6 +49,22 @@
"tailwindcss": "^4",
},
},
+ "integrations/n8n": {
+ "name": "n8n-nodes-pachca",
+ "version": "0.0.0-dev",
+ "devDependencies": {
+ "@n8n/node-cli": "^0.23.0",
+ "@pachca/openapi-parser": "workspace:*",
+ "eslint": "^9.0.0",
+ "n8n-workflow": "*",
+ "tsx": "^4.0.0",
+ "typescript": "^5.9.0",
+ "vitest": "^3.0.0",
+ },
+ "peerDependencies": {
+ "n8n-workflow": "*",
+ },
+ },
"packages/cli": {
"name": "@pachca/cli",
"version": "0.0.0",
@@ -106,11 +123,13 @@
"name": "@pachca/spec",
"version": "1.0.0",
"devDependencies": {
+ "@types/js-yaml": "^4.0.9",
"@typespec/compiler": "^1.7.1",
"@typespec/http": "^1.7.0",
"@typespec/openapi": "^1.7.0",
"@typespec/openapi3": "^1.7.0",
"@typespec/rest": "^0.77.0",
+ "js-yaml": "^4.1.1",
},
},
"sdk/csharp": {
@@ -162,6 +181,8 @@
"@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="],
+ "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.27.3", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-IjLt0gd3L4jlOfilxVXTifn42FnVffMgDC04RJK1KDZpmkBWLv0XC92MVVmkxrFZNS/7l3xWgP/I3nqtX1sQHw=="],
+
"@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="],
"@aws-crypto/crc32c": ["@aws-crypto/crc32c@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag=="],
@@ -176,75 +197,75 @@
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
- "@aws-sdk/client-cloudfront": ["@aws-sdk/client-cloudfront@3.1001.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.16", "@aws-sdk/credential-provider-node": "^3.972.15", "@aws-sdk/middleware-host-header": "^3.972.6", "@aws-sdk/middleware-logger": "^3.972.6", "@aws-sdk/middleware-recursion-detection": "^3.972.6", "@aws-sdk/middleware-user-agent": "^3.972.16", "@aws-sdk/region-config-resolver": "^3.972.6", "@aws-sdk/types": "^3.973.4", "@aws-sdk/util-endpoints": "^3.996.3", "@aws-sdk/util-user-agent-browser": "^3.972.6", "@aws-sdk/util-user-agent-node": "^3.973.1", "@smithy/config-resolver": "^4.4.9", "@smithy/core": "^3.23.7", "@smithy/fetch-http-handler": "^5.3.12", "@smithy/hash-node": "^4.2.10", "@smithy/invalid-dependency": "^4.2.10", "@smithy/middleware-content-length": "^4.2.10", "@smithy/middleware-endpoint": "^4.4.21", "@smithy/middleware-retry": "^4.4.38", "@smithy/middleware-serde": "^4.2.11", "@smithy/middleware-stack": "^4.2.10", "@smithy/node-config-provider": "^4.3.10", "@smithy/node-http-handler": "^4.4.13", "@smithy/protocol-http": "^5.3.10", "@smithy/smithy-client": "^4.12.1", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.10", "@smithy/util-base64": "^4.3.1", "@smithy/util-body-length-browser": "^4.2.1", "@smithy/util-body-length-node": "^4.2.2", "@smithy/util-defaults-mode-browser": "^4.3.37", "@smithy/util-defaults-mode-node": "^4.2.40", "@smithy/util-endpoints": "^3.3.1", "@smithy/util-middleware": "^4.2.10", "@smithy/util-retry": "^4.2.10", "@smithy/util-stream": "^4.5.16", "@smithy/util-utf8": "^4.2.1", "@smithy/util-waiter": "^4.2.10", "tslib": "^2.6.2" } }, "sha512-zp6+jzAvrfgct46xhUWNFWJApcVLoBNzjwfRUbPKKqkDj2NQd+wh6zy0JMLqdo948FD26fBtVojjeYqyh0EZmw=="],
+ "@aws-sdk/client-cloudfront": ["@aws-sdk/client-cloudfront@3.1009.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.20", "@aws-sdk/credential-provider-node": "^3.972.21", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.21", "@aws-sdk/region-config-resolver": "^3.972.8", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.7", "@smithy/config-resolver": "^4.4.11", "@smithy/core": "^3.23.11", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.25", "@smithy/middleware-retry": "^4.4.42", "@smithy/middleware-serde": "^4.2.14", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.4.16", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.5", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.41", "@smithy/util-defaults-mode-node": "^4.2.44", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-stream": "^4.5.19", "@smithy/util-utf8": "^4.2.2", "@smithy/util-waiter": "^4.2.13", "tslib": "^2.6.2" } }, "sha512-KRac+gkuj3u49IyWkrudHRlP/q/faTto+1xRS7Aj6cDGewMIzgdQArrdZEJoVntbaVZHLM5s/NVmWORzBWNcSw=="],
- "@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.1001.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.16", "@aws-sdk/credential-provider-node": "^3.972.15", "@aws-sdk/middleware-bucket-endpoint": "^3.972.6", "@aws-sdk/middleware-expect-continue": "^3.972.6", "@aws-sdk/middleware-flexible-checksums": "^3.973.2", "@aws-sdk/middleware-host-header": "^3.972.6", "@aws-sdk/middleware-location-constraint": "^3.972.6", "@aws-sdk/middleware-logger": "^3.972.6", "@aws-sdk/middleware-recursion-detection": "^3.972.6", "@aws-sdk/middleware-sdk-s3": "^3.972.16", "@aws-sdk/middleware-ssec": "^3.972.6", "@aws-sdk/middleware-user-agent": "^3.972.16", "@aws-sdk/region-config-resolver": "^3.972.6", "@aws-sdk/signature-v4-multi-region": "^3.996.4", "@aws-sdk/types": "^3.973.4", "@aws-sdk/util-endpoints": "^3.996.3", "@aws-sdk/util-user-agent-browser": "^3.972.6", "@aws-sdk/util-user-agent-node": "^3.973.1", "@smithy/config-resolver": "^4.4.9", "@smithy/core": "^3.23.7", "@smithy/eventstream-serde-browser": "^4.2.10", "@smithy/eventstream-serde-config-resolver": "^4.3.10", "@smithy/eventstream-serde-node": "^4.2.10", "@smithy/fetch-http-handler": "^5.3.12", "@smithy/hash-blob-browser": "^4.2.11", "@smithy/hash-node": "^4.2.10", "@smithy/hash-stream-node": "^4.2.10", "@smithy/invalid-dependency": "^4.2.10", "@smithy/md5-js": "^4.2.10", "@smithy/middleware-content-length": "^4.2.10", "@smithy/middleware-endpoint": "^4.4.21", "@smithy/middleware-retry": "^4.4.38", "@smithy/middleware-serde": "^4.2.11", "@smithy/middleware-stack": "^4.2.10", "@smithy/node-config-provider": "^4.3.10", "@smithy/node-http-handler": "^4.4.13", "@smithy/protocol-http": "^5.3.10", "@smithy/smithy-client": "^4.12.1", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.10", "@smithy/util-base64": "^4.3.1", "@smithy/util-body-length-browser": "^4.2.1", "@smithy/util-body-length-node": "^4.2.2", "@smithy/util-defaults-mode-browser": "^4.3.37", "@smithy/util-defaults-mode-node": "^4.2.40", "@smithy/util-endpoints": "^3.3.1", "@smithy/util-middleware": "^4.2.10", "@smithy/util-retry": "^4.2.10", "@smithy/util-stream": "^4.5.16", "@smithy/util-utf8": "^4.2.1", "@smithy/util-waiter": "^4.2.10", "tslib": "^2.6.2" } }, "sha512-uKgFjQuBjMcd0iigLQwnqIp9gOy/5TGBxa42rcb6l5byDt1mrwOe6fyWTEUEJaNHG2LKYSPUibteGvM1zfm0Rw=="],
+ "@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.1014.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/credential-provider-node": "^3.972.24", "@aws-sdk/middleware-bucket-endpoint": "^3.972.8", "@aws-sdk/middleware-expect-continue": "^3.972.8", "@aws-sdk/middleware-flexible-checksums": "^3.974.3", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-location-constraint": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-sdk-s3": "^3.972.23", "@aws-sdk/middleware-ssec": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/signature-v4-multi-region": "^3.996.11", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.10", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/eventstream-serde-browser": "^4.2.12", "@smithy/eventstream-serde-config-resolver": "^4.3.12", "@smithy/eventstream-serde-node": "^4.2.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-blob-browser": "^4.2.13", "@smithy/hash-node": "^4.2.12", "@smithy/hash-stream-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/md5-js": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/util-waiter": "^4.2.13", "tslib": "^2.6.2" } }, "sha512-0XLrOT4Cm3NEhhiME7l/8LbTXS4KdsbR4dSrY207KNKTcHLLTZ9EXt4ZpgnTfLvWQF3pGP2us4Zi1fYLo0N+Ow=="],
- "@aws-sdk/core": ["@aws-sdk/core@3.973.16", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@aws-sdk/xml-builder": "^3.972.9", "@smithy/core": "^3.23.7", "@smithy/node-config-provider": "^4.3.10", "@smithy/property-provider": "^4.2.10", "@smithy/protocol-http": "^5.3.10", "@smithy/signature-v4": "^5.3.10", "@smithy/smithy-client": "^4.12.1", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.1", "@smithy/util-middleware": "^4.2.10", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-Nasoyb5K4jfvncTKQyA13q55xHoz9as01NVYP05B0Kzux/X5UhMn3qXsZDyWOSXkfSCAIrMBKmVVWbI0vUapdQ=="],
+ "@aws-sdk/core": ["@aws-sdk/core@3.973.25", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/xml-builder": "^3.972.16", "@smithy/core": "^3.23.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-TNrx7eq6nKNOO62HWPqoBqPLXEkW6nLZQGwjL6lq1jZtigWYbK1NbCnT7mKDzbLMHZfuOECUt3n6CzxjUW9HWQ=="],
- "@aws-sdk/crc64-nvme": ["@aws-sdk/crc64-nvme@3.972.3", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-UExeK+EFiq5LAcbHm96CQLSia+5pvpUVSAsVApscBzayb7/6dJBJKwV4/onsk4VbWSmqxDMcfuTD+pC4RxgZHg=="],
+ "@aws-sdk/crc64-nvme": ["@aws-sdk/crc64-nvme@3.972.5", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-2VbTstbjKdT+yKi8m7b3a9CiVac+pL/IY2PHJwsaGkkHmuuqkJZIErPck1h6P3T9ghQMLSdMPyW6Qp7Di5swFg=="],
- "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.14", "", { "dependencies": { "@aws-sdk/core": "^3.973.16", "@aws-sdk/types": "^3.973.4", "@smithy/property-provider": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-PvnBY9rwBuLh9MEsAng28DG+WKl+txerKgf4BU9IPAqYI7FBIo1x6q/utLf4KLyQYgSy1TLQnbQuXx5xfBGASg=="],
+ "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.25", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-EamaclJcCEaPHp6wiVknNMM2RlsPMjAHSsYSFLNENBM8Wz92QPc6cOn3dif6vPDQt0Oo4IEghDy3NMDCzY/IvA=="],
- "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.16", "", { "dependencies": { "@aws-sdk/core": "^3.973.16", "@aws-sdk/types": "^3.973.4", "@smithy/fetch-http-handler": "^5.3.12", "@smithy/node-http-handler": "^4.4.13", "@smithy/property-provider": "^4.2.10", "@smithy/protocol-http": "^5.3.10", "@smithy/smithy-client": "^4.12.1", "@smithy/types": "^4.13.0", "@smithy/util-stream": "^4.5.16", "tslib": "^2.6.2" } }, "sha512-m/QAcvw5OahqGPjeAnKtgfWgjLxeWOYj7JSmxKK6PLyKp2S/t2TAHI6EELEzXnIz28RMgbQLukJkVAqPASVAGQ=="],
+ "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.25", "", { "dependencies": { "@aws-sdk/core": "^3.973.25", "@aws-sdk/types": "^3.973.6", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-qPymamdPcLp6ugoVocG1y5r69ScNiRzb0hogX25/ij+Wz7c7WnsgjLTaz7+eB5BfRxeyUwuw5hgULMuwOGOpcw=="],
- "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.14", "", { "dependencies": { "@aws-sdk/core": "^3.973.16", "@aws-sdk/credential-provider-env": "^3.972.14", "@aws-sdk/credential-provider-http": "^3.972.16", "@aws-sdk/credential-provider-login": "^3.972.14", "@aws-sdk/credential-provider-process": "^3.972.14", "@aws-sdk/credential-provider-sso": "^3.972.14", "@aws-sdk/credential-provider-web-identity": "^3.972.14", "@aws-sdk/nested-clients": "^3.996.4", "@aws-sdk/types": "^3.973.4", "@smithy/credential-provider-imds": "^4.2.10", "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-EGA7ufqNpZKZcD0RwM6gRDEQgwAf19wQ99R1ptdWYDJAnpcMcWiFyT0RIrgiZFLD28CwJmYjnra75hChnEveWA=="],
+ "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.26", "", { "dependencies": { "@aws-sdk/core": "^3.973.25", "@aws-sdk/credential-provider-env": "^3.972.23", "@aws-sdk/credential-provider-http": "^3.972.25", "@aws-sdk/credential-provider-login": "^3.972.26", "@aws-sdk/credential-provider-process": "^3.972.23", "@aws-sdk/credential-provider-sso": "^3.972.26", "@aws-sdk/credential-provider-web-identity": "^3.972.26", "@aws-sdk/nested-clients": "^3.996.16", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-xKxEAMuP6GYx2y5GET+d3aGEroax3AgGfwBE65EQAUe090lzyJ/RzxPX9s8v7Z6qAk0XwfQl+LrmH05X7YvTeg=="],
- "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.14", "", { "dependencies": { "@aws-sdk/core": "^3.973.16", "@aws-sdk/nested-clients": "^3.996.4", "@aws-sdk/types": "^3.973.4", "@smithy/property-provider": "^4.2.10", "@smithy/protocol-http": "^5.3.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-P2kujQHAoV7irCTv6EGyReKFofkHCjIK+F0ZYf5UxeLeecrCwtrDkHoO2Vjsv/eRUumaKblD8czuk3CLlzwGDw=="],
+ "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.26", "", { "dependencies": { "@aws-sdk/core": "^3.973.25", "@aws-sdk/nested-clients": "^3.996.16", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-EFcM8RM3TUxnZOfMJo++3PnyxFu1fL/huzmn3Vh+8IWRgqZawUD3cRwwOr+/4bE9DpyHaLOWFAjY0lfK5X9ZkQ=="],
- "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.15", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.14", "@aws-sdk/credential-provider-http": "^3.972.16", "@aws-sdk/credential-provider-ini": "^3.972.14", "@aws-sdk/credential-provider-process": "^3.972.14", "@aws-sdk/credential-provider-sso": "^3.972.14", "@aws-sdk/credential-provider-web-identity": "^3.972.14", "@aws-sdk/types": "^3.973.4", "@smithy/credential-provider-imds": "^4.2.10", "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-59NBJgTcQ2FC94T+SWkN5UQgViFtrLnkswSKhG5xbjPAotOXnkEF2Bf0bfUV1F3VaXzqAPZJoZ3bpg4rr8XD5Q=="],
+ "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.27", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.23", "@aws-sdk/credential-provider-http": "^3.972.25", "@aws-sdk/credential-provider-ini": "^3.972.26", "@aws-sdk/credential-provider-process": "^3.972.23", "@aws-sdk/credential-provider-sso": "^3.972.26", "@aws-sdk/credential-provider-web-identity": "^3.972.26", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jXpxSolfFnPVj6GCTtx3xIdWNoDR7hYC/0SbetGZxOC9UnNmipHeX1k6spVstf7eWJrMhXNQEgXC0pD1r5tXIg=="],
- "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.14", "", { "dependencies": { "@aws-sdk/core": "^3.973.16", "@aws-sdk/types": "^3.973.4", "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-KAF5LBkJInUPaR9dJDw8LqmbPDRTLyXyRoWVGcJQ+DcN9rxVKBRzAK+O4dTIvQtQ7xaIDZ2kY7zUmDlz6CCXdw=="],
+ "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.25", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-IL/TFW59++b7MpHserjUblGrdP5UXy5Ekqqx1XQkERXBFJcZr74I7VaSrQT5dxdRMU16xGK4L0RQ5fQG1pMgnA=="],
- "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.14", "", { "dependencies": { "@aws-sdk/core": "^3.973.16", "@aws-sdk/nested-clients": "^3.996.4", "@aws-sdk/token-providers": "3.1001.0", "@aws-sdk/types": "^3.973.4", "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-LQzIYrNABnZzkyuIguFa3VVOox9UxPpRW6PL+QYtRHaGl1Ux/+Zi54tAVK31VdeBKPKU3cxqeu8dbOgNqy+naw=="],
+ "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.26", "", { "dependencies": { "@aws-sdk/core": "^3.973.25", "@aws-sdk/nested-clients": "^3.996.16", "@aws-sdk/token-providers": "3.1019.0", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-c6ghvRb6gTlMznWhGxn/bpVCcp0HRaz4DobGVD9kI4vwHq186nU2xN/S7QGkm0lo0H2jQU8+dgpUFLxfTcwCOg=="],
- "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.14", "", { "dependencies": { "@aws-sdk/core": "^3.973.16", "@aws-sdk/nested-clients": "^3.996.4", "@aws-sdk/types": "^3.973.4", "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-rOwB3vXHHHnGvAOjTgQETxVAsWjgF61XlbGd/ulvYo7EpdXs8cbIHE3PGih9tTj/65ZOegSqZGFqLaKntaI9Kw=="],
+ "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.26", "", { "dependencies": { "@aws-sdk/core": "^3.973.25", "@aws-sdk/nested-clients": "^3.996.16", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-cXcS3+XD3iwhoXkM44AmxjmbcKueoLCINr1e+IceMmCySda5ysNIfiGBGe9qn5EMiQ9Jd7pP0AGFtcd6OV3Lvg=="],
- "@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@aws-sdk/util-arn-parser": "^3.972.2", "@smithy/node-config-provider": "^4.3.10", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "@smithy/util-config-provider": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-3H2bhvb7Cb/S6WFsBy/Dy9q2aegC9JmGH1inO8Lb2sWirSqpLJlZmvQHPE29h2tIxzv6el/14X/tLCQ8BQU6ZQ=="],
+ "@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-WR525Rr2QJSETa9a050isktyWi/4yIGcmY3BQ1kpHqb0LqUglQHCS8R27dTJxxWNZvQ0RVGtEZjTCbZJpyF3Aw=="],
- "@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-QMdffpU+GkSGC+bz6WdqlclqIeCsOfgX8JFZ5xvwDtX+UTj4mIXm3uXu7Ko6dBseRcJz1FA6T9OmlAAY6JgJUg=="],
+ "@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-5DTBTiotEES1e2jOHAq//zyzCjeMB78lEHd35u15qnrid4Nxm7diqIf9fQQ3Ov0ChH1V3Vvt13thOnrACmfGVQ=="],
- "@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.973.2", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "^3.973.16", "@aws-sdk/crc64-nvme": "^3.972.3", "@aws-sdk/types": "^3.973.4", "@smithy/is-array-buffer": "^4.2.1", "@smithy/node-config-provider": "^4.3.10", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "@smithy/util-middleware": "^4.2.10", "@smithy/util-stream": "^4.5.16", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-KM6QujWdasNjRLG+f7YEqEY5D36vR6Govm7nPIwxjILpb5rJ0pPJZpYY1nrzgtlxwJIYAznfBK5YXoLOHKHyfQ=="],
+ "@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.974.5", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "^3.973.25", "@aws-sdk/crc64-nvme": "^3.972.5", "@aws-sdk/types": "^3.973.6", "@smithy/is-array-buffer": "^4.2.2", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-SPSvF0G1t8m8CcB0L+ClNFszzQOvXaxmRj25oRWDf6aU+TuN2PXPFAJ9A6lt1IvX4oGAqqbTdMPTYs/SSHUYYQ=="],
- "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-5XHwjPH1lHB+1q4bfC7T8Z5zZrZXfaLcjSMwTd1HPSPrCmPFMbg3UQ5vgNWcVj0xoX4HWqTGkSf2byrjlnRg5w=="],
+ "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ=="],
- "@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-XdZ2TLwyj3Am6kvUc67vquQvs6+D8npXvXgyEUJAdkUDx5oMFJKOqpK+UpJhVDsEL068WAJl2NEGzbSik7dGJQ=="],
+ "@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-KaUoFuoFPziIa98DSQsTPeke1gvGXlc5ZGMhy+b+nLxZ4A7jmJgLzjEF95l8aOQN2T/qlPP3MrAyELm8ExXucw=="],
- "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-iFnaMFMQdljAPrvsCVKYltPt2j40LQqukAbXvW7v0aL5I+1GO7bZ/W8m12WxW3gwyK5p5u1WlHg8TSAizC5cZw=="],
+ "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA=="],
- "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-dY4v3of5EEMvik6+UDwQ96KfUFDk8m1oZDdkSc5lwi4o7rFrjnv0A+yTV+gu230iybQZnKgDLg/rt2P3H+Vscw=="],
+ "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.9", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-/Wt5+CT8dpTFQxEJ9iGy/UGrXr7p2wlIOEHvIr/YcHYByzoLjrqkYqXdJjd9UIgWjv7eqV2HnFJen93UTuwfTQ=="],
- "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.16", "", { "dependencies": { "@aws-sdk/core": "^3.973.16", "@aws-sdk/types": "^3.973.4", "@aws-sdk/util-arn-parser": "^3.972.2", "@smithy/core": "^3.23.7", "@smithy/node-config-provider": "^4.3.10", "@smithy/protocol-http": "^5.3.10", "@smithy/signature-v4": "^5.3.10", "@smithy/smithy-client": "^4.12.1", "@smithy/types": "^4.13.0", "@smithy/util-config-provider": "^4.2.1", "@smithy/util-middleware": "^4.2.10", "@smithy/util-stream": "^4.5.16", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-U4K1rqyJYvT/zgTI3+rN+MToa51dFnnq1VSsVJuJWPNEKcEnuZVqf7yTpkJJMkYixVW5TTi1dgupd+nmJ0JyWw=="],
+ "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.26", "", { "dependencies": { "@aws-sdk/core": "^3.973.25", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/core": "^3.23.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-5q7UGSTtt7/KF0Os8wj2VZtlLxeWJVb0e2eDrDJlWot2EIxUNKDDMPFq/FowUqrwZ40rO2bu6BypxaKNvQhI+g=="],
- "@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-acvMUX9jF4I2Ew+Z/EA6gfaFaz9ehci5wxBmXCZeulLuv8m+iGf6pY9uKz8TPjg39bdAz3hxoE0eLP8Qz+IYlA=="],
+ "@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wqlK0yO/TxEC2UsY9wIlqeeutF6jjLe0f96Pbm40XscTo57nImUk9lBcw0dPgsm0sppFtAkSlDrfpK+pC30Wqw=="],
- "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.16", "", { "dependencies": { "@aws-sdk/core": "^3.973.16", "@aws-sdk/types": "^3.973.4", "@aws-sdk/util-endpoints": "^3.996.3", "@smithy/core": "^3.23.7", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-AmVxtxn8ZkNJbuPu3KKfW9IkJgTgcEtgSwbo0NVcAb31iGvLgHXj2nbbyrUDfh2fx8otXmqL+qw1lRaTi+V3vA=="],
+ "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.26", "", { "dependencies": { "@aws-sdk/core": "^3.973.25", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-retry": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-AilFIh4rI/2hKyyGN6XrB0yN96W2o7e7wyrPWCM6QjZM1mcC/pVkW3IWWRvuBWMpVP8Fg+rMpbzeLQ6dTM4gig=="],
- "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.4", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.16", "@aws-sdk/middleware-host-header": "^3.972.6", "@aws-sdk/middleware-logger": "^3.972.6", "@aws-sdk/middleware-recursion-detection": "^3.972.6", "@aws-sdk/middleware-user-agent": "^3.972.16", "@aws-sdk/region-config-resolver": "^3.972.6", "@aws-sdk/types": "^3.973.4", "@aws-sdk/util-endpoints": "^3.996.3", "@aws-sdk/util-user-agent-browser": "^3.972.6", "@aws-sdk/util-user-agent-node": "^3.973.1", "@smithy/config-resolver": "^4.4.9", "@smithy/core": "^3.23.7", "@smithy/fetch-http-handler": "^5.3.12", "@smithy/hash-node": "^4.2.10", "@smithy/invalid-dependency": "^4.2.10", "@smithy/middleware-content-length": "^4.2.10", "@smithy/middleware-endpoint": "^4.4.21", "@smithy/middleware-retry": "^4.4.38", "@smithy/middleware-serde": "^4.2.11", "@smithy/middleware-stack": "^4.2.10", "@smithy/node-config-provider": "^4.3.10", "@smithy/node-http-handler": "^4.4.13", "@smithy/protocol-http": "^5.3.10", "@smithy/smithy-client": "^4.12.1", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.10", "@smithy/util-base64": "^4.3.1", "@smithy/util-body-length-browser": "^4.2.1", "@smithy/util-body-length-node": "^4.2.2", "@smithy/util-defaults-mode-browser": "^4.3.37", "@smithy/util-defaults-mode-node": "^4.2.40", "@smithy/util-endpoints": "^3.3.1", "@smithy/util-middleware": "^4.2.10", "@smithy/util-retry": "^4.2.10", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-NowB1HfOnWC4kwZOnTg8E8rSL0U+RSjSa++UtEV4ipoH6JOjMLnHyGilqwl+Pe1f0Al6v9yMkSJ/8Ot0f578CQ=="],
+ "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.16", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.25", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.9", "@aws-sdk/middleware-user-agent": "^3.972.26", "@aws-sdk/region-config-resolver": "^3.972.10", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.12", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-L7Qzoj/qQU1cL5GnYLQP5LbI+wlLCLoINvcykR3htKcQ4tzrPf2DOs72x933BM7oArYj1SKrkb2lGlsJHIic3g=="],
- "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/config-resolver": "^4.4.9", "@smithy/node-config-provider": "^4.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-Aa5PusHLXAqLTX1UKDvI3pHQJtIsF7Q+3turCHqfz/1F61/zDMWfbTC8evjhrrYVAtz9Vsv3SJ/waSUeu7B6gw=="],
+ "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/config-resolver": "^4.4.13", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-1dq9ToC6e070QvnVhhbAs3bb5r6cQ10gTVc6cyRV5uvQe7P138TV2uG2i6+Yok4bAkVAcx5AqkTEBUvWEtBlsQ=="],
- "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.4", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.16", "@aws-sdk/types": "^3.973.4", "@smithy/protocol-http": "^5.3.10", "@smithy/signature-v4": "^5.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-MGa8ro0onekYIiesHX60LwKdkxK3Kd61p7TTbLwZemBqlnD9OLrk9sXZdFOIxXanJ+3AaJnV/jiX866eD/4PDg=="],
+ "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.14", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.26", "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-4nZSrBr1NO+48HCM/6BRU8mnRjuHZjcpziCvLXZk5QVftwWz5Mxqbhwdz4xf7WW88buaTB8uRO2MHklSX1m0vg=="],
- "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1001.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.16", "@aws-sdk/nested-clients": "^3.996.4", "@aws-sdk/types": "^3.973.4", "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-09XAq/uIYgeZhohuGRrR/R+ek3+ljFNdzWCXdqb9rlIERDjSfNiLjTtpHgSK1xTPmC5G4yWoEAyMfTXiggS6wA=="],
+ "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1019.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.25", "@aws-sdk/nested-clients": "^3.996.16", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-OF+2RfRmUKyjzrRWlDcyju3RBsuqcrYDQ8TwrJg8efcOotMzuZN4U9mpVTIdATpmEc4lWNZBMSjPzrGm6JPnAQ=="],
- "@aws-sdk/types": ["@aws-sdk/types@3.973.4", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q=="],
+ "@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="],
- "@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-VkykWbqMjlSgBFDyrY3nOSqupMc6ivXuGmvci6Q3NnLq5kC+mKQe2QBZ4nrWRE/jqOxeFP2uYzLtwncYYcvQDg=="],
+ "@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA=="],
- "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.10", "@smithy/util-endpoints": "^3.3.1", "tslib": "^2.6.2" } }, "sha512-yWIQSNiCjykLL+ezN5A+DfBb1gfXTytBxm57e64lYmwxDHNmInYHRJYYRAGWG1o77vKEiWaw4ui28e3yb1k5aQ=="],
+ "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.5", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-endpoints": "^3.3.3", "tslib": "^2.6.2" } }, "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw=="],
- "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.4", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog=="],
+ "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.5", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ=="],
- "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/types": "^4.13.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-Fwr/llD6GOrFgQnKaI2glhohdGuBDfHfora6iG9qsBBBR8xv1SdCSwbtf5CWlUdCw5X7g76G/9Hf0Inh0EmoxA=="],
+ "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA=="],
- "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.1", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.16", "@aws-sdk/types": "^3.973.4", "@smithy/node-config-provider": "^4.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-kmgbDqT7aCBEVrqESM2JUjbf0zhDUQ7wnt3q1RuVS+3mglrcfVb2bwkbmf38npOyyPGtQPV5dWN3m+sSFAVAgQ=="],
+ "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.12", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.26", "@aws-sdk/types": "^3.973.6", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-8phW0TS8ntENJgDcFewYT/Q8dOmarpvSxEjATu2GUBAutiHr++oEGCiBUwxslCMNvwW2cAPZNT53S/ym8zm/gg=="],
- "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.9", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-ItnlMgSqkPrUfJs7EsvU/01zw5UeIb2tNPhD09LBLHbg+g+HDiKibSLwpkuz/ZIlz4F2IMn+5XgE4AK/pfPuog=="],
+ "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.16", "", { "dependencies": { "@smithy/types": "^4.13.1", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" } }, "sha512-iu2pyvaqmeatIJLURLqx9D+4jKAdTH20ntzB6BFwjyN7V960r4jK32mx0Zf7YbtOYAbmbtQfDNuL60ONinyw7A=="],
- "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.3", "", {}, "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw=="],
+ "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="],
- "@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="],
+ "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
"@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
@@ -266,9 +287,9 @@
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
- "@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
+ "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
- "@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
+ "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
@@ -276,105 +297,115 @@
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
+ "@borewit/text-codec": ["@borewit/text-codec@0.2.2", "", {}, "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ=="],
+
"@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="],
- "@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@11.0.3", "", { "dependencies": { "@chevrotain/gast": "11.0.3", "@chevrotain/types": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ=="],
+ "@browserbasehq/sdk": ["@browserbasehq/sdk@2.9.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-Xzm1+6suzQypXjley4Phqer++pjnYyST6S7CArUn3kWyGA8aruXjAV5wkmqE21lgXo9K3/OQJvCu48bKEZFNDQ=="],
+
+ "@browserbasehq/stagehand": ["@browserbasehq/stagehand@1.14.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.27.3", "@browserbasehq/sdk": "^2.0.0", "ws": "^8.18.0", "zod-to-json-schema": "^3.23.5" }, "peerDependencies": { "@playwright/test": "^1.42.1", "deepmerge": "^4.3.1", "dotenv": "^16.4.5", "openai": "^4.62.1", "zod": "^3.23.8" } }, "sha512-Hi/EzgMFWz+FKyepxHTrqfTPjpsuBS4zRy3e9sbMpBgLPv+9c0R+YZEvS7Bw4mTS66QtvvURRT6zgDGFotthVQ=="],
- "@chevrotain/gast": ["@chevrotain/gast@11.0.3", "", { "dependencies": { "@chevrotain/types": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q=="],
+ "@cfworker/json-schema": ["@cfworker/json-schema@4.1.1", "", {}, "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og=="],
- "@chevrotain/regexp-to-ast": ["@chevrotain/regexp-to-ast@11.0.3", "", {}, "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA=="],
+ "@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@11.1.2", "", { "dependencies": { "@chevrotain/gast": "11.1.2", "@chevrotain/types": "11.1.2", "lodash-es": "4.17.23" } }, "sha512-XTsjvDVB5nDZBQB8o0o/0ozNelQtn2KrUVteIHSlPd2VAV2utEb6JzyCJaJ8tGxACR4RiBNWy5uYUHX2eji88Q=="],
- "@chevrotain/types": ["@chevrotain/types@11.0.3", "", {}, "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ=="],
+ "@chevrotain/gast": ["@chevrotain/gast@11.1.2", "", { "dependencies": { "@chevrotain/types": "11.1.2", "lodash-es": "4.17.23" } }, "sha512-Z9zfXR5jNZb1Hlsd/p+4XWeUFugrHirq36bKzPWDSIacV+GPSVXdk+ahVWZTwjhNwofAWg/sZg58fyucKSQx5g=="],
- "@chevrotain/utils": ["@chevrotain/utils@11.0.3", "", {}, "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ=="],
+ "@chevrotain/regexp-to-ast": ["@chevrotain/regexp-to-ast@11.1.2", "", {}, "sha512-nMU3Uj8naWer7xpZTYJdxbAs6RIv/dxYzkYU8GSwgUtcAAlzjcPfX1w+RKRcYG8POlzMeayOQ/znfwxEGo5ulw=="],
+
+ "@chevrotain/types": ["@chevrotain/types@11.1.2", "", {}, "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw=="],
+
+ "@chevrotain/utils": ["@chevrotain/utils@11.1.2", "", {}, "sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA=="],
"@clack/core": ["@clack/core@0.4.2", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-NYQfcEy8MWIxrT5Fj8nIVchfRFA26yYKJcvBS7WlUIlw2OmQOY9DhGGXMovyI5J5PpxrCPGkgUi207EBrjpBvg=="],
"@clack/prompts": ["@clack/prompts@0.10.1", "", { "dependencies": { "@clack/core": "0.4.2", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-Q0T02vx8ZM9XSv9/Yde0jTmmBQufZhPJfYAg2XrrrxWWaZgq1rr8nU8Hv710BQ1dhoP8rtY7YUdpGej2Qza/cw=="],
- "@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
+ "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
+
+ "@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
- "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
+ "@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
- "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
+ "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="],
- "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
+ "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="],
- "@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
+ "@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="],
- "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
+ "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="],
- "@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
+ "@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="],
- "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
+ "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="],
- "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
+ "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="],
- "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
+ "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="],
- "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
+ "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="],
- "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
+ "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="],
- "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
+ "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="],
- "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
+ "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="],
- "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
+ "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="],
- "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
+ "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="],
- "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
+ "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="],
- "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
+ "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="],
- "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
+ "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="],
- "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
+ "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="],
- "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
+ "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q=="],
- "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
+ "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="],
- "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
+ "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow=="],
- "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
+ "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="],
- "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
+ "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg=="],
- "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
+ "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="],
- "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
+ "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="],
- "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
+ "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="],
- "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
+ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="],
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
- "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="],
+ "@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="],
"@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
"@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
- "@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="],
+ "@eslint/eslintrc": ["@eslint/eslintrc@3.3.5", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg=="],
- "@eslint/js": ["@eslint/js@9.39.2", "", {}, "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA=="],
+ "@eslint/js": ["@eslint/js@9.39.4", "", {}, "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw=="],
"@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
- "@floating-ui/core": ["@floating-ui/core@1.7.4", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg=="],
+ "@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="],
- "@floating-ui/dom": ["@floating-ui/dom@1.7.5", "", { "dependencies": { "@floating-ui/core": "^1.7.4", "@floating-ui/utils": "^0.2.10" } }, "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg=="],
+ "@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="],
- "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.7", "", { "dependencies": { "@floating-ui/dom": "^1.7.5" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg=="],
+ "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.8", "", { "dependencies": { "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A=="],
- "@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="],
+ "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="],
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
@@ -384,11 +415,13 @@
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
+ "@ibm-cloud/watsonx-ai": ["@ibm-cloud/watsonx-ai@1.7.10", "", { "dependencies": { "@types/node": "^18.0.0", "extend": "3.0.2", "form-data": "^4.0.4", "ibm-cloud-sdk-core": "^5.4.5", "ts-node": "^10.9.2" } }, "sha512-+ckgkR/qLQSG5hmVrD3OywWGEmY8Vgo3WR3T0jGJxcO9w89gPwgQENn3qFnhF0YlILGEl4zNPuTYYDj1MtNSng=="],
+
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
"@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="],
- "@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="],
+ "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
@@ -438,33 +471,33 @@
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
- "@inquirer/ansi": ["@inquirer/ansi@2.0.3", "", {}, "sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw=="],
+ "@inquirer/ansi": ["@inquirer/ansi@2.0.4", "", {}, "sha512-DpcZrQObd7S0R/U3bFdkcT5ebRwbTTC4D3tCc1vsJizmgPLxNJBo+AAFmrZwe8zk30P2QzgzGWZ3Q9uJwWuhIg=="],
- "@inquirer/checkbox": ["@inquirer/checkbox@5.0.6", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/core": "^11.1.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-qLZ1gOpsqsieB5k98GQ9bWYggvMsCXTc7HUwhEQpTsxFQYGthqR9UysCwqB7L9h47THYdXhJegnYb1IqURMjng=="],
+ "@inquirer/checkbox": ["@inquirer/checkbox@5.1.2", "", { "dependencies": { "@inquirer/ansi": "^2.0.4", "@inquirer/core": "^11.1.7", "@inquirer/figures": "^2.0.4", "@inquirer/type": "^4.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-PubpMPO2nJgMufkoB3P2wwxNXEMUXnBIKi/ACzDUYfaoPuM7gSTmuxJeMscoLVEsR4qqrCMf5p0SiYGWnVJ8kw=="],
"@inquirer/confirm": ["@inquirer/confirm@3.2.0", "", { "dependencies": { "@inquirer/core": "^9.1.0", "@inquirer/type": "^1.5.3" } }, "sha512-oOIwPs0Dvq5220Z8lGL/6LHRTEr9TgLHmiI99Rj1PJ1p1czTys+olrgBqZk4E2qC0YTzeHprxSQmoHioVdJ7Lw=="],
"@inquirer/core": ["@inquirer/core@9.2.1", "", { "dependencies": { "@inquirer/figures": "^1.0.6", "@inquirer/type": "^2.0.0", "@types/mute-stream": "^0.0.4", "@types/node": "^22.5.5", "@types/wrap-ansi": "^3.0.0", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", "mute-stream": "^1.0.0", "signal-exit": "^4.1.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.2" } }, "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg=="],
- "@inquirer/editor": ["@inquirer/editor@5.0.6", "", { "dependencies": { "@inquirer/core": "^11.1.3", "@inquirer/external-editor": "^2.0.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-dxTi/TB29NaW18u0pQl3B140695izGUMzr340a4Yhxll3oa0/iwxl6C88sX9LDUPFaaM4FDASEMnLm8XVk2VVg=="],
+ "@inquirer/editor": ["@inquirer/editor@5.0.10", "", { "dependencies": { "@inquirer/core": "^11.1.7", "@inquirer/external-editor": "^2.0.4", "@inquirer/type": "^4.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-VJx4XyaKea7t8hEApTw5dxeIyMtWXre2OiyJcICCRZI4hkoHsMoCnl/KbUnJJExLbH9csLLHMVR144ZhFE1CwA=="],
- "@inquirer/expand": ["@inquirer/expand@5.0.6", "", { "dependencies": { "@inquirer/core": "^11.1.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-HmgMzFdMk/gmPXfuFy4xgWkyIVbdH81otQkrFbhklFZcGauwDFD1EbgmZdgmYCN5pWhSEnYIadg1kysLgPIYag=="],
+ "@inquirer/expand": ["@inquirer/expand@5.0.10", "", { "dependencies": { "@inquirer/core": "^11.1.7", "@inquirer/type": "^4.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-fC0UHJPXsTRvY2fObiwuQYaAnHrp3aDqfwKUJSdfpgv18QUG054ezGbaRNStk/BKD5IPijeMKWej8VV8O5Q/eQ=="],
- "@inquirer/external-editor": ["@inquirer/external-editor@2.0.3", "", { "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-LgyI7Agbda74/cL5MvA88iDpvdXI2KuMBCGRkbCl2Dg1vzHeOgs+s0SDcXV7b+WZJrv2+ERpWSM65Fpi9VfY3w=="],
+ "@inquirer/external-editor": ["@inquirer/external-editor@2.0.4", "", { "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Prenuv9C1PHj2Itx0BcAOVBTonz02Hc2Nd2DbU67PdGUaqn0nPCnV34oDyyoaZHnmfRxkpuhh/u51ThkrO+RdA=="],
"@inquirer/figures": ["@inquirer/figures@1.0.15", "", {}, "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g=="],
"@inquirer/input": ["@inquirer/input@2.3.0", "", { "dependencies": { "@inquirer/core": "^9.1.0", "@inquirer/type": "^1.5.3" } }, "sha512-XfnpCStx2xgh1LIRqPXrTNEEByqQWoxsWYzNRSEUxJ5c6EQlhMogJ3vHKu8aXuTacebtaZzMAHwEL0kAflKOBw=="],
- "@inquirer/number": ["@inquirer/number@4.0.6", "", { "dependencies": { "@inquirer/core": "^11.1.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-owMkAY+gR0BggomDTL+Z22x/yfE4ocFrmNyJacOiaDVA/d+iL4IWyk7Ds7JEuDMxuhHFB46Dubdxg1uiD7GlCA=="],
+ "@inquirer/number": ["@inquirer/number@4.0.10", "", { "dependencies": { "@inquirer/core": "^11.1.7", "@inquirer/type": "^4.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Ht8OQstxiS3APMGjHV0aYAjRAysidWdwurWEo2i8yI5xbhOBWqizT0+MU1S2GCcuhIBg+3SgWVjEoXgfhY+XaA=="],
- "@inquirer/password": ["@inquirer/password@5.0.6", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/core": "^11.1.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-c4BT4SB79iYwPhtGVBSvrlTnn4oFSYnwocafmktpay8RK75T2c2+fLlR0i1Cxw0QOhdy/YULdmpHoy1sOrPzvA=="],
+ "@inquirer/password": ["@inquirer/password@5.0.10", "", { "dependencies": { "@inquirer/ansi": "^2.0.4", "@inquirer/core": "^11.1.7", "@inquirer/type": "^4.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-QbNyvIE8q2GTqKLYSsA8ATG+eETo+m31DSR0+AU7x3d2FhaTWzqQek80dj3JGTo743kQc6mhBR0erMjYw5jQ0A=="],
- "@inquirer/prompts": ["@inquirer/prompts@8.2.1", "", { "dependencies": { "@inquirer/checkbox": "^5.0.5", "@inquirer/confirm": "^6.0.5", "@inquirer/editor": "^5.0.5", "@inquirer/expand": "^5.0.5", "@inquirer/input": "^5.0.5", "@inquirer/number": "^4.0.5", "@inquirer/password": "^5.0.5", "@inquirer/rawlist": "^5.2.1", "@inquirer/search": "^4.1.1", "@inquirer/select": "^5.0.5" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-76knJFW2oXdI6If5YRmEoT5u7l+QroXYrMiINFcb97LsyECgsbO9m6iWlPuhBtaFgNITPHQCk3wbex38q8gsjg=="],
+ "@inquirer/prompts": ["@inquirer/prompts@8.3.2", "", { "dependencies": { "@inquirer/checkbox": "^5.1.2", "@inquirer/confirm": "^6.0.10", "@inquirer/editor": "^5.0.10", "@inquirer/expand": "^5.0.10", "@inquirer/input": "^5.0.10", "@inquirer/number": "^4.0.10", "@inquirer/password": "^5.0.10", "@inquirer/rawlist": "^5.2.6", "@inquirer/search": "^4.1.6", "@inquirer/select": "^5.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-yFroiSj2iiBFlm59amdTvAcQFvWS6ph5oKESls/uqPBect7rTU2GbjyZO2DqxMGuIwVA8z0P4K6ViPcd/cp+0w=="],
- "@inquirer/rawlist": ["@inquirer/rawlist@5.2.2", "", { "dependencies": { "@inquirer/core": "^11.1.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-ld2EhLlf3fsBv7QfxR31NdBecGdS6eeFFZ+Nx88ApjtifeCEc9TNrw8x5tGe+gd6HG1ERczOb4B/bMojiGIp1g=="],
+ "@inquirer/rawlist": ["@inquirer/rawlist@5.2.6", "", { "dependencies": { "@inquirer/core": "^11.1.7", "@inquirer/type": "^4.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-jfw0MLJ5TilNsa9zlJ6nmRM0ZFVZhhTICt4/6CU2Dv1ndY7l3sqqo1gIYZyMMDw0LvE1u1nzJNisfHEhJIxq5w=="],
- "@inquirer/search": ["@inquirer/search@4.1.2", "", { "dependencies": { "@inquirer/core": "^11.1.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-kdGbbbWYKldWxpxodKYPmFl/ctBi3DjWlA4LX48jXtqJ7NEeoEKlyFTbE4xNEFcGDi15tvaxRLzCV4A53zqYIw=="],
+ "@inquirer/search": ["@inquirer/search@4.1.6", "", { "dependencies": { "@inquirer/core": "^11.1.7", "@inquirer/figures": "^2.0.4", "@inquirer/type": "^4.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-3/6kTRae98hhDevENScy7cdFEuURnSpM3JbBNg8yfXLw88HgTOl+neUuy/l9W0No5NzGsLVydhBzTIxZP7yChQ=="],
"@inquirer/select": ["@inquirer/select@2.5.0", "", { "dependencies": { "@inquirer/core": "^9.1.0", "@inquirer/figures": "^1.0.5", "@inquirer/type": "^1.5.3", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" } }, "sha512-YmDobTItPP3WcEI86GvPo+T2sRHkxxOq/kXmsBjHS5BVXUgvgZ5AfJjkvQvZr03T81NnI3KrrRuMzeuYUQRFOA=="],
@@ -484,17 +517,55 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
+ "@langchain/classic": ["@langchain/classic@1.0.5", "", { "dependencies": { "@langchain/openai": "1.1.3", "@langchain/textsplitters": "1.0.1", "handlebars": "^4.7.8", "js-yaml": "^4.1.1", "jsonpointer": "^5.0.1", "openapi-types": "^12.1.3", "p-retry": "^7.0.0", "uuid": "^10.0.0", "yaml": "^2.2.1", "zod": "^3.25.76 || ^4" }, "optionalDependencies": { "langsmith": "^0.3.64" }, "peerDependencies": { "@langchain/core": "^1.0.0", "cheerio": "*", "peggy": "^3.0.2", "typeorm": "*" }, "optionalPeers": ["cheerio", "peggy", "typeorm"] }, "sha512-yMlcuQ80iG0SAEzgym95oLS+bJZJlmsFrMb+qkwg5mzHfL9DzAIFyvaMPiDnwKM0iv52u7iwD/aucLljZul9mQ=="],
+
+ "@langchain/community": ["@langchain/community@1.1.14", "", { "dependencies": { "@langchain/classic": "1.0.17", "@langchain/openai": "1.2.7", "binary-extensions": "^2.2.0", "flat": "^5.0.2", "js-yaml": "^4.1.1", "math-expression-evaluator": "^2.0.0", "uuid": "^10.0.0", "zod": "^3.25.76 || ^4" }, "peerDependencies": { "@arcjet/redact": "^v1.1.0", "@aws-crypto/sha256-js": "^5.0.0", "@aws-sdk/client-dynamodb": "^3.985.0", "@aws-sdk/client-lambda": "^3.985.0", "@aws-sdk/client-s3": "^3.985.0", "@aws-sdk/client-sagemaker-runtime": "^3.985.0", "@aws-sdk/client-sfn": "^3.985.0", "@aws-sdk/credential-provider-node": "^3.388.0", "@azure/search-documents": "^12.2.0", "@azure/storage-blob": "^12.30.0", "@browserbasehq/sdk": "*", "@browserbasehq/stagehand": "^1.0.0", "@clickhouse/client": "^0.2.5", "@datastax/astra-db-ts": "^1.0.0", "@elastic/elasticsearch": "^8.4.0", "@getmetal/metal-sdk": "*", "@getzep/zep-cloud": "^1.0.6", "@getzep/zep-js": "^0.9.0", "@gomomento/sdk-core": "^1.117.2", "@google-cloud/storage": "^6.10.1 || ^7.7.0", "@gradientai/nodejs-sdk": "^1.2.0", "@huggingface/inference": "^4.13.11", "@huggingface/transformers": "^3.8.1", "@ibm-cloud/watsonx-ai": "*", "@lancedb/lancedb": "^0.19.1", "@langchain/core": "^1.1.21", "@layerup/layerup-security": "^1.5.12", "@libsql/client": "^0.17.0", "@mendable/firecrawl-js": "^1.4.3", "@mlc-ai/web-llm": "*", "@mozilla/readability": "*", "@neondatabase/serverless": "*", "@notionhq/client": "^2.2.10", "@opensearch-project/opensearch": "*", "@planetscale/database": "^1.8.0", "@premai/prem-sdk": "^0.3.25", "@raycast/api": "^1.55.2", "@rockset/client": "^0.9.1", "@smithy/eventstream-codec": "^2.0.5", "@smithy/protocol-http": "^3.0.6", "@smithy/signature-v4": "^2.0.10", "@smithy/util-utf8": "^2.0.0", "@spider-cloud/spider-client": "^0.1.85", "@supabase/supabase-js": "^2.45.0", "@tensorflow-models/universal-sentence-encoder": "*", "@tensorflow/tfjs-core": "*", "@upstash/ratelimit": "^1.1.3 || ^2.0.3", "@upstash/redis": "^1.20.6", "@upstash/vector": "^1.1.1", "@vercel/kv": "*", "@vercel/postgres": "*", "@writerai/writer-sdk": "^0.40.2", "@xata.io/client": "^0.28.0", "@zilliz/milvus2-sdk-node": ">=2.3.5", "apify-client": "^2.22.0", "assemblyai": "^4.23.0", "azion": "^3.0.0", "better-sqlite3": ">=9.4.0 <12.0.0", "cassandra-driver": "^4.7.2", "cborg": "^4.5.8", "cheerio": "^1.0.0-rc.12", "chromadb": "*", "closevector-common": "0.1.3", "closevector-node": "0.1.6", "closevector-web": "0.1.6", "convex": "^1.3.1", "couchbase": "^4.6.0", "crypto-js": "^4.2.0", "d3-dsv": "^2.0.0", "discord.js": "^14.25.1", "duck-duck-scrape": "^2.2.5", "epub2": "^3.0.1", "faiss-node": "*", "fast-xml-parser": "*", "firebase-admin": "^13.6.1", "google-auth-library": "*", "googleapis": "*", "hnswlib-node": "^3.0.0", "html-to-text": "^9.0.5", "ibm-cloud-sdk-core": "*", "ignore": "^5.2.0", "interface-datastore": "^8.2.11", "ioredis": "^5.3.2", "it-all": "^3.0.4", "jsdom": "*", "jsonwebtoken": "^9.0.3", "lodash": "^4.17.23", "lunary": "^0.7.10", "mammoth": "^1.11.0", "mariadb": "^3.4.0", "mem0ai": "^2.1.8", "mysql2": "^3.16.3", "neo4j-driver": "*", "node-llama-cpp": ">=3.0.0", "notion-to-md": "^3.1.0", "officeparser": "^4.0.4", "openai": "*", "pdf-parse": "1.1.1", "pg": "^8.11.0", "pg-copy-streams": "^6.0.5", "pickleparser": "^0.2.1", "playwright": "^1.58.2", "portkey-ai": "^0.1.11", "puppeteer": "*", "pyodide": ">=0.24.1 <0.27.0", "replicate": "*", "sonix-speech-recognition": "^2.1.1", "srt-parser-2": "^1.2.3", "typeorm": "^0.3.28", "typesense": "^3.0.1", "usearch": "^1.1.1", "voy-search": "0.6.2", "word-extractor": "*", "ws": "^8.14.2", "youtubei.js": "*" }, "optionalPeers": ["@arcjet/redact", "@aws-crypto/sha256-js", "@aws-sdk/client-dynamodb", "@aws-sdk/client-lambda", "@aws-sdk/client-s3", "@aws-sdk/client-sagemaker-runtime", "@aws-sdk/client-sfn", "@aws-sdk/credential-provider-node", "@azure/search-documents", "@azure/storage-blob", "@browserbasehq/sdk", "@clickhouse/client", "@datastax/astra-db-ts", "@elastic/elasticsearch", "@getmetal/metal-sdk", "@getzep/zep-cloud", "@getzep/zep-js", "@gomomento/sdk-core", "@google-cloud/storage", "@gradientai/nodejs-sdk", "@huggingface/inference", "@huggingface/transformers", "@lancedb/lancedb", "@layerup/layerup-security", "@libsql/client", "@mendable/firecrawl-js", "@mlc-ai/web-llm", "@mozilla/readability", "@neondatabase/serverless", "@notionhq/client", "@opensearch-project/opensearch", "@planetscale/database", "@premai/prem-sdk", "@raycast/api", "@rockset/client", "@smithy/eventstream-codec", "@smithy/protocol-http", "@smithy/signature-v4", "@smithy/util-utf8", "@spider-cloud/spider-client", "@supabase/supabase-js", "@tensorflow-models/universal-sentence-encoder", "@tensorflow/tfjs-core", "@upstash/ratelimit", "@upstash/redis", "@upstash/vector", "@vercel/kv", "@vercel/postgres", "@writerai/writer-sdk", "@xata.io/client", "@zilliz/milvus2-sdk-node", "apify-client", "assemblyai", "azion", "better-sqlite3", "cassandra-driver", "cborg", "cheerio", "chromadb", "closevector-common", "closevector-node", "closevector-web", "convex", "couchbase", "crypto-js", "d3-dsv", "discord.js", "duck-duck-scrape", "epub2", "faiss-node", "fast-xml-parser", "firebase-admin", "google-auth-library", "googleapis", "hnswlib-node", "html-to-text", "ignore", "interface-datastore", "ioredis", "it-all", "jsdom", "jsonwebtoken", "lodash", "lunary", "mammoth", "mariadb", "mem0ai", "mysql2", "neo4j-driver", "node-llama-cpp", "notion-to-md", "officeparser", "pdf-parse", "pg", "pg-copy-streams", "pickleparser", "playwright", "portkey-ai", "puppeteer", "pyodide", "replicate", "sonix-speech-recognition", "srt-parser-2", "typeorm", "typesense", "usearch", "voy-search", "word-extractor", "ws", "youtubei.js"] }, "sha512-Jb64jqkjwocfK04RYW8oP9Z0VO3E1LfSlOx5QK3g7LJI7zQN8o1QzVvfXWigKQRuvMYue+X7DkunLaiEx/mohA=="],
+
+ "@langchain/core": ["@langchain/core@1.1.31", "", { "dependencies": { "@cfworker/json-schema": "^4.0.2", "@standard-schema/spec": "^1.1.0", "ansi-styles": "^5.0.0", "camelcase": "6", "decamelize": "1.2.0", "js-tiktoken": "^1.0.12", "langsmith": ">=0.5.0 <1.0.0", "mustache": "^4.2.0", "p-queue": "^6.6.2", "uuid": "^11.1.0", "zod": "^3.25.76 || ^4" } }, "sha512-FxsgIUONjKaRpjx59sISgmb0OMCbAetPGyhzjGa2kX0y1f8LZ5xm9VB2db7W9HYWyLvzRWcMA51Uu4OSTJmtZQ=="],
+
+ "@langchain/langgraph": ["@langchain/langgraph@1.2.6", "", { "dependencies": { "@langchain/langgraph-checkpoint": "^1.0.1", "@langchain/langgraph-sdk": "~1.8.1", "@standard-schema/spec": "1.1.0", "uuid": "^10.0.0" }, "peerDependencies": { "@langchain/core": "^1.1.16", "zod": "^3.25.32 || ^4.2.0", "zod-to-json-schema": "^3.x" }, "optionalPeers": ["zod-to-json-schema"] }, "sha512-5cX402dNGN6w9+0mlMU2dgUiKx6Y1tlENp7x05e9ByDbQCHSDc0kyqRWNFLGc7vatQ92S4ylxQrcCJvi8Fr4SQ=="],
+
+ "@langchain/langgraph-checkpoint": ["@langchain/langgraph-checkpoint@1.0.1", "", { "dependencies": { "uuid": "^10.0.0" }, "peerDependencies": { "@langchain/core": "^1.0.1" } }, "sha512-HM0cJLRpIsSlWBQ/xuDC67l52SqZ62Bh2Y61DX+Xorqwoh5e1KxYvfCD7GnSTbWWhjBOutvnR0vPhu4orFkZfw=="],
+
+ "@langchain/langgraph-sdk": ["@langchain/langgraph-sdk@1.8.1", "", { "dependencies": { "@types/json-schema": "^7.0.15", "p-queue": "^9.0.1", "p-retry": "^7.1.1", "uuid": "^13.0.0" }, "peerDependencies": { "@langchain/core": "^1.1.16", "react": "^18 || ^19", "react-dom": "^18 || ^19", "svelte": "^4.0.0 || ^5.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@langchain/core", "react", "react-dom", "svelte", "vue"] }, "sha512-le+rRwCN3wK7CEqxepraFK/aO5dOQ0heLNHBOcWugC6P6k1UqrqUBX+dBHZxcIz5d6n0FqierFyv74Ky8XIBew=="],
+
+ "@langchain/openai": ["@langchain/openai@1.1.3", "", { "dependencies": { "js-tiktoken": "^1.0.12", "openai": "^6.9.0", "zod": "^3.25.76 || ^4" }, "peerDependencies": { "@langchain/core": "^1.0.0" } }, "sha512-p+xR+4HRms5Ozjf5miC6U2AYRyNVSTdO7AMBkMYs1Tp6DWHBd+mQ72H8Ogd2dKrPuS5UDJ5dbpI1fS+OrTbgQQ=="],
+
+ "@langchain/textsplitters": ["@langchain/textsplitters@1.0.1", "", { "dependencies": { "js-tiktoken": "^1.0.12" }, "peerDependencies": { "@langchain/core": "^1.0.0" } }, "sha512-rheJlB01iVtrOUzttscutRgLybPH9qR79EyzBEbf1u97ljWyuxQfCwIWK+SjoQTM9O8M7GGLLRBSYE26Jmcoww=="],
+
"@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="],
"@mdx-js/react": ["@mdx-js/react@3.1.1", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw=="],
- "@mermaid-js/parser": ["@mermaid-js/parser@0.6.3", "", { "dependencies": { "langium": "3.3.1" } }, "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA=="],
+ "@mermaid-js/parser": ["@mermaid-js/parser@1.0.1", "", { "dependencies": { "langium": "^4.0.0" } }, "sha512-opmV19kN1JsK0T6HhhokHpcVkqKpF+x2pPDKKM2ThHtZAB5F4PROopk0amuVYK5qMrIA4erzpNm8gmPNJgMDxQ=="],
+
+ "@n8n/ai-node-sdk": ["@n8n/ai-node-sdk@0.4.1", "", { "dependencies": { "@n8n/ai-utilities": "0.7.1" } }, "sha512-ntGncJGZJ37B2s8dGbMLEo9+KW2vAHLn+DhTDHPUYnNut/p7V2gQzzC4ltDLkefChcaMqL0P8l79ntCnJ2pIaA=="],
+
+ "@n8n/ai-utilities": ["@n8n/ai-utilities@0.7.1", "", { "dependencies": { "@langchain/classic": "1.0.5", "@langchain/community": "1.1.14", "@langchain/core": "1.1.31", "@langchain/openai": "1.1.3", "@langchain/textsplitters": "1.0.1", "@n8n/config": "2.12.1", "@n8n/typescript-config": "1.3.0", "https-proxy-agent": "7.0.6", "js-tiktoken": "1.0.12", "langchain": "1.2.30", "proxy-from-env": "^1.1.0", "tmp-promise": "3.0.3", "undici": "^6.21.0", "zod": "3.25.67", "zod-to-json-schema": "3.23.3" }, "peerDependencies": { "n8n-workflow": "*" } }, "sha512-+VDOInOIh2M3W5o1O77oeip1NEpmhcR5GcEHaJpa9Cpw5B0pgU848WGmK3NDPwCZniOAuRFEqcvBu1W/+S5qLw=="],
+
+ "@n8n/config": ["@n8n/config@2.12.1", "", { "dependencies": { "@n8n/di": "0.10.0", "reflect-metadata": "0.2.2", "zod": "3.25.67" } }, "sha512-rgNrFJvlheRT5rH8KPL589Xcc6B4gtcSRcFuEPuNVeJ1f0XLliKWf9M3ui120e4jtQ9AhRCVGwmIaGN538WrHw=="],
+
+ "@n8n/di": ["@n8n/di@0.10.0", "", { "dependencies": { "reflect-metadata": "0.2.2" } }, "sha512-wqVjmb/tfE1Dyax6607K5InLM89pHZvmVTfakv6y17XJSwu2JbkT/+FI1z2tQswoSlhBIH3N1BZSiYsVbP58bw=="],
+
+ "@n8n/errors": ["@n8n/errors@0.6.0", "", { "dependencies": { "callsites": "3.1.0" } }, "sha512-oVJ0lgRYJY6/aPOW2h37ea5T+nX7/wULRn5FymwYeaiYlsLdqwIQEtGwZrajpzxJB0Os74u4lSH3WWQgZCkgxQ=="],
+
+ "@n8n/eslint-plugin-community-nodes": ["@n8n/eslint-plugin-community-nodes@0.9.0", "", { "dependencies": { "@typescript-eslint/utils": "^8.35.0", "fastest-levenshtein": "1.0.16" }, "peerDependencies": { "eslint": ">= 9" } }, "sha512-8PH129M3W34ihNTj79hXq95++puWUMGm7wTRH5NiLZZbiLyGwyv2CpZcT92uBehQ2pYc26TxzEbIvp8+MCU2Ow=="],
+
+ "@n8n/expression-runtime": ["@n8n/expression-runtime@0.5.0", "", { "dependencies": { "@n8n/tournament": "1.0.6", "isolated-vm": "^6.0.2", "js-base64": "3.7.2", "jssha": "3.3.1", "lodash": "4.17.23", "luxon": "3.7.2", "md5": "2.3.0", "title-case": "3.0.3", "transliteration": "2.3.5" } }, "sha512-DqkF79FyuljdjlSz1i6PfbrQ47w5U/fIqjoyAbRVxr+YatiM3RAe0zhfQ1ur1lV5QTq0BHnY8zsfFQ+DIItPhA=="],
+
+ "@n8n/node-cli": ["@n8n/node-cli@0.23.1", "", { "dependencies": { "@clack/prompts": "^0.11.0", "@n8n/ai-node-sdk": "0.4.1", "@n8n/eslint-plugin-community-nodes": "0.9.0", "@oclif/core": "^4.5.2", "change-case": "^5.4.4", "eslint-import-resolver-typescript": "^4.4.3", "eslint-plugin-import-x": "^4.15.2", "eslint-plugin-n8n-nodes-base": "1.16.5", "fast-glob": "3.2.12", "handlebars": "4.7.8", "picocolors": "1.0.1", "prettier": "3.6.2", "prompts": "^2.4.2", "rimraf": "6.0.1", "ts-morph": "^27.0.2", "typescript-eslint": "^8.35.0" }, "peerDependencies": { "eslint": ">= 9" }, "bin": { "n8n-node": "bin/n8n-node.mjs" } }, "sha512-bl5qL92Ymjl4+nkk4D57007W/+XbBHnuHlMiwMjKR68NQeolCYNqKwXlmAsAgEauZTWe0Rqueybr90FY2rML7w=="],
+
+ "@n8n/tournament": ["@n8n/tournament@1.0.6", "", { "dependencies": { "@n8n_io/riot-tmpl": "^4.0.1", "ast-types": "^0.16.1", "esprima-next": "^5.8.4", "recast": "^0.22.0" } }, "sha512-UGSxYXXVuOX0yL6HTLBStKYwLIa0+JmRKiSZSCMcM2s2Wax984KWT6XIA1TR/27i7yYpDk1MY14KsTPnuEp27A=="],
+
+ "@n8n/typescript-config": ["@n8n/typescript-config@1.3.0", "", {}, "sha512-wnrHUHdyfL8PgwwwBDWUaEnRRjszLYMVv5NzXnPtRaiewz2reOWeruhFTL0aPjJioYT2LcB9dLelCA44ytnXAA=="],
+
+ "@n8n_io/riot-tmpl": ["@n8n_io/riot-tmpl@4.0.1", "", { "dependencies": { "eslint-config-riot": "^1.0.0" } }, "sha512-/zdRbEfTFjsm1NqnpPQHgZTkTdbp5v3VUxGeMA9098sps8jRCTraQkc3AQstJgHUm7ylBXJcIVhnVeLUMWAfwQ=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
"@next/env": ["@next/env@16.0.10", "", {}, "sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang=="],
- "@next/eslint-plugin-next": ["@next/eslint-plugin-next@16.1.6", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ=="],
+ "@next/eslint-plugin-next": ["@next/eslint-plugin-next@16.2.1", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-r0epZGo24eT4g08jJlg2OEryBphXqO8aL18oajoTKLzHJ6jVr6P6FI58DLMug04MwD3j8Fj0YK0slyzneKVyzA=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.0.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4XgdKtdVsaflErz+B5XeG0T5PeXKDdruDf3CRpnhN+8UebNa5N2H58+3GDgpn/9GBurrQ1uWW768FfscwYkJRg=="],
@@ -520,57 +591,57 @@
"@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="],
- "@oclif/core": ["@oclif/core@4.8.3", "", { "dependencies": { "ansi-escapes": "^4.3.2", "ansis": "^3.17.0", "clean-stack": "^3.0.1", "cli-spinners": "^2.9.2", "debug": "^4.4.3", "ejs": "^3.1.10", "get-package-type": "^0.1.0", "indent-string": "^4.0.0", "is-wsl": "^2.2.0", "lilconfig": "^3.1.3", "minimatch": "^10.2.4", "semver": "^7.7.3", "string-width": "^4.2.3", "supports-color": "^8", "tinyglobby": "^0.2.14", "widest-line": "^3.1.0", "wordwrap": "^1.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-f7Rc1JBZO0wNMyDmNzP5IFOv5eM97S9pO4JUFdu2OLyk73YeBI9wog1Yyf666NOQvyptkbG1xh8inzMDQLNTyQ=="],
+ "@oclif/core": ["@oclif/core@4.10.3", "", { "dependencies": { "ansi-escapes": "^4.3.2", "ansis": "^3.17.0", "clean-stack": "^3.0.1", "cli-spinners": "^2.9.2", "debug": "^4.4.3", "ejs": "^3.1.10", "get-package-type": "^0.1.0", "indent-string": "^4.0.0", "is-wsl": "^2.2.0", "lilconfig": "^3.1.3", "minimatch": "^10.2.4", "semver": "^7.7.3", "string-width": "^4.2.3", "supports-color": "^8", "tinyglobby": "^0.2.14", "widest-line": "^3.1.0", "wordwrap": "^1.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-0mD8vcrrX5uRsxzvI8tbWmSVGngvZA/Qo6O0ZGvLPAWEauSf5GFniwgirhY0SkszuHwu0S1J1ivj/jHmqtIDuA=="],
- "@oclif/plugin-autocomplete": ["@oclif/plugin-autocomplete@3.2.40", "", { "dependencies": { "@oclif/core": "^4", "ansis": "^3.16.0", "debug": "^4.4.1", "ejs": "^3.1.10" } }, "sha512-HCfDuUV3l5F5Wz7SKkaoFb+OMQ5vKul8zvsPNgI0QbZcQuGHmn3svk+392wSfXboyA1gq8kzEmKPAoQK6r6UNw=="],
+ "@oclif/plugin-autocomplete": ["@oclif/plugin-autocomplete@3.2.42", "", { "dependencies": { "@oclif/core": "^4", "ansis": "^3.16.0", "debug": "^4.4.1", "ejs": "^3.1.10" } }, "sha512-VPL/XJDKhtDxZLTGdtbrAW3FQyCvTaFldBFs+u6XdeSN8PazmU+K8oPiVUsrm1jUYUx3HeVTopce7ap5t+8TFg=="],
- "@oclif/plugin-help": ["@oclif/plugin-help@6.2.37", "", { "dependencies": { "@oclif/core": "^4" } }, "sha512-5N/X/FzlJaYfpaHwDC0YHzOzKDWa41s9t+4FpCDu4f9OMReds4JeNBaaWk9rlIzdKjh2M6AC5Q18ORfECRkHGA=="],
+ "@oclif/plugin-help": ["@oclif/plugin-help@6.2.40", "", { "dependencies": { "@oclif/core": "^4" } }, "sha512-sU/PMrz1LnnnNk4T3qvZU8dTUiSc0MZaL7woh2wfuNSXbCnxicJzx4kX1sYeY6eF0NmqFiYlpNEQJykBG0g1sA=="],
- "@oclif/plugin-not-found": ["@oclif/plugin-not-found@3.2.74", "", { "dependencies": { "@inquirer/prompts": "^7.10.1", "@oclif/core": "^4.8.0", "ansis": "^3.17.0", "fast-levenshtein": "^3.0.0" } }, "sha512-6RD/EuIUGxAYR45nMQg+nw+PqwCXUxkR6Eyn+1fvbVjtb9d+60OPwB77LCRUI4zKNI+n0LOFaMniEdSpb+A7kQ=="],
+ "@oclif/plugin-not-found": ["@oclif/plugin-not-found@3.2.77", "", { "dependencies": { "@inquirer/prompts": "^7.10.1", "@oclif/core": "^4.10.2", "ansis": "^3.17.0", "fast-levenshtein": "^3.0.0" } }, "sha512-bU9lpYYk8aTafGFbsEoj88KLqJGFcY2w84abcuAUHsGgwpGA/G67Z3DwzaSkfuH6HZ58orC3ueEKGCMpF5nUDQ=="],
- "@oclif/plugin-warn-if-update-available": ["@oclif/plugin-warn-if-update-available@3.1.55", "", { "dependencies": { "@oclif/core": "^4", "ansis": "^3.17.0", "debug": "^4.4.3", "http-call": "^5.2.2", "lodash": "^4.17.23", "registry-auth-token": "^5.1.1" } }, "sha512-VIEBoaoMOCjl3y+w/kdfZMODi0mVMnDuM0vkBf3nqeidhRXVXq87hBqYDdRwN1XoD+eDfE8tBbOP7qtSOONztQ=="],
+ "@oclif/plugin-warn-if-update-available": ["@oclif/plugin-warn-if-update-available@3.1.57", "", { "dependencies": { "@oclif/core": "^4", "ansis": "^3.17.0", "debug": "^4.4.3", "http-call": "^5.2.2", "lodash": "^4.17.23", "registry-auth-token": "^5.1.1" } }, "sha512-y8BiMMiX3gnDO3kSck7R61bB74N8SI38pN9LbpaDlhZcjcN27wuIR5trePFxTxx85iow1YC5qvzYtwUZsDVjXg=="],
- "@oclif/test": ["@oclif/test@4.1.16", "", { "dependencies": { "ansis": "^3.17.0", "debug": "^4.4.3" }, "peerDependencies": { "@oclif/core": ">= 3.0.0" } }, "sha512-LPrF++WGGBE0pe3GUkzEteI5WrwTT7usGpIMSxkyJhYnFXKkwASyTcCmOhNH4QC65kqsLt1oBA88BMkCJqPtxg=="],
+ "@oclif/test": ["@oclif/test@4.1.17", "", { "dependencies": { "ansis": "^3.17.0", "debug": "^4.4.3" }, "peerDependencies": { "@oclif/core": ">= 3.0.0" } }, "sha512-OaD6/2vW9MqL58ZtaTGO1wc2vnPxZ/LLN0qp/+HVdMsBt/UDubxZreC3cxGR9rT8SMfyBvGIU8MzmZEBuiikAQ=="],
- "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.17.1", "", { "os": "android", "cpu": "arm" }, "sha512-+VuZyMYYaap5uDAU1xDU3Kul0FekLqpBS8kI5JozlWfYQKnc/HsZg2gHPkQrj0SC9lt74WMNCfOzZZJlYXSdEQ=="],
+ "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.19.1", "", { "os": "android", "cpu": "arm" }, "sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg=="],
- "@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.17.1", "", { "os": "android", "cpu": "arm64" }, "sha512-YlDDTjvOEKhom/cRSVsXsMVeXVIAM9PJ/x2mfe08rfuS0iIEfJd8PngKbEIhG72WPxleUa+vkEZj9ncmC14z3Q=="],
+ "@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.19.1", "", { "os": "android", "cpu": "arm64" }, "sha512-oolbkRX+m7Pq2LNjr/kKgYeC7bRDMVTWPgxBGMjSpZi/+UskVo4jsMU3MLheZV55jL6c3rNelPl4oD60ggYmqA=="],
- "@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.17.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HOYYLSY4JDk14YkXaz/ApgJYhgDP4KsG8EZpgpOxdszGW9HmIMMY/vXqVKYW74dSH+GQkIXYxBrEh3nv+XODVg=="],
+ "@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.19.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-nUC6d2i3R5B12sUW4O646qD5cnMXf2oBGPLIIeaRfU9doJRORAbE2SGv4eW6rMqhD+G7nf2Y8TTJTLiiO3Q/dQ=="],
- "@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.17.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-JHPJbsa5HvPq2/RIdtGlqfaG9zV2WmgvHrKTYmlW0L5esqtKCBuetFudXTBzkNcyD69kSZLzH92AzTr6vFHMFg=="],
+ "@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.19.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-cV50vE5+uAgNcFa3QY1JOeKDSkM/9ReIcc/9wn4TavhW/itkDGrXhw9jaKnkQnGbjJ198Yh5nbX/Gr2mr4Z5jQ=="],
- "@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.17.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UD1FRC8j8xZstFXYsXwQkNmmg7vUbee006IqxokwDUUA+xEgKZDpLhBEiVKM08Urb+bn7Q0gn6M1pyNR0ng5mg=="],
+ "@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.19.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-xZOQiYGFxtk48PBKff+Zwoym7ScPAIVp4c14lfLxizO2LTTTJe5sx9vQNGrBymrf/vatSPNMD4FgsaaRigPkqw=="],
- "@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.17.1", "", { "os": "linux", "cpu": "arm" }, "sha512-wFWC1wyf2ROFWTxK5x0Enm++DSof3EBQ/ypyAesMDLiYxOOASDoMOZG1ylWUnlKaCt5W7eNOWOzABpdfFf/ssA=="],
+ "@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.19.1", "", { "os": "linux", "cpu": "arm" }, "sha512-lXZYWAC6kaGe/ky2su94e9jN9t6M0/6c+GrSlCqL//XO1cxi5lpAhnJYdyrKfm0ZEr/c7RNyAx3P7FSBcBd5+A=="],
- "@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.17.1", "", { "os": "linux", "cpu": "arm" }, "sha512-k/hUif0GEBk/csSqCfTPXb8AAVs1NNWCa/skBghvNbTtORcWfOVqJ3mM+2pE189+enRm4UnryLREu5ysI0kXEQ=="],
+ "@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.19.1", "", { "os": "linux", "cpu": "arm" }, "sha512-veG1kKsuK5+t2IsO9q0DErYVSw2azvCVvWHnfTOS73WE0STdLLB7Q1bB9WR+yHPQM76ASkFyRbogWo1GR1+WbQ=="],
- "@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.17.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Cwm6A071ww60QouJ9LoHAwBgEoZzHQ0Qaqk2E7WLfBdiQN9mLXIDhnrpn04hlRElRPhLiu/dtg+o5PPLvaINXQ=="],
+ "@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.19.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-heV2+jmXyYnUrpUXSPugqWDRpnsQcDm2AX4wzTuvgdlZfoNYO0O3W2AVpJYaDn9AG4JdM6Kxom8+foE7/BcSig=="],
- "@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.17.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-+hwlE2v3m0r3sk93SchJL1uyaKcPjf+NGO/TD2DZUDo+chXx7FfaEj0nUMewigSt7oZ2sQN9Z4NJOtUa75HE5Q=="],
+ "@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.19.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jvo2Pjs1c9KPxMuMPIeQsgu0mOJF9rEb3y3TdpsrqwxRM+AN6/nDDwv45n5ZrUnQMsdBy5gIabioMKnQfWo9ew=="],
- "@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.17.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-bO+rsaE5Ox8cFyeL5Ct5tzot1TnQpFa/Wmu5k+hqBYSH2dNVDGoi0NizBN5QV8kOIC6O5MZr81UG4yW/2FyDTA=="],
+ "@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.19.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-vLmdNxWCdN7Uo5suays6A/+ywBby2PWBBPXctWPg5V0+eVuzsJxgAn6MMB4mPlshskYbppjpN2Zg83ArHze9gQ=="],
- "@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.17.1", "", { "os": "linux", "cpu": "none" }, "sha512-B/P+hxKQ1oX4YstI9Lyh4PGzqB87Ddqj/A4iyRBbPdXTcxa+WW3oRLx1CsJKLmHPdDk461Hmbghq1Bm3pl+8Aw=="],
+ "@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.19.1", "", { "os": "linux", "cpu": "none" }, "sha512-/b+WgR+VTSBxzgOhDO7TlMXC1ufPIMR6Vj1zN+/x+MnyXGW7prTLzU9eW85Aj7Th7CCEG9ArCbTeqxCzFWdg2w=="],
- "@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.17.1", "", { "os": "linux", "cpu": "none" }, "sha512-ulp2H3bFXzd/th2maH+QNKj5qgOhJ3v9Yspdf1svTw3CDOuuTl6sRKsWQ7MUw0vnkSNvQndtflBwVXgzZvURsQ=="],
+ "@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.19.1", "", { "os": "linux", "cpu": "none" }, "sha512-YlRdeWb9j42p29ROh+h4eg/OQ3dTJlpHSa+84pUM9+p6i3djtPz1q55yLJhgW9XfDch7FN1pQ/Vd6YP+xfRIuw=="],
- "@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.17.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-LAXYVe3rKk09Zo9YKF2ZLBcH8sz8Oj+JIyiUxiHtq0hiYLMsN6dOpCf2hzQEjPAmsSEA/hdC1PVKeXo+oma8mQ=="],
+ "@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.19.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-EDpafVOQWF8/MJynsjOGFThcqhRHy417sRyLfQmeiamJ8qVhSKAn2Dn2VVKUGCjVB9C46VGjhNo7nOPUi1x6uA=="],
- "@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.17.1", "", { "os": "linux", "cpu": "x64" }, "sha512-3RAhxipMKE8RCSPn7O//sj440i+cYTgYbapLeOoDvQEt6R1QcJjTsFgI4iz99FhVj3YbPxlZmcLB5VW+ipyRTA=="],
+ "@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.19.1", "", { "os": "linux", "cpu": "x64" }, "sha512-NxjZe+rqWhr+RT8/Ik+5ptA3oz7tUw361Wa5RWQXKnfqwSSHdHyrw6IdcTfYuml9dM856AlKWZIUXDmA9kkiBQ=="],
- "@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.17.1", "", { "os": "linux", "cpu": "x64" }, "sha512-wpjMEubGU8r9VjZTLdZR3aPHaBqTl8Jl8F4DBbgNoZ+yhkhQD1/MGvY70v2TLnAI6kAHSvcqgfvaqKDa2iWsPQ=="],
+ "@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.19.1", "", { "os": "linux", "cpu": "x64" }, "sha512-cM/hQwsO3ReJg5kR+SpI69DMfvNCp+A/eVR4b4YClE5bVZwz8rh2Nh05InhwI5HR/9cArbEkzMjcKgTHS6UaNw=="],
- "@oxc-resolver/binding-openharmony-arm64": ["@oxc-resolver/binding-openharmony-arm64@11.17.1", "", { "os": "none", "cpu": "arm64" }, "sha512-XIE4w17RYAVIgx+9Gs3deTREq5tsmalbatYOOBGNdH7n0DfTE600c7wYXsp7ANc3BPDXsInnOzXDEPCvO1F6cg=="],
+ "@oxc-resolver/binding-openharmony-arm64": ["@oxc-resolver/binding-openharmony-arm64@11.19.1", "", { "os": "none", "cpu": "arm64" }, "sha512-QF080IowFB0+9Rh6RcD19bdgh49BpQHUW5TajG1qvWHvmrQznTZZjYlgE2ltLXyKY+qs4F/v5xuX1XS7Is+3qA=="],
- "@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.17.1", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-Lqi5BlHX3zS4bpSOkIbOKVf7DIk6Gvmdifr2OuOI58eUUyP944M8/OyaB09cNpPy9Vukj7nmmhOzj8pwLgAkIg=="],
+ "@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.19.1", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-w8UCKhX826cP/ZLokXDS6+milN8y4X7zidsAttEdWlVoamTNf6lhBJldaWr3ukTDiye7s4HRcuPEPOXNC432Vg=="],
- "@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.17.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-l6lTcLBQVj1HNquFpXSsrkCIM8X5Hlng5YNQJrg00z/KyovvDV5l3OFhoRyZ+aLBQ74zUnMRaJZC7xcBnHyeNg=="],
+ "@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.19.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-nJ4AsUVZrVKwnU/QRdzPCCrO0TrabBqgJ8pJhXITdZGYOV28TIYystV1VFLbQ7DtAcaBHpocT5/ZJnF78YJPtQ=="],
- "@oxc-resolver/binding-win32-ia32-msvc": ["@oxc-resolver/binding-win32-ia32-msvc@11.17.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-VTzVtfnCCsU/6GgvursWoyZrhe3Gj/RyXzDWmh4/U1Y3IW0u1FZbp+hCIlBL16pRPbDc5YvXVtCOnA41QOrOoQ=="],
+ "@oxc-resolver/binding-win32-ia32-msvc": ["@oxc-resolver/binding-win32-ia32-msvc@11.19.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-EW+ND5q2Tl+a3pH81l1QbfgbF3HmqgwLfDfVithRFheac8OTcnbXt/JxqD2GbDkb7xYEqy1zNaVFRr3oeG8npA=="],
- "@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.17.1", "", { "os": "win32", "cpu": "x64" }, "sha512-jRPVU+6/12baj87q2+UGRh30FBVBzqKdJ7rP/mSqiL1kpNQB9yZ1j0+m3sru1m+C8hiFK7lBFwjUtYUBI7+UpQ=="],
+ "@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.19.1", "", { "os": "win32", "cpu": "x64" }, "sha512-6hIU3RQu45B+VNTY4Ru8ppFwjVS/S5qwYyGhBotmjxfEKk41I2DlGtRfGJndZ5+6lneE2pwloqunlOyZuX/XAw=="],
"@pachca/cli": ["@pachca/cli@workspace:packages/cli"],
@@ -594,6 +665,10 @@
"@pachca/swift": ["@pachca/swift@workspace:sdk/swift"],
+ "@package-json/types": ["@package-json/types@0.0.12", "", {}, "sha512-uu43FGU34B5VM9mCNjXCwLaGHYjXdNincqKLaraaCW+7S2+SmiBg1Nv8bPnmschrIfZmfKNY9f3fC376MRrObw=="],
+
+ "@playwright/test": ["@playwright/test@1.58.2", "", { "dependencies": { "playwright": "1.58.2" }, "bin": { "playwright": "cli.js" } }, "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA=="],
+
"@pnpm/config.env-replace": ["@pnpm/config.env-replace@1.1.0", "", {}, "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w=="],
"@pnpm/network.ca-file": ["@pnpm/network.ca-file@1.0.2", "", { "dependencies": { "graceful-fs": "4.2.10" } }, "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA=="],
@@ -662,67 +737,67 @@
"@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="],
- "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="],
+ "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.0", "", { "os": "android", "cpu": "arm" }, "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A=="],
- "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="],
+ "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.0", "", { "os": "android", "cpu": "arm64" }, "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw=="],
- "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="],
+ "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA=="],
- "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w=="],
+ "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw=="],
- "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.59.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA=="],
+ "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw=="],
- "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg=="],
+ "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA=="],
- "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw=="],
+ "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.0", "", { "os": "linux", "cpu": "arm" }, "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g=="],
- "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA=="],
+ "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ=="],
- "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA=="],
+ "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A=="],
- "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA=="],
+ "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ=="],
- "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg=="],
+ "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.0", "", { "os": "linux", "cpu": "none" }, "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw=="],
- "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q=="],
+ "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.0", "", { "os": "linux", "cpu": "none" }, "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog=="],
- "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA=="],
+ "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ=="],
- "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA=="],
+ "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg=="],
- "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg=="],
+ "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.0", "", { "os": "linux", "cpu": "none" }, "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA=="],
- "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg=="],
+ "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.0", "", { "os": "linux", "cpu": "none" }, "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ=="],
- "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.59.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w=="],
+ "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ=="],
- "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg=="],
+ "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.0", "", { "os": "linux", "cpu": "x64" }, "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg=="],
- "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg=="],
+ "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.0", "", { "os": "linux", "cpu": "x64" }, "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw=="],
- "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.59.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ=="],
+ "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw=="],
- "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.59.0", "", { "os": "none", "cpu": "arm64" }, "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA=="],
+ "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.0", "", { "os": "none", "cpu": "arm64" }, "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA=="],
- "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A=="],
+ "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ=="],
- "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.59.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA=="],
+ "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w=="],
- "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA=="],
+ "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.0", "", { "os": "win32", "cpu": "x64" }, "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA=="],
- "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="],
+ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.0", "", { "os": "win32", "cpu": "x64" }, "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w=="],
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
- "@scalar/helpers": ["@scalar/helpers@0.2.11", "", {}, "sha512-Y7DLt1bIZF9dvHzJwSJTcC1lpSr1Tbf4VBhHOCRIHu23Rr7/lhQnddRxFmPV1tZXwEQKz7F7yRrubwCfKPCucw=="],
+ "@scalar/helpers": ["@scalar/helpers@0.2.18", "", {}, "sha512-w1d4tpNEVZ293oB2BAgLrS0kVPUtG3eByNmOCJA5eK9vcT4D3cmsGtWjUaaqit0BQCsBFHK51rasGvSWnApYTw=="],
- "@scalar/json-magic": ["@scalar/json-magic@0.9.6", "", { "dependencies": { "@scalar/helpers": "0.2.11", "yaml": "^2.8.0" } }, "sha512-2TKoqkAophHti1nH+rvQlR4lhD6X9tqQpuNeAE0cytHSX/yndkSOE0yA7cep5T9tFjGN4Km0gMnelvY3LgWs4A=="],
+ "@scalar/json-magic": ["@scalar/json-magic@0.11.7", "", { "dependencies": { "@scalar/helpers": "0.2.18", "pathe": "^2.0.3", "yaml": "^2.8.0" } }, "sha512-GVz9E0vXu+ecypkdn0biK1gbQVkK4QTTX1Hq3eMgxlLQC91wwiqWfCqwfhuX0LRu+Z5OmYhLhufDJEEh56rVgA=="],
- "@scalar/openapi-parser": ["@scalar/openapi-parser@0.24.10", "", { "dependencies": { "@scalar/helpers": "0.2.12", "@scalar/json-magic": "0.11.1", "@scalar/openapi-types": "0.5.3", "@scalar/openapi-upgrader": "0.1.8", "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.8.0" } }, "sha512-E9K8OYD7XKHsvTyLTSdILKHbm4Q3n/MA3EGdDTEBLJHSJd1vLOwiJzrp3+h+xiqFxlX7vlecInZvFy/3c1fqPg=="],
+ "@scalar/openapi-parser": ["@scalar/openapi-parser@0.24.17", "", { "dependencies": { "@scalar/helpers": "0.2.18", "@scalar/json-magic": "0.11.7", "@scalar/openapi-types": "0.5.4", "@scalar/openapi-upgrader": "0.1.11", "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.8.0" } }, "sha512-aM9UVrzlMreC3X/sZbyj+7XDZmat3ecGC3RpU8dqEO/HIH+CEX0xMLuP+41DhePCYg5+9TtDomSfWuMq4x1Z1A=="],
- "@scalar/openapi-types": ["@scalar/openapi-types@0.5.3", "", { "dependencies": { "zod": "^4.1.11" } }, "sha512-m4n/Su3K01d15dmdWO1LlqecdSPKuNjuokrJLdiQ485kW/hRHbXW1QP6tJL75myhw/XhX5YhYAR+jrwnGjXiMw=="],
+ "@scalar/openapi-types": ["@scalar/openapi-types@0.5.4", "", { "dependencies": { "zod": "^4.3.5" } }, "sha512-2pEbhprh8lLGDfUI6mNm9EV104pjb3+aJsXrFaqfgOSre7r6NlgM5HcSbsLjzDAnTikjJhJ3IMal1Rz8WVwiOw=="],
- "@scalar/openapi-upgrader": ["@scalar/openapi-upgrader@0.1.8", "", { "dependencies": { "@scalar/openapi-types": "0.5.3" } }, "sha512-2xuYLLs0fBadLIk4I1ObjMiCnOyLPEMPf24A1HtHQvhKGDnGlvT63F2rU2Xw8lxCjgHnzveMPnOJEbwIy64RCg=="],
+ "@scalar/openapi-upgrader": ["@scalar/openapi-upgrader@0.1.11", "", { "dependencies": { "@scalar/openapi-types": "0.5.4" } }, "sha512-ngJcHGoCHmpWgYtNy08vmzFfLdQEkMpvaCQqNPPMNKq0QEXOv89e/rn+TZJZgPnRlY7fDIoIhn9lNgr+azBW+w=="],
"@shikijs/core": ["@shikijs/core@1.29.2", "", { "dependencies": { "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.4" } }, "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ=="],
@@ -742,141 +817,169 @@
"@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="],
- "@smithy/abort-controller": ["@smithy/abort-controller@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-qocxM/X4XGATqQtUkbE9SPUB6wekBi+FyJOMbPj0AhvyvFGYEmOlz6VB22iMePCQsFmMIvFSeViDvA7mZJG47g=="],
+ "@smithy/abort-controller": ["@smithy/abort-controller@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q=="],
- "@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-y5d4xRiD6TzeP5BWlb+Ig/VFqF+t9oANNhGeMqyzU7obw7FYgTgVi50i5JqBTeKp+TABeDIeeXFZdz65RipNtA=="],
+ "@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw=="],
- "@smithy/chunked-blob-reader-native": ["@smithy/chunked-blob-reader-native@4.2.2", "", { "dependencies": { "@smithy/util-base64": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-QzzYIlf4yg0w5TQaC9VId3B3ugSk1MI/wb7tgcHtd7CBV9gNRKZrhc2EPSxSZuDy10zUZ0lomNMgkc6/VVe8xg=="],
+ "@smithy/chunked-blob-reader-native": ["@smithy/chunked-blob-reader-native@4.2.3", "", { "dependencies": { "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw=="],
- "@smithy/config-resolver": ["@smithy/config-resolver@4.4.9", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.10", "@smithy/types": "^4.13.0", "@smithy/util-config-provider": "^4.2.1", "@smithy/util-endpoints": "^3.3.1", "@smithy/util-middleware": "^4.2.10", "tslib": "^2.6.2" } }, "sha512-ejQvXqlcU30h7liR9fXtj7PIAau1t/sFbJpgWPfiYDs7zd16jpH0IsSXKcba2jF6ChTXvIjACs27kNMc5xxE2Q=="],
+ "@smithy/config-resolver": ["@smithy/config-resolver@4.4.13", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg=="],
- "@smithy/core": ["@smithy/core@3.23.7", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.11", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.1", "@smithy/util-body-length-browser": "^4.2.1", "@smithy/util-middleware": "^4.2.10", "@smithy/util-stream": "^4.5.16", "@smithy/util-utf8": "^4.2.1", "@smithy/uuid": "^1.1.1", "tslib": "^2.6.2" } }, "sha512-/+ldRdtiO5Cb26afAZOG1FZM0x7D4AYdjpyOv2OScJw+4C7X+OLdRnNKF5UyUE0VpPgSKr3rnF/kvprRA4h2kg=="],
+ "@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="],
- "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.10", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.10", "@smithy/property-provider": "^4.2.10", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.10", "tslib": "^2.6.2" } }, "sha512-3bsMLJJLTZGZqVGGeBVFfLzuRulVsGTj12BzRKODTHqUABpIr0jMN1vN3+u6r2OfyhAQ2pXaMZWX/swBK5I6PQ=="],
+ "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="],
- "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.10", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-A4ynrsFFfSXUHicfTcRehytppFBcY3HQxEGYiyGktPIOye3Ot7fxpiy4VR42WmtGI4Wfo6OXt/c1Ky1nUFxYYQ=="],
+ "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.12", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA=="],
- "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.10", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-0xupsu9yj9oDVuQ50YCTS9nuSYhGlrwqdaKQel9y2Fz7LU9fNErVlw9N0o4pm4qqvWEGbSTI4HKc6XJfB30MVw=="],
+ "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A=="],
- "@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-8kn6sinrduk0yaYHMJDsNuiFpXwQwibR7n/4CDUqn4UgaG+SeBHu5jHGFdU9BLFAM7Q4/gvr9RYxBHz9/jKrhA=="],
+ "@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q=="],
- "@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.10", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-uUrxPGgIffnYfvIOUmBM5i+USdEBRTdh7mLPttjphgtooxQ8CtdO1p6K5+Q4BBAZvKlvtJ9jWyrWpBJYzBKsyQ=="],
+ "@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA=="],
- "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.10", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-aArqzOEvcs2dK+xQVCgLbpJQGfZihw8SD4ymhkwNTtwKbnrzdhJsFDKuMQnam2kF69WzgJYOU5eJlCx+CA32bw=="],
+ "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.12", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ=="],
- "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.10", "@smithy/querystring-builder": "^4.2.10", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-muS5tFw+A/uo+U+yig06vk1776UFM+aAp9hFM8efI4ZcHhTcgv6NTeK4x7ltHeMPBwnhEjcf0MULTyxNkSNxDw=="],
+ "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.15", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A=="],
- "@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.11", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.1", "@smithy/chunked-blob-reader-native": "^4.2.2", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-DrcAx3PM6AEbWZxsKl6CWAGnVwiz28Wp1ZhNu+Hi4uI/6C1PIZBIaPM2VoqBDAsOWbM6ZVzOEQMxFLLdmb4eBQ=="],
+ "@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.13", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.2", "@smithy/chunked-blob-reader-native": "^4.2.3", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YrF4zWKh+ghLuquldj6e/RzE3xZYL8wIPfkt0MqCRphVICjyyjH8OwKD7LLlKpVEbk4FLizFfC1+gwK6XQdR3g=="],
- "@smithy/hash-node": ["@smithy/hash-node@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-buffer-from": "^4.2.1", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-1VzIOI5CcsvMDvP3iv1vG/RfLJVVVc67dCRyLSB2Hn9SWCZrDO3zvcIzj3BfEtqRW5kcMg5KAeVf1K3dR6nD3w=="],
+ "@smithy/hash-node": ["@smithy/hash-node@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w=="],
- "@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-w78xsYrOlwXKwN5tv1GnKIRbHb1HygSpeZMP6xDxCPGf1U/xDHjCpJu64c5T35UKyEPwa0bPeIcvU69VY3khUA=="],
+ "@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-O3YbmGExeafuM/kP7Y8r6+1y0hIh3/zn6GROx0uNlB54K9oihAL75Qtc+jFfLNliTi6pxOAYZrRKD9A7iA6UFw=="],
- "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-vy9KPNSFUU0ajFYk0sDZIYiUlAWGEAhRfehIr5ZkdFrRFTAuXEPUd41USuqHU6vvLX4r6Q9X7MKBco5+Il0Org=="],
+ "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g=="],
- "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Yfu664Qbf1B4IYIsYgKoABt010daZjkaCRvdU/sPnZG6TtHOB0md0RjNdLGzxe5UIdn9js4ftPICzmkRa9RJ4Q=="],
+ "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="],
- "@smithy/md5-js": ["@smithy/md5-js@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-Op+Dh6dPLWTjWITChFayDllIaCXRofOed8ecpggTC5fkh8yXes0vAEX7gRUfjGK+TlyxoCAA05gHbZW/zB9JwQ=="],
+ "@smithy/md5-js": ["@smithy/md5-js@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-W/oIpHCpWU2+iAkfZYyGWE+qkpuf3vEXHLxQQDx9FPNZTTdnul0dZ2d/gUFrtQ5je1G2kp4cjG0/24YueG2LbQ=="],
- "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.10", "", { "dependencies": { "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-TQZ9kX5c6XbjhaEBpvhSvMEZ0klBs1CFtOdPFwATZSbC9UeQfKHPLPN9Y+I6wZGMOavlYTOlHEPDrt42PMSH9w=="],
+ "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA=="],
- "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.21", "", { "dependencies": { "@smithy/core": "^3.23.7", "@smithy/middleware-serde": "^4.2.11", "@smithy/node-config-provider": "^4.3.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.10", "@smithy/util-middleware": "^4.2.10", "tslib": "^2.6.2" } }, "sha512-CoVGZaqIC0tEjz0ga3ciwCMA5fd/4lIOwO2wx0fH+cTi1zxSFZnMJbIiIF9G1d4vRSDyTupDrpS3FKBBJGkRZg=="],
+ "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.27", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-serde": "^4.2.15", "@smithy/node-config-provider": "^4.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA=="],
- "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.38", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.10", "@smithy/protocol-http": "^5.3.10", "@smithy/service-error-classification": "^4.2.10", "@smithy/smithy-client": "^4.12.1", "@smithy/types": "^4.13.0", "@smithy/util-middleware": "^4.2.10", "@smithy/util-retry": "^4.2.10", "@smithy/uuid": "^1.1.1", "tslib": "^2.6.2" } }, "sha512-WdHvdhjE6Fj78vxFwDKFDwlqGOGRUWrwGeuENUbTVE46Su9mnQM+dXHtbnCaQvwuSYrRsjpe8zUsFpwUp/azlA=="],
+ "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.44", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/service-error-classification": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-Y1Rav7m5CFRPQyM4CI0koD/bXjyjJu3EQxZZhtLGD88WIrBrQ7kqXM96ncd6rYnojwOo/u9MXu57JrEvu/nLrA=="],
- "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.11", "", { "dependencies": { "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-STQdONGPwbbC7cusL60s7vOa6He6A9w2jWhoapL0mgVjmR19pr26slV+yoSP76SIssMTX/95e5nOZ6UQv6jolg=="],
+ "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.15", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg=="],
- "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-pmts/WovNcE/tlyHa8z/groPeOtqtEpp61q3W0nW1nDJuMq/x+hWa/OVQBtgU0tBqupeXq0VBOLA4UZwE8I0YA=="],
+ "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw=="],
- "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.10", "", { "dependencies": { "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-UALRbJtVX34AdP2VECKVlnNgidLHA2A7YgcJzwSBg1hzmnO/bZBHl/LDQQyYifzUwp1UOODnl9JJ3KNawpUJ9w=="],
+ "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.12", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw=="],
- "@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.13", "", { "dependencies": { "@smithy/abort-controller": "^4.2.10", "@smithy/protocol-http": "^5.3.10", "@smithy/querystring-builder": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-o8CP8w6tlUA0lk+Qfwm6Ed0jCWk3bEY6iBOJjdBaowbXKCSClk8zIHQvUL6RUZMvuNafF27cbRCMYqw6O1v4aA=="],
+ "@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A=="],
- "@smithy/property-provider": ["@smithy/property-provider@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-5jm60P0CU7tom0eNrZ7YrkgBaoLFXzmqB0wVS+4uK8PPGmosSrLNf6rRd50UBvukztawZ7zyA8TxlrKpF5z9jw=="],
+ "@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
- "@smithy/protocol-http": ["@smithy/protocol-http@5.3.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-2NzVWpYY0tRdfeCJLsgrR89KE3NTWT2wGulhNUxYlRmtRmPwLQwKzhrfVaiNlA9ZpJvbW7cjTVChYKgnkqXj1A=="],
+ "@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="],
- "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-uri-escape": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-HeN7kEvuzO2DmAzLukE9UryiUvejD3tMp9a1D1NJETerIfKobBUCLfviP6QEk500166eD2IATaXM59qgUI+YDA=="],
+ "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg=="],
- "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-4Mh18J26+ao1oX5wXJfWlTT+Q1OpDR8ssiC9PDOuEgVBGloqg18Fw7h5Ct8DyT9NBYwJgtJ2nLjKKFU6RP1G1Q=="],
+ "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw=="],
- "@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0" } }, "sha512-0R/+/Il5y8nB/By90o8hy/bWVYptbIfvoTYad0igYQO5RefhNCDmNzqxaMx7K1t/QWo0d6UynqpqN5cCQt1MCg=="],
+ "@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1" } }, "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ=="],
- "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-pHgASxl50rrtOztgQCPmOXFjRW+mCd7ALr/3uXNzRrRoGV5G2+78GOsQ3HlQuBVHCh9o6xqMNvlIKZjWn4Euug=="],
+ "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="],
- "@smithy/signature-v4": ["@smithy/signature-v4@5.3.10", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.1", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.1", "@smithy/util-middleware": "^4.2.10", "@smithy/util-uri-escape": "^4.2.1", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-Wab3wW8468WqTKIxI+aZe3JYO52/RYT/8sDOdzkUhjnLakLe9qoQqIcfih/qxcF4qWEFoWBszY0mj5uxffaVXA=="],
+ "@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="],
- "@smithy/smithy-client": ["@smithy/smithy-client@4.12.1", "", { "dependencies": { "@smithy/core": "^3.23.7", "@smithy/middleware-endpoint": "^4.4.21", "@smithy/middleware-stack": "^4.2.10", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "@smithy/util-stream": "^4.5.16", "tslib": "^2.6.2" } }, "sha512-Xf9UFHlAihewfkmLNZ6I/Ek6kcYBKoU3cbRS9Z4q++9GWoW0YFbAHs7wMbuXm+nGuKHZ5OKheZMuDdaWPv8DJw=="],
+ "@smithy/smithy-client": ["@smithy/smithy-client@4.12.7", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ=="],
- "@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="],
+ "@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="],
- "@smithy/url-parser": ["@smithy/url-parser@4.2.10", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-uypjF7fCDsRk26u3qHmFI/ePL7bxxB9vKkE+2WKEciHhz+4QtbzWiHRVNRJwU3cKhrYDYQE3b0MRFtqfLYdA4A=="],
+ "@smithy/url-parser": ["@smithy/url-parser@4.2.12", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA=="],
- "@smithy/util-base64": ["@smithy/util-base64@4.3.1", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.1", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-BKGuawX4Doq/bI/uEmg+Zyc36rJKWuin3py89PquXBIBqmbnJwBBsmKhdHfNEp0+A4TDgLmT/3MSKZ1SxHcR6w=="],
+ "@smithy/util-base64": ["@smithy/util-base64@4.3.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ=="],
- "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-SiJeLiozrAoCrgDBUgsVbmqHmMgg/2bA15AzcbcW+zan7SuyAVHN4xTSbq0GlebAIwlcaX32xacnrG488/J/6g=="],
+ "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ=="],
- "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4rHqBvxtJEBvsZcFQSPQqXP2b/yy/YlB66KlcEgcH2WNoOKCKB03DSLzXmOsXjbl8dJ4OEYTn31knhdznwk7zw=="],
+ "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g=="],
- "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.1", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-/swhmt1qTiVkaejlmMPPDgZhEaWb/HWMGRBheaxwuVkusp/z+ErJyQxO6kaXumOciZSWlmq6Z5mNylCd33X7Ig=="],
+ "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="],
- "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-462id/00U8JWFw6qBuTSWfN5TxOHvDu4WliI97qOIOnuC/g+NDAknTU8eoGXEPlLkRVgWEr03jJBLV4o2FL8+A=="],
+ "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ=="],
- "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.37", "", { "dependencies": { "@smithy/property-provider": "^4.2.10", "@smithy/smithy-client": "^4.12.1", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-JlPZhV1kQCGNJgofRTU6E8kHrjCKsb6cps8gco8QDVaFl7biFYzHg0p1x89ytIWyVyCkY3nOpO8tJPM47Vqlww=="],
+ "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.43", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Qd/0wCKMaXxev/z00TvNzGCH2jlKKKxXP1aDxB6oKwSQthe3Og2dMhSayGCnsma1bK/kQX1+X7SMP99t6FgiiQ=="],
- "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.40", "", { "dependencies": { "@smithy/config-resolver": "^4.4.9", "@smithy/credential-provider-imds": "^4.2.10", "@smithy/node-config-provider": "^4.3.10", "@smithy/property-provider": "^4.2.10", "@smithy/smithy-client": "^4.12.1", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-BM5cPEsyxHdYYO4Da77E94lenhaVPNUzBTyCGDkcw/n/mE8Q1cfHwr+n/w2bNPuUsPC30WaW5/hGKWOTKqw8kw=="],
+ "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.47", "", { "dependencies": { "@smithy/config-resolver": "^4.4.13", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-qSRbYp1EQ7th+sPFuVcVO05AE0QH635hycdEXlpzIahqHHf2Fyd/Zl+8v0XYMJ3cgDVPa0lkMefU7oNUjAP+DQ=="],
- "@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.1", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-xyctc4klmjmieQiF9I1wssBWleRV0RhJ2DpO8+8yzi2LO1Z+4IWOZNGZGNj4+hq9kdo+nyfrRLmQTzc16Op2Vg=="],
+ "@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig=="],
- "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-c1hHtkgAWmE35/50gmdKajgGAKV3ePJ7t6UtEmpfCWJmQE9BQAQPz0URUVI89eSkcDqCtzqllxzG28IQoZPvwA=="],
+ "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg=="],
- "@smithy/util-middleware": ["@smithy/util-middleware@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-LxaQIWLp4y0r72eA8mwPNQ9va4h5KeLM0I3M/HV9klmFaY2kN766wf5vsTzmaOpNNb7GgXAd9a25P3h8T49PSA=="],
+ "@smithy/util-middleware": ["@smithy/util-middleware@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ=="],
- "@smithy/util-retry": ["@smithy/util-retry@4.2.10", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-HrBzistfpyE5uqTwiyLsFHscgnwB0kgv8vySp7q5kZ0Eltn/tjosaSGGDj/jJ9ys7pWzIP/icE2d+7vMKXLv7A=="],
+ "@smithy/util-retry": ["@smithy/util-retry@4.2.12", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ=="],
- "@smithy/util-stream": ["@smithy/util-stream@4.5.16", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.12", "@smithy/node-http-handler": "^4.4.13", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.1", "@smithy/util-buffer-from": "^4.2.1", "@smithy/util-hex-encoding": "^4.2.1", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-c7awZV6cxY0czgDDSr+Bz0XfRtg8AwW2BWhrHhLJISrpmwv8QzA2qzTllWyMVNdy1+UJr9vCm29hzuh3l8TTFw=="],
+ "@smithy/util-stream": ["@smithy/util-stream@4.5.20", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-4yXLm5n/B5SRBR2p8cZ90Sbv4zL4NKsgxdzCzp/83cXw2KxLEumt5p+GAVyRNZgQOSrzXn9ARpO0lUe8XSlSDw=="],
- "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YmiUDn2eo2IOiWYYvGQkgX5ZkBSiTQu4FlDo5jNPpAxng2t6Sjb6WutnZV9l6VR4eJul1ABmCrnWBC9hKHQa6Q=="],
+ "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw=="],
- "@smithy/util-utf8": ["@smithy/util-utf8@4.2.1", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-DSIwNaWtmzrNQHv8g7DBGR9mulSit65KSj5ymGEIAknmIN8IpbZefEep10LaMG/P/xquwbmJ1h9ectz8z6mV6g=="],
+ "@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
- "@smithy/util-waiter": ["@smithy/util-waiter@4.2.10", "", { "dependencies": { "@smithy/abort-controller": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-4eTWph/Lkg1wZEDAyObwme0kmhEb7J/JjibY2znJdrYRgKbKqB7YoEhhJVJ4R1g/SYih4zuwX7LpJaM8RsnTVg=="],
+ "@smithy/util-waiter": ["@smithy/util-waiter@4.2.13", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-2zdZ9DTHngRtcYxJK1GUDxruNr53kv5W2Lupe0LMU+Imr6ohQg8M2T14MNkj1Y0wS3FFwpgpGQyvuaMF7CiTmQ=="],
- "@smithy/uuid": ["@smithy/uuid@1.1.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-dSfDCeihDmZlV2oyr0yWPTUfh07suS+R5OB+FZGiv/hHyK3hrFBW5rR1UYjfa57vBsrP9lciFkRPzebaV1Qujw=="],
+ "@smithy/uuid": ["@smithy/uuid@1.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g=="],
+
+ "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
"@szmarczak/http-timer": ["@szmarczak/http-timer@5.0.1", "", { "dependencies": { "defer-to-connect": "^2.0.1" } }, "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw=="],
- "@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="],
+ "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="],
+
+ "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="],
- "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.18", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="],
+ "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="],
- "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.18", "", { "os": "android", "cpu": "arm64" }, "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q=="],
+ "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="],
- "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A=="],
+ "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="],
- "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw=="],
+ "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="],
- "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.18", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA=="],
+ "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="],
- "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18", "", { "os": "linux", "cpu": "arm" }, "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA=="],
+ "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="],
- "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw=="],
+ "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="],
- "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg=="],
+ "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="],
- "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g=="],
+ "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="],
- "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ=="],
+ "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="],
- "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.18", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.0", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA=="],
+ "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="],
- "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA=="],
+ "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="],
- "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.18", "", { "os": "win32", "cpu": "x64" }, "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q=="],
+ "@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "postcss": "^8.5.6", "tailwindcss": "4.2.2" } }, "sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ=="],
- "@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.18", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "postcss": "^8.4.41", "tailwindcss": "4.1.18" } }, "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g=="],
+ "@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="],
+
+ "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
+
+ "@ts-morph/common": ["@ts-morph/common@0.28.1", "", { "dependencies": { "minimatch": "^10.0.1", "path-browserify": "^1.0.1", "tinyglobby": "^0.2.14" } }, "sha512-W74iWf7ILp1ZKNYXY5qbddNaml7e9Sedv5lvU1V8lftlitkc9Pq1A+jlH23ltDgWYeZFFEqGCD1Ies9hqu3O+g=="],
+
+ "@tsconfig/node10": ["@tsconfig/node10@1.0.12", "", {}, "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ=="],
+
+ "@tsconfig/node12": ["@tsconfig/node12@1.0.11", "", {}, "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag=="],
+
+ "@tsconfig/node14": ["@tsconfig/node14@1.0.3", "", {}, "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow=="],
+
+ "@tsconfig/node16": ["@tsconfig/node16@1.0.4", "", {}, "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA=="],
+
+ "@turbo/darwin-64": ["@turbo/darwin-64@2.8.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-FQ9EX1xMU5nbwjxXxM3yU88AQQ6Sqc6S44exPRroMcx9XZHqqppl5ymJF0Ig/z3nvQNwDmz1Gsnvxubo+nXWjQ=="],
+
+ "@turbo/darwin-arm64": ["@turbo/darwin-arm64@2.8.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gpyh9ATFGThD6/s9L95YWY54cizg/VRWl2B67h0yofG8BpHf67DFAh9nuJVKG7bY0+SBJDAo5cMur+wOl9YOYw=="],
+
+ "@turbo/linux-64": ["@turbo/linux-64@2.8.20", "", { "os": "linux", "cpu": "x64" }, "sha512-p2QxWUYyYUgUFG0b0kR+pPi8t7c9uaVlRtjTTI1AbCvVqkpjUfCcReBn6DgG/Hu8xrWdKLuyQFaLYFzQskZbcA=="],
+
+ "@turbo/linux-arm64": ["@turbo/linux-arm64@2.8.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-Gn5yjlZGLRZWarLWqdQzv0wMqyBNIdq1QLi48F1oY5Lo9kiohuf7BPQWtWxeNVS2NgJ1+nb/DzK1JduYC4AWOA=="],
+
+ "@turbo/windows-64": ["@turbo/windows-64@2.8.20", "", { "os": "win32", "cpu": "x64" }, "sha512-vyaDpYk/8T6Qz5V/X+ihKvKFEZFUoC0oxYpC1sZanK6gaESJlmV3cMRT3Qhcg4D2VxvtC2Jjs9IRkrZGL+exLw=="],
+
+ "@turbo/windows-arm64": ["@turbo/windows-arm64@2.8.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-voicVULvUV5yaGXo0Iue13BcHGYW3u0VgqSbfQwBaHbpj1zLjYV4KIe+7fYIo6DO8FVUJzxFps3ODCQG/Wy2Qw=="],
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
@@ -944,7 +1047,7 @@
"@types/d3-zoom": ["@types/d3-zoom@3.0.8", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw=="],
- "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
+ "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="],
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
@@ -974,7 +1077,9 @@
"@types/mute-stream": ["@types/mute-stream@0.0.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow=="],
- "@types/node": ["@types/node@22.19.11", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w=="],
+ "@types/node": ["@types/node@22.19.15", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg=="],
+
+ "@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
@@ -982,41 +1087,45 @@
"@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="],
+ "@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="],
+
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
"@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
+ "@types/uuid": ["@types/uuid@10.0.0", "", {}, "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ=="],
+
"@types/wrap-ansi": ["@types/wrap-ansi@3.0.0", "", {}, "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g=="],
- "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.56.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.56.0", "@typescript-eslint/type-utils": "8.56.0", "@typescript-eslint/utils": "8.56.0", "@typescript-eslint/visitor-keys": "8.56.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.56.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw=="],
+ "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.57.2", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/type-utils": "8.57.2", "@typescript-eslint/utils": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.57.2", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w=="],
- "@typescript-eslint/parser": ["@typescript-eslint/parser@8.56.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.56.0", "@typescript-eslint/types": "8.56.0", "@typescript-eslint/typescript-estree": "8.56.0", "@typescript-eslint/visitor-keys": "8.56.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg=="],
+ "@typescript-eslint/parser": ["@typescript-eslint/parser@8.57.2", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/types": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA=="],
- "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.56.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.56.0", "@typescript-eslint/types": "^8.56.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg=="],
+ "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.57.2", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.57.2", "@typescript-eslint/types": "^8.57.2", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw=="],
- "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.56.0", "", { "dependencies": { "@typescript-eslint/types": "8.56.0", "@typescript-eslint/visitor-keys": "8.56.0" } }, "sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w=="],
+ "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2" } }, "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw=="],
- "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.56.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg=="],
+ "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.57.2", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw=="],
- "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.56.0", "", { "dependencies": { "@typescript-eslint/types": "8.56.0", "@typescript-eslint/typescript-estree": "8.56.0", "@typescript-eslint/utils": "8.56.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA=="],
+ "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2", "@typescript-eslint/utils": "8.57.2", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg=="],
- "@typescript-eslint/types": ["@typescript-eslint/types@8.56.0", "", {}, "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ=="],
+ "@typescript-eslint/types": ["@typescript-eslint/types@8.57.2", "", {}, "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="],
- "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.56.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.56.0", "@typescript-eslint/tsconfig-utils": "8.56.0", "@typescript-eslint/types": "8.56.0", "@typescript-eslint/visitor-keys": "8.56.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q=="],
+ "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.57.2", "", { "dependencies": { "@typescript-eslint/project-service": "8.57.2", "@typescript-eslint/tsconfig-utils": "8.57.2", "@typescript-eslint/types": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA=="],
- "@typescript-eslint/utils": ["@typescript-eslint/utils@8.56.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.56.0", "@typescript-eslint/types": "8.56.0", "@typescript-eslint/typescript-estree": "8.56.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ=="],
+ "@typescript-eslint/utils": ["@typescript-eslint/utils@8.57.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/types": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg=="],
- "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.56.0", "", { "dependencies": { "@typescript-eslint/types": "8.56.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg=="],
+ "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "eslint-visitor-keys": "^5.0.0" } }, "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw=="],
- "@typespec/asset-emitter": ["@typespec/asset-emitter@0.79.0", "", { "peerDependencies": { "@typespec/compiler": "^1.9.0" } }, "sha512-pNMtfSSwgmTQ2ex6bd1l6BUW2RLjSFnWQO5C5bNSleV62YEH5jMLn3THWDU9oUB0JoiBjgomV8cPqNRTJ+iV9w=="],
+ "@typespec/asset-emitter": ["@typespec/asset-emitter@0.79.1", "", { "peerDependencies": { "@typespec/compiler": "^1.10.0" } }, "sha512-53s3GLu5BwNkl7Itr/OizfhymTV2u7k5/cwjUOAt03AUDfiKlwbsp+iCIsq1vccJuoDOiXOceJOfL8rAf4/9LQ=="],
- "@typespec/compiler": ["@typespec/compiler@1.9.0", "", { "dependencies": { "@babel/code-frame": "~7.28.6", "@inquirer/prompts": "^8.0.1", "ajv": "~8.17.1", "change-case": "~5.4.4", "env-paths": "^3.0.0", "globby": "~16.1.0", "is-unicode-supported": "^2.1.0", "mustache": "~4.2.0", "picocolors": "~1.1.1", "prettier": "~3.8.0", "semver": "^7.7.1", "tar": "^7.5.2", "temporal-polyfill": "^0.3.0", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.12", "yaml": "~2.8.2", "yargs": "~18.0.0" }, "bin": { "tsp": "cmd/tsp.js", "tsp-server": "cmd/tsp-server.js" } }, "sha512-Rz9fFWQSTJSnhBfZvtA/bDIuO82fknYdtyMsL9lZNJE82rquC6JByHPFsnbGH1VXA0HhMj9L7Oqyp3f0m/BTOA=="],
+ "@typespec/compiler": ["@typespec/compiler@1.10.0", "", { "dependencies": { "@babel/code-frame": "~7.29.0", "@inquirer/prompts": "^8.0.1", "ajv": "~8.18.0", "change-case": "~5.4.4", "env-paths": "^4.0.0", "globby": "~16.1.0", "is-unicode-supported": "^2.1.0", "mustache": "~4.2.0", "picocolors": "~1.1.1", "prettier": "~3.8.0", "semver": "^7.7.1", "tar": "^7.5.2", "temporal-polyfill": "^0.3.0", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.12", "yaml": "~2.8.2", "yargs": "~18.0.0" }, "bin": { "tsp": "cmd/tsp.js", "tsp-server": "cmd/tsp-server.js" } }, "sha512-R6BATDkughntPpaxeESJF+wxma5PEjgmnnKvH0/ByqUH8VyhIckQWE9kkP0Uc/EJ0o0VYhe8qCwWQvV70k5lTw=="],
- "@typespec/http": ["@typespec/http@1.9.0", "", { "peerDependencies": { "@typespec/compiler": "^1.9.0", "@typespec/streams": "^0.79.0" }, "optionalPeers": ["@typespec/streams"] }, "sha512-JzlZZsgCo71f2KhWbf4BLOz5e+dVLj7gJJ4kvXvrmuG9QHoT41VaGPpCQamYgpZLMz2LQbsOtw34AmpovhuJSw=="],
+ "@typespec/http": ["@typespec/http@1.10.0", "", { "peerDependencies": { "@typespec/compiler": "^1.10.0", "@typespec/streams": "^0.80.0" }, "optionalPeers": ["@typespec/streams"] }, "sha512-/fj55fmUj4m/FmNdfH0V52menVrmS2r5Xj9d1H+pnjQbxvvaxS906RSRcoF8kbg3PvlibP/Py5u82TAk53AyqA=="],
- "@typespec/openapi": ["@typespec/openapi@1.9.0", "", { "peerDependencies": { "@typespec/compiler": "^1.9.0", "@typespec/http": "^1.9.0" } }, "sha512-5ieXCWRLcyFLv3IFk26ena/RW/NxvT5KiHaoNVFRd79J0XZjFcE0Od6Lxxqj4dWmCo3C8oKtOwFoQuie18G3lQ=="],
+ "@typespec/openapi": ["@typespec/openapi@1.10.0", "", { "peerDependencies": { "@typespec/compiler": "^1.10.0", "@typespec/http": "^1.10.0" } }, "sha512-tukmyp+c9CFlA2FdF61XfT9eTe5WXWz6J8pOrJ9+IYg0BcBwhJkvDj6BYpDD6SjxbRr1wO5ZL2Whe6MequsyVw=="],
- "@typespec/openapi3": ["@typespec/openapi3@1.9.0", "", { "dependencies": { "@scalar/json-magic": "^0.9.1", "@scalar/openapi-parser": "^0.24.1", "@scalar/openapi-types": "^0.5.0", "@typespec/asset-emitter": "^0.79.0", "yaml": "~2.8.2" }, "peerDependencies": { "@typespec/compiler": "^1.9.0", "@typespec/events": "^0.79.0", "@typespec/http": "^1.9.0", "@typespec/json-schema": "^1.9.0", "@typespec/openapi": "^1.9.0", "@typespec/sse": "^0.79.0", "@typespec/streams": "^0.79.0", "@typespec/versioning": "^0.79.0" }, "optionalPeers": ["@typespec/events", "@typespec/json-schema", "@typespec/sse", "@typespec/streams", "@typespec/versioning"], "bin": { "tsp-openapi3": "cmd/tsp-openapi3.js" } }, "sha512-htwhrGHQxuoNwAljeJE8CBt5yfKOv48T9Ugv91Y+4yNnlevJfDT29yrfD2mXYMujVOr3Kte1qilazClafkUIgg=="],
+ "@typespec/openapi3": ["@typespec/openapi3@1.10.0", "", { "dependencies": { "@scalar/json-magic": "^0.11.5", "@scalar/openapi-parser": "^0.24.1", "@scalar/openapi-types": "^0.5.0", "@typespec/asset-emitter": "^0.79.1", "yaml": "~2.8.2" }, "peerDependencies": { "@typespec/compiler": "^1.10.0", "@typespec/events": "^0.80.0", "@typespec/http": "^1.10.0", "@typespec/json-schema": "^1.10.0", "@typespec/openapi": "^1.10.0", "@typespec/sse": "^0.80.0", "@typespec/streams": "^0.80.0", "@typespec/versioning": "^0.80.0" }, "optionalPeers": ["@typespec/events", "@typespec/json-schema", "@typespec/sse", "@typespec/streams", "@typespec/versioning"], "bin": { "tsp-openapi3": "cmd/tsp-openapi3.js" } }, "sha512-G2UTfsDuUprvhFIymHiLKly6FoA9UkRmTImqgmROP4JkKCdY/Mo6Xo03sufY8urywVjIWE3dryXpy5DjpOt9Eg=="],
"@typespec/rest": ["@typespec/rest@0.77.0", "", { "peerDependencies": { "@typespec/compiler": "^1.7.0", "@typespec/http": "^1.7.0" } }, "sha512-DEUMD9zYqUVUhKCGktV7Z+sFkzj+bcSpJRhEXxOrJxupWM4I3N4deMop+ulxezxlLxIRUz7ELc+6WucYXgOnAA=="],
@@ -1060,6 +1169,8 @@
"@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="],
+ "@upsetjs/venn.js": ["@upsetjs/venn.js@2.0.0", "", { "optionalDependencies": { "d3-selection": "^3.0.0", "d3-transition": "^3.0.1" } }, "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw=="],
+
"@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],
"@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="],
@@ -1074,11 +1185,19 @@
"@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
- "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
+ "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
+
+ "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
- "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
+ "acorn-walk": ["acorn-walk@8.3.5", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw=="],
+
+ "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
+
+ "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
+
+ "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="],
"ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="],
@@ -1092,6 +1211,8 @@
"ansis": ["ansis@3.17.0", "", {}, "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg=="],
+ "arg": ["arg@4.1.3", "", {}, "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="],
+
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="],
@@ -1102,6 +1223,8 @@
"array-includes": ["array-includes@3.1.9", "", { "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" } }, "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ=="],
+ "array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="],
+
"array.prototype.findlast": ["array.prototype.findlast@1.2.5", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ=="],
"array.prototype.findlastindex": ["array.prototype.findlastindex@1.2.6", "", { "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" } }, "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ=="],
@@ -1114,8 +1237,12 @@
"arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="],
+ "assert": ["assert@2.1.0", "", { "dependencies": { "call-bind": "^1.0.2", "is-nan": "^1.3.2", "object-is": "^1.1.5", "object.assign": "^4.1.4", "util": "^0.12.5" } }, "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw=="],
+
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
+ "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="],
+
"ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="],
"astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="],
@@ -1126,26 +1253,36 @@
"async-retry": ["async-retry@1.3.3", "", { "dependencies": { "retry": "0.13.1" } }, "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw=="],
+ "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
+
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
"axe-core": ["axe-core@4.11.1", "", {}, "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A=="],
+ "axios": ["axios@1.14.0", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } }, "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ=="],
+
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
- "balanced-match": ["balanced-match@4.0.2", "", { "dependencies": { "jackspeak": "^4.2.3" } }, "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg=="],
+ "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
+
+ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
- "baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="],
+ "baseline-browser-mapping": ["baseline-browser-mapping@2.10.11", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-DAKrHphkJyiGuau/cFieRYhcTFeK/lBuD++C7cZ6KZHbMhBrisoi+EvhQ5RZrIfV5qwsW8kgQ07JIC+MDJRAhg=="],
+
+ "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
"bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="],
- "brace-expansion": ["brace-expansion@5.0.2", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw=="],
+ "brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
+ "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
+
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
"cacheable-lookup": ["cacheable-lookup@7.0.0", "", {}, "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w=="],
@@ -1162,7 +1299,9 @@
"camel-case": ["camel-case@4.1.2", "", { "dependencies": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" } }, "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw=="],
- "caniuse-lite": ["caniuse-lite@1.0.30001770", "", {}, "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw=="],
+ "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
+
+ "caniuse-lite": ["caniuse-lite@1.0.30001781", "", {}, "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw=="],
"capital-case": ["capital-case@1.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", "upper-case-first": "^2.0.2" } }, "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A=="],
@@ -1184,9 +1323,11 @@
"chardet": ["chardet@2.1.1", "", {}, "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ=="],
+ "charenc": ["charenc@0.0.2", "", {}, "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA=="],
+
"check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="],
- "chevrotain": ["chevrotain@11.0.3", "", { "dependencies": { "@chevrotain/cst-dts-gen": "11.0.3", "@chevrotain/gast": "11.0.3", "@chevrotain/regexp-to-ast": "11.0.3", "@chevrotain/types": "11.0.3", "@chevrotain/utils": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw=="],
+ "chevrotain": ["chevrotain@11.1.2", "", { "dependencies": { "@chevrotain/cst-dts-gen": "11.1.2", "@chevrotain/gast": "11.1.2", "@chevrotain/regexp-to-ast": "11.1.2", "@chevrotain/types": "11.1.2", "@chevrotain/utils": "11.1.2", "lodash-es": "4.17.23" } }, "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg=="],
"chevrotain-allstar": ["chevrotain-allstar@0.3.1", "", { "dependencies": { "lodash-es": "^4.17.21" }, "peerDependencies": { "chevrotain": "^11.0.0" } }, "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw=="],
@@ -1202,22 +1343,30 @@
"cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="],
+ "code-block-writer": ["code-block-writer@13.0.3", "", {}, "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg=="],
+
"collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
+ "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
+
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
"commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="],
+ "comment-parser": ["comment-parser@1.4.5", "", {}, "sha512-aRDkn3uyIlCFfk5NUA+VdwMmMsh8JGhc4hapfV4yxymHGQ3BVskMQfoXGpCo5IoBuQ9tS5iiVKhCpTcB4pW4qw=="],
+
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
"config-chain": ["config-chain@1.1.13", "", { "dependencies": { "ini": "^1.3.4", "proto-list": "~1.2.1" } }, "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ=="],
+ "console-table-printer": ["console-table-printer@2.15.0", "", { "dependencies": { "simple-wcswidth": "^1.1.2" } }, "sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw=="],
+
"constant-case": ["constant-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", "upper-case": "^2.0.2" } }, "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ=="],
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
@@ -1226,8 +1375,12 @@
"cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="],
+ "create-require": ["create-require@1.1.1", "", {}, "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="],
+
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
+ "crypt": ["crypt@0.0.2", "", {}, "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow=="],
+
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"cytoscape": ["cytoscape@3.33.1", "", {}, "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ=="],
@@ -1300,7 +1453,7 @@
"d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="],
- "dagre-d3-es": ["dagre-d3-es@7.0.13", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q=="],
+ "dagre-d3-es": ["dagre-d3-es@7.0.14", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg=="],
"damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="],
@@ -1310,10 +1463,12 @@
"data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="],
- "dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="],
+ "dayjs": ["dayjs@1.11.20", "", {}, "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+ "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="],
+
"decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="],
"decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
@@ -1322,13 +1477,17 @@
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
+ "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
+
"defer-to-connect": ["defer-to-connect@2.0.1", "", {}, "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="],
"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
- "delaunator": ["delaunator@5.0.1", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw=="],
+ "delaunator": ["delaunator@5.1.0", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ=="],
+
+ "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
@@ -1342,25 +1501,33 @@
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
+ "diff": ["diff@4.0.4", "", {}, "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ=="],
+
+ "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
+
"doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
- "dompurify": ["dompurify@3.3.1", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q=="],
+ "dompurify": ["dompurify@3.3.3", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA=="],
"dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="],
+ "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
+
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
+ "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
+
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
- "electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="],
+ "electron-to-chromium": ["electron-to-chromium@1.5.328", "", {}, "sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w=="],
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"emoji-regex-xs": ["emoji-regex-xs@1.0.0", "", {}, "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg=="],
- "enhanced-resolve": ["enhanced-resolve@5.19.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="],
+ "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="],
- "env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="],
+ "env-paths": ["env-paths@4.0.0", "", { "dependencies": { "is-safe-filename": "^0.1.0" } }, "sha512-pxP8eL2SwwaTRi/KHYwLYXinDs7gL3jxFcBYmEdYfZmZXbaVDvdppd0XBU8qVz03rDfKZMXg1omHCbsJjZrMsw=="],
"error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="],
@@ -1370,7 +1537,7 @@
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
- "es-iterator-helpers": ["es-iterator-helpers@1.2.2", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.1", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "safe-array-concat": "^1.1.3" } }, "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w=="],
+ "es-iterator-helpers": ["es-iterator-helpers@1.3.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.1", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "math-intrinsics": "^1.1.0", "safe-array-concat": "^1.1.3" } }, "sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ=="],
"es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
@@ -1386,18 +1553,22 @@
"esast-util-from-js": ["esast-util-from-js@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "acorn": "^8.0.0", "esast-util-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw=="],
- "esbuild": ["esbuild@0.27.3", "", { "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" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
+ "esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
- "eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="],
+ "eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="],
- "eslint-config-next": ["eslint-config-next@16.1.6", "", { "dependencies": { "@next/eslint-plugin-next": "16.1.6", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^7.0.0", "globals": "16.4.0", "typescript-eslint": "^8.46.0" }, "peerDependencies": { "eslint": ">=9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-vKq40io2B0XtkkNDYyleATwblNt8xuh3FWp8SpSz3pt7P01OkBFlKsJZ2mWt5WsCySlDQLckb1zMY9yE9Qy0LA=="],
+ "eslint-config-next": ["eslint-config-next@16.2.1", "", { "dependencies": { "@next/eslint-plugin-next": "16.2.1", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^7.0.0", "globals": "16.4.0", "typescript-eslint": "^8.46.0" }, "peerDependencies": { "eslint": ">=9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-qhabwjQZ1Mk53XzXvmogf8KQ0tG0CQXF0CZ56+2/lVhmObgmaqj7x5A1DSrWdZd3kwI7GTPGUjFne+krRxYmFg=="],
"eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="],
+ "eslint-config-riot": ["eslint-config-riot@1.0.0", "", {}, "sha512-NB/L/1Y30qyJcG5xZxCJKW/+bqyj+llbcCwo9DEz8bESIP0SLTOQ8T1DWCCFc+wJ61AMEstj4511PSScqMMfCw=="],
+
+ "eslint-import-context": ["eslint-import-context@0.1.9", "", { "dependencies": { "get-tsconfig": "^4.10.1", "stable-hash-x": "^0.2.0" }, "peerDependencies": { "unrs-resolver": "^1.0.0" }, "optionalPeers": ["unrs-resolver"] }, "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg=="],
+
"eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.9", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", "resolve": "^1.22.4" } }, "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g=="],
"eslint-import-resolver-typescript": ["eslint-import-resolver-typescript@3.10.1", "", { "dependencies": { "@nolyfill/is-core-module": "1.0.39", "debug": "^4.4.0", "get-tsconfig": "^4.10.0", "is-bun-module": "^2.0.0", "stable-hash": "^0.0.5", "tinyglobby": "^0.2.13", "unrs-resolver": "^1.6.2" }, "peerDependencies": { "eslint": "*", "eslint-plugin-import": "*", "eslint-plugin-import-x": "*" }, "optionalPeers": ["eslint-plugin-import", "eslint-plugin-import-x"] }, "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ=="],
@@ -1406,8 +1577,12 @@
"eslint-plugin-import": ["eslint-plugin-import@2.32.0", "", { "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" }, "peerDependencies": { "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA=="],
+ "eslint-plugin-import-x": ["eslint-plugin-import-x@4.16.2", "", { "dependencies": { "@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" }, "peerDependencies": { "@typescript-eslint/utils": "^8.56.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "eslint-import-resolver-node": "*" }, "optionalPeers": ["@typescript-eslint/utils", "eslint-import-resolver-node"] }, "sha512-rM9K8UBHcWKpzQzStn1YRN2T5NvdeIfSVoKu/lKF41znQXHAUcBbYXe5wd6GNjZjTrP7viQ49n1D83x/2gYgIw=="],
+
"eslint-plugin-jsx-a11y": ["eslint-plugin-jsx-a11y@6.10.2", "", { "dependencies": { "aria-query": "^5.3.2", "array-includes": "^3.1.8", "array.prototype.flatmap": "^1.3.2", "ast-types-flow": "^0.0.8", "axe-core": "^4.10.0", "axobject-query": "^4.1.0", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", "hasown": "^2.0.2", "jsx-ast-utils": "^3.3.5", "language-tags": "^1.0.9", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "safe-regex-test": "^1.0.3", "string.prototype.includes": "^2.0.1" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q=="],
+ "eslint-plugin-n8n-nodes-base": ["eslint-plugin-n8n-nodes-base@1.16.5", "", { "dependencies": { "@typescript-eslint/utils": "^6.21.0", "camel-case": "^4.1.2", "indefinite": "^2.5.1", "pascal-case": "^3.1.2", "pluralize": "^8.0.0", "sentence-case": "^3.0.4", "title-case": "^3.0.3" } }, "sha512-/Bx2xj1ZzwEN+KQmnf7i0QRzgNMuphythQI2qXHoJQd8nm6aJGC9ZyVmDBFkM9P1ZfVBgBK7bDdjNxcbIK8Hgw=="],
+
"eslint-plugin-react": ["eslint-plugin-react@7.37.5", "", { "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA=="],
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="],
@@ -1420,6 +1595,8 @@
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
+ "esprima-next": ["esprima-next@5.8.4", "", { "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" } }, "sha512-8nYVZ4ioIH4Msjb/XmhnBdz5WRRBaYqevKa1cv9nGJdCehMbzZCPNEEnqfLCZVetUVrUPEcb5IYyu1GG4hFqgg=="],
+
"esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="],
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
@@ -1442,6 +1619,10 @@
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
+ "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
+
+ "eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
+
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
@@ -1464,9 +1645,9 @@
"fast-wrap-ansi": ["fast-wrap-ansi@0.2.0", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w=="],
- "fast-xml-builder": ["fast-xml-builder@1.0.0", "", {}, "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ=="],
+ "fast-xml-builder": ["fast-xml-builder@1.1.4", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg=="],
- "fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="],
+ "fast-xml-parser": ["fast-xml-parser@5.5.8", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ=="],
"fastest-levenshtein": ["fastest-levenshtein@1.0.16", "", {}, "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg=="],
@@ -1478,6 +1659,8 @@
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
+ "file-type": ["file-type@21.3.4", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g=="],
+
"filelist": ["filelist@1.0.6", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
@@ -1486,18 +1669,28 @@
"find-yarn-workspace-root": ["find-yarn-workspace-root@2.0.0", "", { "dependencies": { "micromatch": "^4.0.2" } }, "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ=="],
+ "flat": ["flat@5.0.2", "", { "bin": { "flat": "cli.js" } }, "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ=="],
+
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
- "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
+ "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="],
"flexsearch": ["flexsearch@0.7.43", "", {}, "sha512-c5o/+Um8aqCSOXGcZoqZOm+NqtVwNsvVpWv6lfmSclU954O3wvQKxxK8zj74fPaSJbXpSLTs4PRhh+wnoCXnKg=="],
+ "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
+
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
+ "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
+
+ "form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
+
"form-data-encoder": ["form-data-encoder@2.1.4", "", {}, "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw=="],
"formatly": ["formatly@0.3.0", "", { "dependencies": { "fd-package-json": "^2.0.0" }, "bin": { "formatly": "bin/index.mjs" } }, "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w=="],
+ "formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
+
"fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
@@ -1530,19 +1723,21 @@
"get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
- "get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="],
+ "get-tsconfig": ["get-tsconfig@4.13.7", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q=="],
"git-hooks-list": ["git-hooks-list@3.2.0", "", {}, "sha512-ZHG9a1gEhUMX1TvGrLdyWb9kDopCBbTnI8z4JgRMYxsijWipgjSEYoPWqBuIB0DnRnvqlQSEeVmzpeuPm7NdFQ=="],
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
+ "glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="],
+
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
"globals": ["globals@16.4.0", "", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="],
"globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
- "globby": ["globby@16.1.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "fast-glob": "^3.3.3", "ignore": "^7.0.5", "is-path-inside": "^4.0.0", "slash": "^5.1.0", "unicorn-magic": "^0.4.0" } }, "sha512-+A4Hq7m7Ze592k9gZRy4gJ27DrXRNnC1vPjxTt1qQxEY8RxagBkBxivkCwg7FxSTG0iLLEMaUx13oOr0R2/qcQ=="],
+ "globby": ["globby@16.1.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "fast-glob": "^3.3.3", "ignore": "^7.0.5", "is-path-inside": "^4.0.0", "slash": "^5.1.0", "unicorn-magic": "^0.4.0" } }, "sha512-dW7vl+yiAJSp6aCekaVnVJxurRv7DCOLyXqEG3RYMYUg7AuJ2jCqPkZTA8ooqC2vtnkaMcV5WfFBMuEnTu1OQg=="],
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
@@ -1556,6 +1751,8 @@
"hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="],
+ "handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="],
+
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
@@ -1594,28 +1791,42 @@
"http2-wrapper": ["http2-wrapper@2.2.1", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.2.0" } }, "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ=="],
+ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
+
+ "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="],
+
+ "ibm-cloud-sdk-core": ["ibm-cloud-sdk-core@5.4.9", "", { "dependencies": { "@types/debug": "^4.1.12", "@types/node": "^18.19.80", "@types/tough-cookie": "^4.0.0", "axios": "^1.13.5", "camelcase": "^6.3.0", "debug": "^4.3.4", "dotenv": "^16.4.5", "extend": "3.0.2", "file-type": "^21.3.2", "form-data": "^4.0.4", "isstream": "0.1.2", "jsonwebtoken": "^9.0.3", "load-esm": "^1.0.3", "mime-types": "2.1.35", "retry-axios": "^2.6.0", "tough-cookie": "^4.1.3" } }, "sha512-340fGcZEwUBdxBOPmn8V8fIiFRWF92yFqSFRNLwPQz4h+PS4jcAyd3JGqU6CpFqzUTt+PatVX/jHFwzUTVdmxQ=="],
+
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
+ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
+ "indefinite": ["indefinite@2.5.2", "", {}, "sha512-J3ELLIk835hmgDMUfNltTCrHz9+CteTnSuXJqvxZT18wo1U2M9/QeeJMw99QdZwPEEr1DE2aBYda101Ojjdw5g=="],
+
"indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
+ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
+
"ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
"inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="],
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
- "internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="],
+ "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
"is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="],
"is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="],
+ "is-arguments": ["is-arguments@1.2.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA=="],
+
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
@@ -1626,6 +1837,8 @@
"is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="],
+ "is-buffer": ["is-buffer@1.1.6", "", {}, "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="],
+
"is-bun-module": ["is-bun-module@2.0.0", "", { "dependencies": { "semver": "^7.7.1" } }, "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ=="],
"is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="],
@@ -1656,8 +1869,12 @@
"is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
+ "is-nan": ["is-nan@1.3.2", "", { "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" } }, "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w=="],
+
"is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="],
+ "is-network-error": ["is-network-error@1.3.1", "", {}, "sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw=="],
+
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
@@ -1670,6 +1887,8 @@
"is-retry-allowed": ["is-retry-allowed@1.2.0", "", {}, "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg=="],
+ "is-safe-filename": ["is-safe-filename@0.1.1", "", {}, "sha512-4SrR7AdnY11LHfDKTZY1u6Ga3RuxZdl3YKWWShO5iyuG5h8QS4GD2tOb04peBJ5I7pXbR+CGBNEhTcwK+FzN3g=="],
+
"is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="],
"is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="],
@@ -1696,6 +1915,10 @@
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
+ "isolated-vm": ["isolated-vm@6.1.2", "", { "dependencies": { "node-gyp-build": "^4.8.4" } }, "sha512-GGfsHqtlZiiurZaxB/3kY7LLAXR3sgzDul0fom4cSyBjx6ZbjpTrFWiH3z/nUfLJGJ8PIq9LQmQFiAxu24+I7A=="],
+
+ "isstream": ["isstream@0.1.2", "", {}, "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g=="],
+
"iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="],
"jackspeak": ["jackspeak@4.2.3", "", { "dependencies": { "@isaacs/cliui": "^9.0.0" } }, "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg=="],
@@ -1704,6 +1927,12 @@
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
+ "jmespath": ["jmespath@0.16.0", "", {}, "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw=="],
+
+ "js-base64": ["js-base64@3.7.2", "", {}, "sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ=="],
+
+ "js-tiktoken": ["js-tiktoken@1.0.12", "", { "dependencies": { "base64-js": "^1.5.1" } }, "sha512-L7wURW1fH9Qaext0VzaUDpFGVQgjkdE3Dgsy9/+yXyGEpBKnylTd0mU0bfbNkKDlXRb6TEsZkwuflu1B8uQbJQ=="],
+
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
@@ -1724,9 +1953,19 @@
"jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="],
+ "jsonrepair": ["jsonrepair@3.13.2", "", { "bin": { "jsonrepair": "bin/cli.js" } }, "sha512-Leuly0nbM4R+S5SVJk3VHfw1oxnlEK9KygdZvfUtEtTawNDyzB4qa1xWTmFt1aeoA7sXZkVTRuIixJ8bAvqVUg=="],
+
+ "jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="],
+
+ "jssha": ["jssha@3.3.1", "", {}, "sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ=="],
+
"jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="],
- "katex": ["katex@0.16.28", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-YHzO7721WbmAL6Ov1uzN/l5mY5WWWhJBSW+jq4tkfZfsxmo1hu6frS0EOswvjBUnWE6NtjEs48SFn5CQESRLZg=="],
+ "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
+
+ "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
+
+ "katex": ["katex@0.16.44", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-EkxoDTk8ufHqHlf9QxGwcxeLkWRR3iOuYfRpfORgYfqc8s13bgb+YtRY59NK5ZpRaCwq1kqA6a5lpX8C/eLphQ=="],
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
@@ -1734,9 +1973,15 @@
"kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="],
- "knip": ["knip@5.83.1", "", { "dependencies": { "@nodelib/fs.walk": "^1.2.3", "fast-glob": "^3.3.3", "formatly": "^0.3.0", "jiti": "^2.6.0", "js-yaml": "^4.1.1", "minimist": "^1.2.8", "oxc-resolver": "^11.15.0", "picocolors": "^1.1.1", "picomatch": "^4.0.1", "smol-toml": "^1.5.2", "strip-json-comments": "5.0.3", "zod": "^4.1.11" }, "peerDependencies": { "@types/node": ">=18", "typescript": ">=5.0.4 <7" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-av3ZG/Nui6S/BNL8Tmj12yGxYfTnwWnslouW97m40him7o8MwiMjZBY9TPvlEWUci45aVId0/HbgTwSKIDGpMw=="],
+ "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
+
+ "knip": ["knip@5.88.1", "", { "dependencies": { "@nodelib/fs.walk": "^1.2.3", "fast-glob": "^3.3.3", "formatly": "^0.3.0", "jiti": "^2.6.0", "minimist": "^1.2.8", "oxc-resolver": "^11.19.1", "picocolors": "^1.1.1", "picomatch": "^4.0.1", "smol-toml": "^1.5.2", "strip-json-comments": "5.0.3", "unbash": "^2.2.0", "yaml": "^2.8.2", "zod": "^4.1.11" }, "peerDependencies": { "@types/node": ">=18", "typescript": ">=5.0.4 <7" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-tpy5o7zu1MjawVkLPuahymVJekYY3kYjvzcoInhIchgePxTlo+api90tBv2KfhAIe5uXh+mez1tAfmbv8/TiZg=="],
- "langium": ["langium@3.3.1", "", { "dependencies": { "chevrotain": "~11.0.3", "chevrotain-allstar": "~0.3.0", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.11", "vscode-uri": "~3.0.8" } }, "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w=="],
+ "langchain": ["langchain@1.2.30", "", { "dependencies": { "@langchain/langgraph": "^1.1.2", "@langchain/langgraph-checkpoint": "^1.0.0", "langsmith": ">=0.5.0 <1.0.0", "uuid": "^11.1.0", "zod": "^3.25.76 || ^4" }, "peerDependencies": { "@langchain/core": "^1.1.31" } }, "sha512-Ofsk7LTGvIkyy3uesv7hpyerpTghdjNpYFJfIxJRGGLjd+4JgTVkT/Ax6Cjg0F6doEuO7VRID0NK2QwmT3A/bg=="],
+
+ "langium": ["langium@4.2.1", "", { "dependencies": { "chevrotain": "~11.1.1", "chevrotain-allstar": "~0.3.1", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.11", "vscode-uri": "~3.1.0" } }, "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ=="],
+
+ "langsmith": ["langsmith@0.3.87", "", { "dependencies": { "@types/uuid": "^10.0.0", "chalk": "^4.1.2", "console-table-printer": "^2.12.1", "p-queue": "^6.6.2", "semver": "^7.6.3", "uuid": "^10.0.0" }, "peerDependencies": { "@opentelemetry/api": "*", "@opentelemetry/exporter-trace-otlp-proto": "*", "@opentelemetry/sdk-trace-base": "*", "openai": "*" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/exporter-trace-otlp-proto", "@opentelemetry/sdk-trace-base", "openai"] }, "sha512-XXR1+9INH8YX96FKWc5tie0QixWz6tOqAsAKfcJyPkE0xPep+NDz0IQLR32q4bn10QK3LqD2HN6T3n6z1YLW7Q=="],
"language-subtag-registry": ["language-subtag-registry@0.3.23", "", {}, "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ=="],
@@ -1748,33 +1993,35 @@
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
- "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
+ "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
- "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
+ "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
- "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="],
+ "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
- "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="],
+ "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
- "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="],
+ "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
- "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="],
+ "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
- "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="],
+ "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
- "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="],
+ "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
- "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="],
+ "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
- "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="],
+ "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
- "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="],
+ "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
- "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
+ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
"lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
- "liquidjs": ["liquidjs@10.25.1", "", { "dependencies": { "commander": "^10.0.0" }, "bin": { "liquidjs": "bin/liquid.js", "liquid": "bin/liquid.js" } }, "sha512-D+jsJvkGigFn8qNUgh8U6XNHhGFBp+p8Dk26ea/Hl+XrjFVSg9OXlN31hGAfS3MYQ3Kr8Xi9sEVBVQa/VTVSmg=="],
+ "liquidjs": ["liquidjs@10.25.2", "", { "dependencies": { "commander": "^10.0.0" }, "bin": { "liquidjs": "bin/liquid.js", "liquid": "bin/liquid.js" } }, "sha512-ZbgcjEjGNlAIjqhuMzymO3lCpHgmVMftKfrq4/YLLxmKaFFeQMXRGrJTqKX7OXX1hKVPUDpTIrvL7lxt3X/hmw=="],
+
+ "load-esm": ["load-esm@1.0.3", "", {}, "sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA=="],
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
@@ -1782,8 +2029,22 @@
"lodash-es": ["lodash-es@4.17.23", "", {}, "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg=="],
+ "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="],
+
+ "lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="],
+
+ "lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="],
+
+ "lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="],
+
+ "lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="],
+
+ "lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="],
+
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
+ "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],
+
"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
@@ -1798,19 +2059,27 @@
"lucide-react": ["lucide-react@0.469.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw=="],
+ "luxon": ["luxon@3.7.2", "", {}, "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew=="],
+
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
+ "make-error": ["make-error@1.3.6", "", {}, "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="],
+
"markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="],
"markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
"marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="],
+ "math-expression-evaluator": ["math-expression-evaluator@2.0.7", "", {}, "sha512-uwliJZ6BPHRq4eiqNWxZBDzKUiS5RIynFFcgchqhBOloVLVBpZpNG8jRYkedLcBvhph8TnRyWEuxPqiQcwIdog=="],
+
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
+ "md5": ["md5@2.3.0", "", { "dependencies": { "charenc": "0.0.2", "crypt": "0.0.2", "is-buffer": "~1.1.6" } }, "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g=="],
+
"mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="],
- "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="],
+ "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="],
"mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="],
@@ -1842,7 +2111,7 @@
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
- "mermaid": ["mermaid@11.12.2", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.1", "@mermaid-js/parser": "^0.6.3", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.13", "dayjs": "^1.11.18", "dompurify": "^3.2.5", "katex": "^0.16.22", "khroma": "^2.1.0", "lodash-es": "^4.17.21", "marked": "^16.2.1", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0" } }, "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w=="],
+ "mermaid": ["mermaid@11.13.0", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.2", "@mermaid-js/parser": "^1.0.1", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", "cytoscape": "^3.33.1", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", "dayjs": "^1.11.19", "dompurify": "^3.3.1", "katex": "^0.16.25", "khroma": "^2.1.0", "lodash-es": "^4.17.23", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0" } }, "sha512-fEnci+Immw6lKMFI8sqzjlATTyjLkRa6axrEgLV2yHTfv8r+h1wjFbV6xeRtd4rUV1cS4EpR9rwp3Rci7TRWDw=="],
"micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="],
@@ -1916,17 +2185,21 @@
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
+ "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
+
+ "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
+
"mimic-response": ["mimic-response@4.0.0", "", {}, "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg=="],
"minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
- "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
+ "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
"minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="],
- "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="],
+ "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
@@ -1934,19 +2207,33 @@
"mute-stream": ["mute-stream@1.0.0", "", {}, "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA=="],
+ "n8n-nodes-pachca": ["n8n-nodes-pachca@workspace:integrations/n8n"],
+
+ "n8n-workflow": ["n8n-workflow@2.13.1", "", { "dependencies": { "@n8n/errors": "0.6.0", "@n8n/expression-runtime": "0.5.0", "@n8n/tournament": "1.0.6", "ast-types": "0.16.1", "callsites": "3.1.0", "esprima-next": "5.8.4", "form-data": "4.0.4", "jmespath": "0.16.0", "js-base64": "3.7.2", "jsonrepair": "3.13.2", "jssha": "3.3.1", "lodash": "4.17.23", "luxon": "3.7.2", "md5": "2.3.0", "recast": "0.22.0", "title-case": "3.0.3", "transliteration": "2.3.5", "uuid": "10.0.0", "xml2js": "0.6.2", "zod": "3.25.67" } }, "sha512-79qJU7aIwtzZY1bhYhFwjLs83yu4Cb+ZFC7+hjN9N+9+mRV0CObHTR9tg5/EwD1HURfztwdCK+N/dlfjO8xDlw=="],
+
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"napi-postinstall": ["napi-postinstall@0.3.4", "", { "bin": { "napi-postinstall": "lib/cli.js" } }, "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ=="],
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
+ "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
+
"next": ["next@16.0.10", "", { "dependencies": { "@next/env": "16.0.10", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.0.10", "@next/swc-darwin-x64": "16.0.10", "@next/swc-linux-arm64-gnu": "16.0.10", "@next/swc-linux-arm64-musl": "16.0.10", "@next/swc-linux-x64-gnu": "16.0.10", "@next/swc-linux-x64-musl": "16.0.10", "@next/swc-win32-arm64-msvc": "16.0.10", "@next/swc-win32-x64-msvc": "16.0.10", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-RtWh5PUgI+vxlV3HdR+IfWA1UUHu0+Ram/JBO4vWB54cVPentCD0e+lxyAYEsDTqGGMg7qpjhKh6dc6aW7W/sA=="],
"next-mdx-remote": ["next-mdx-remote@5.0.0", "", { "dependencies": { "@babel/code-frame": "^7.23.5", "@mdx-js/mdx": "^3.0.1", "@mdx-js/react": "^3.0.1", "unist-util-remove": "^3.1.0", "vfile": "^6.0.1", "vfile-matter": "^5.0.0" }, "peerDependencies": { "react": ">=16" } }, "sha512-RNNbqRpK9/dcIFZs/esQhuLA8jANqlH694yqoDBK8hkVdJUndzzGmnPHa2nyi90N4Z9VmzuSWNRpr5ItT3M7xQ=="],
"no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="],
- "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
+ "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
+
+ "node-exports-info": ["node-exports-info@1.6.0", "", { "dependencies": { "array.prototype.flatmap": "^1.3.3", "es-errors": "^1.3.0", "object.entries": "^1.1.9", "semver": "^6.3.1" } }, "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw=="],
+
+ "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
+
+ "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="],
+
+ "node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="],
"normalize-package-data": ["normalize-package-data@6.0.2", "", { "dependencies": { "hosted-git-info": "^7.0.0", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" } }, "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g=="],
@@ -1956,6 +2243,8 @@
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
+ "object-is": ["object-is@1.1.6", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1" } }, "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q=="],
+
"object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
"object.assign": ["object.assign@4.1.7", "", { "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" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
@@ -1968,22 +2257,36 @@
"object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
- "oclif": ["oclif@4.22.81", "", { "dependencies": { "@aws-sdk/client-cloudfront": "^3.995.0", "@aws-sdk/client-s3": "^3.995.0", "@inquirer/confirm": "^3.1.22", "@inquirer/input": "^2.2.4", "@inquirer/select": "^2.5.0", "@oclif/core": "^4.8.0", "@oclif/plugin-help": "^6.2.37", "@oclif/plugin-not-found": "^3.2.74", "@oclif/plugin-warn-if-update-available": "^3.1.55", "ansis": "^3.16.0", "async-retry": "^1.3.3", "change-case": "^4", "debug": "^4.4.0", "ejs": "^3.1.10", "find-yarn-workspace-root": "^2.0.0", "fs-extra": "^8.1", "github-slugger": "^2", "got": "^13", "lodash": "^4.17.23", "normalize-package-data": "^6", "semver": "^7.7.4", "sort-package-json": "^2.15.1", "tiny-jsonc": "^1.0.2", "validate-npm-package-name": "^5.0.1" }, "bin": { "oclif": "bin/run.js" } }, "sha512-MO2bupt/3wWYqt05F8ZLwMYKN58YqDfRVdJxAvCdg/wZJg6/sDXVKoMSTSzwqsnIaJGjru2LBNvk8lH+p+1uMQ=="],
+ "oclif": ["oclif@4.22.96", "", { "dependencies": { "@aws-sdk/client-cloudfront": "3.1009.0", "@aws-sdk/client-s3": "3.1014.0", "@inquirer/confirm": "^3.1.22", "@inquirer/input": "^2.2.4", "@inquirer/select": "^2.5.0", "@oclif/core": "4.9.0", "@oclif/plugin-help": "^6.2.38", "@oclif/plugin-not-found": "^3.2.76", "@oclif/plugin-warn-if-update-available": "^3.1.57", "ansis": "^3.16.0", "async-retry": "^1.3.3", "change-case": "^4", "debug": "^4.4.0", "ejs": "^3.1.10", "find-yarn-workspace-root": "^2.0.0", "fs-extra": "^8.1", "github-slugger": "^2", "got": "^13", "lodash": "^4.17.23", "normalize-package-data": "^6", "semver": "^7.7.4", "sort-package-json": "^2.15.1", "tiny-jsonc": "^1.0.2", "validate-npm-package-name": "^5.0.1" }, "bin": { "oclif": "bin/run.js" } }, "sha512-aWM9cMb7Q3KW09qi5Mkw0Hq9sIM7DjVlyMAUl8Q2FP3+e5afBlUU9vmL3EJazVPhqcbg5u18E3z+6kCMk72KYw=="],
"oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="],
+ "openai": ["openai@6.33.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-xAYN1W3YsDXJWA5F277135YfkEk6H7D3D6vWwRhJ3OEkzRgcyK8z/P5P9Gyi/wB4N8kK9kM5ZjprfvyHagKmpw=="],
+
+ "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
+
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
- "oxc-resolver": ["oxc-resolver@11.17.1", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.17.1", "@oxc-resolver/binding-android-arm64": "11.17.1", "@oxc-resolver/binding-darwin-arm64": "11.17.1", "@oxc-resolver/binding-darwin-x64": "11.17.1", "@oxc-resolver/binding-freebsd-x64": "11.17.1", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.17.1", "@oxc-resolver/binding-linux-arm-musleabihf": "11.17.1", "@oxc-resolver/binding-linux-arm64-gnu": "11.17.1", "@oxc-resolver/binding-linux-arm64-musl": "11.17.1", "@oxc-resolver/binding-linux-ppc64-gnu": "11.17.1", "@oxc-resolver/binding-linux-riscv64-gnu": "11.17.1", "@oxc-resolver/binding-linux-riscv64-musl": "11.17.1", "@oxc-resolver/binding-linux-s390x-gnu": "11.17.1", "@oxc-resolver/binding-linux-x64-gnu": "11.17.1", "@oxc-resolver/binding-linux-x64-musl": "11.17.1", "@oxc-resolver/binding-openharmony-arm64": "11.17.1", "@oxc-resolver/binding-wasm32-wasi": "11.17.1", "@oxc-resolver/binding-win32-arm64-msvc": "11.17.1", "@oxc-resolver/binding-win32-ia32-msvc": "11.17.1", "@oxc-resolver/binding-win32-x64-msvc": "11.17.1" } }, "sha512-pyRXK9kH81zKlirHufkFhOFBZRks8iAMLwPH8gU7lvKFiuzUH9L8MxDEllazwOb8fjXMcWjY1PMDfMJ2/yh5cw=="],
+ "oxc-resolver": ["oxc-resolver@11.19.1", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.19.1", "@oxc-resolver/binding-android-arm64": "11.19.1", "@oxc-resolver/binding-darwin-arm64": "11.19.1", "@oxc-resolver/binding-darwin-x64": "11.19.1", "@oxc-resolver/binding-freebsd-x64": "11.19.1", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.19.1", "@oxc-resolver/binding-linux-arm-musleabihf": "11.19.1", "@oxc-resolver/binding-linux-arm64-gnu": "11.19.1", "@oxc-resolver/binding-linux-arm64-musl": "11.19.1", "@oxc-resolver/binding-linux-ppc64-gnu": "11.19.1", "@oxc-resolver/binding-linux-riscv64-gnu": "11.19.1", "@oxc-resolver/binding-linux-riscv64-musl": "11.19.1", "@oxc-resolver/binding-linux-s390x-gnu": "11.19.1", "@oxc-resolver/binding-linux-x64-gnu": "11.19.1", "@oxc-resolver/binding-linux-x64-musl": "11.19.1", "@oxc-resolver/binding-openharmony-arm64": "11.19.1", "@oxc-resolver/binding-wasm32-wasi": "11.19.1", "@oxc-resolver/binding-win32-arm64-msvc": "11.19.1", "@oxc-resolver/binding-win32-ia32-msvc": "11.19.1", "@oxc-resolver/binding-win32-x64-msvc": "11.19.1" } }, "sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg=="],
"p-cancelable": ["p-cancelable@3.0.0", "", {}, "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw=="],
+ "p-finally": ["p-finally@1.0.0", "", {}, "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow=="],
+
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
+ "p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="],
+
+ "p-retry": ["p-retry@7.1.1", "", { "dependencies": { "is-network-error": "^1.1.0" } }, "sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w=="],
+
+ "p-timeout": ["p-timeout@3.2.0", "", { "dependencies": { "p-finally": "^1.0.0" } }, "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg=="],
+
+ "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
+
"package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="],
"param-case": ["param-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A=="],
@@ -1996,46 +2299,68 @@
"pascal-case": ["pascal-case@3.1.2", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g=="],
+ "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
+
"path-case": ["path-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg=="],
"path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="],
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
+ "path-expression-matcher": ["path-expression-matcher@1.2.0", "", {}, "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ=="],
+
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
+ "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
+
+ "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
+
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
- "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
+ "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
"pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
+ "playwright": ["playwright@1.58.2", "", { "dependencies": { "playwright-core": "1.58.2" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A=="],
+
+ "playwright-core": ["playwright-core@1.58.2", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg=="],
+
+ "pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="],
+
"points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="],
"points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="],
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
- "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
+ "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
"prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="],
+ "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
+
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
"proto-list": ["proto-list@1.2.4", "", {}, "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA=="],
+ "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
+
+ "psl": ["psl@1.15.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w=="],
+
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
+ "querystringify": ["querystringify@2.2.0", "", {}, "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="],
+
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="],
@@ -2052,6 +2377,8 @@
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
+ "recast": ["recast@0.22.0", "", { "dependencies": { "assert": "^2.0.0", "ast-types": "0.15.2", "esprima": "~4.0.0", "source-map": "~0.6.1", "tslib": "^2.0.1" } }, "sha512-5AAx+mujtXijsEavc5lWXBPQqrM4+Dl5qNH96N2aNeuJFUzpiiToKPsxQD/zAIJHspz7zz0maX0PCtCTFVlixQ=="],
+
"recma-build-jsx": ["recma-build-jsx@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-build-jsx": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew=="],
"recma-jsx": ["recma-jsx@1.0.1", "", { "dependencies": { "acorn-jsx": "^5.0.0", "estree-util-to-js": "^2.0.0", "recma-parse": "^1.0.0", "recma-stringify": "^1.0.0", "unified": "^11.0.0" }, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w=="],
@@ -2060,6 +2387,8 @@
"recma-stringify": ["recma-stringify@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-to-js": "^2.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g=="],
+ "reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="],
+
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
"regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="],
@@ -2084,8 +2413,12 @@
"remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="],
+ "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
+
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
+ "requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="],
+
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
"resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="],
@@ -2098,11 +2431,15 @@
"retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
+ "retry-axios": ["retry-axios@2.6.0", "", { "peerDependencies": { "axios": "*" } }, "sha512-pOLi+Gdll3JekwuFjXO3fTq+L9lzMQGcSq7M5gIjExcl3Gu1hd4XXuf5o3+LuSBsaULQH7DiNbsqPd1chVpQGQ=="],
+
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
- "robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="],
+ "rimraf": ["rimraf@6.0.1", "", { "dependencies": { "glob": "^11.0.0", "package-json-from-dist": "^1.0.0" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A=="],
+
+ "robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="],
- "rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
+ "rollup": ["rollup@4.60.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.0", "@rollup/rollup-android-arm64": "4.60.0", "@rollup/rollup-darwin-arm64": "4.60.0", "@rollup/rollup-darwin-x64": "4.60.0", "@rollup/rollup-freebsd-arm64": "4.60.0", "@rollup/rollup-freebsd-x64": "4.60.0", "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", "@rollup/rollup-linux-arm-musleabihf": "4.60.0", "@rollup/rollup-linux-arm64-gnu": "4.60.0", "@rollup/rollup-linux-arm64-musl": "4.60.0", "@rollup/rollup-linux-loong64-gnu": "4.60.0", "@rollup/rollup-linux-loong64-musl": "4.60.0", "@rollup/rollup-linux-ppc64-gnu": "4.60.0", "@rollup/rollup-linux-ppc64-musl": "4.60.0", "@rollup/rollup-linux-riscv64-gnu": "4.60.0", "@rollup/rollup-linux-riscv64-musl": "4.60.0", "@rollup/rollup-linux-s390x-gnu": "4.60.0", "@rollup/rollup-linux-x64-gnu": "4.60.0", "@rollup/rollup-linux-x64-musl": "4.60.0", "@rollup/rollup-openbsd-x64": "4.60.0", "@rollup/rollup-openharmony-arm64": "4.60.0", "@rollup/rollup-win32-arm64-msvc": "4.60.0", "@rollup/rollup-win32-ia32-msvc": "4.60.0", "@rollup/rollup-win32-x64-gnu": "4.60.0", "@rollup/rollup-win32-x64-msvc": "4.60.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ=="],
"roughjs": ["roughjs@4.6.6", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="],
@@ -2120,6 +2457,8 @@
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
+ "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
+
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="],
@@ -2154,11 +2493,13 @@
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
+ "simple-wcswidth": ["simple-wcswidth@1.1.2", "", {}, "sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw=="],
+
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
"slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="],
- "smol-toml": ["smol-toml@1.6.0", "", {}, "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw=="],
+ "smol-toml": ["smol-toml@1.6.1", "", {}, "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg=="],
"snake-case": ["snake-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg=="],
@@ -2184,6 +2525,8 @@
"stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="],
+ "stable-hash-x": ["stable-hash-x@0.2.0", "", {}, "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ=="],
+
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
"std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
@@ -2206,7 +2549,7 @@
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
- "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
+ "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
@@ -2216,7 +2559,9 @@
"strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="],
- "strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="],
+ "strnum": ["strnum@2.2.2", "", {}, "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA=="],
+
+ "strtok3": ["strtok3@10.3.5", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA=="],
"style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="],
@@ -2230,15 +2575,15 @@
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
- "tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="],
+ "tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="],
- "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
+ "tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="],
- "tar": ["tar@7.5.9", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg=="],
+ "tar": ["tar@7.5.13", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng=="],
- "temporal-polyfill": ["temporal-polyfill@0.3.0", "", { "dependencies": { "temporal-spec": "0.3.0" } }, "sha512-qNsTkX9K8hi+FHDfHmf22e/OGuXmfBm9RqNismxBrnSmZVJKegQ+HYYXT+R7Ha8F/YSm2Y34vmzD4cxMu2u95g=="],
+ "temporal-polyfill": ["temporal-polyfill@0.3.2", "", { "dependencies": { "temporal-spec": "0.3.1" } }, "sha512-TzHthD/heRK947GNiSu3Y5gSPpeUDH34+LESnfsq8bqpFhsB79HFBX8+Z834IVX68P3EUyRPZK5bL/1fh437Eg=="],
- "temporal-spec": ["temporal-spec@0.3.0", "", {}, "sha512-n+noVpIqz4hYgFSMOSiINNOUOMFtV5cZQNCmmszA6GiVFVRt3G7AqVyhXjhCSmowvQn+NsGn+jMDMKJYHd3bSQ=="],
+ "temporal-spec": ["temporal-spec@0.3.1", "", {}, "sha512-B4TUhezh9knfSIMwt7RVggApDRJZo73uZdj8AacL2mZ8RP5KtLianh2MXxL06GN9ESYiIsiuoLQhgVfwe55Yhw=="],
"tiny-jsonc": ["tiny-jsonc@1.0.2", "", {}, "sha512-f5QDAfLq6zIVSyCZQZhhyl0QS6MvAyTxgz4X4x3+EoCktNWEYJ6PeoEA97fyb98njpBNNi88ybpD7m+BDFXaCw=="],
@@ -2254,16 +2599,34 @@
"tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="],
+ "title-case": ["title-case@3.0.3", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA=="],
+
+ "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="],
+
+ "tmp-promise": ["tmp-promise@3.0.3", "", { "dependencies": { "tmp": "^0.2.0" } }, "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ=="],
+
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
+ "token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="],
+
+ "tough-cookie": ["tough-cookie@4.1.4", "", { "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" } }, "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag=="],
+
+ "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
+
+ "transliteration": ["transliteration@2.3.5", "", { "dependencies": { "yargs": "^17.5.1" }, "bin": { "slugify": "dist/bin/slugify", "transliterate": "dist/bin/transliterate" } }, "sha512-HAGI4Lq4Q9dZ3Utu2phaWgtm3vB6PkLUFqWAScg/UW+1eZ/Tg6Exo4oC0/3VUol/w4BlefLhUUSVBr/9/ZGQOw=="],
+
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
- "ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="],
+ "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
"ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="],
+ "ts-morph": ["ts-morph@27.0.2", "", { "dependencies": { "@ts-morph/common": "~0.28.1", "code-block-writer": "^13.0.3" } }, "sha512-fhUhgeljcrdZ+9DZND1De1029PrE+cMkIP7ooqkLRTrRLTqcki2AstsyJm0vRNbTbVCNJ0idGlbBrfqc7/nA8w=="],
+
+ "ts-node": ["ts-node@10.9.2", "", { "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", "@tsconfig/node16": "^1.0.2", "acorn": "^8.4.1", "acorn-walk": "^8.1.1", "arg": "^4.1.0", "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "peerDependencies": { "@swc/core": ">=1.2.50", "@swc/wasm": ">=1.2.50", "@types/node": "*", "typescript": ">=2.7" }, "optionalPeers": ["@swc/core", "@swc/wasm"], "bin": { "ts-node": "dist/bin.js", "ts-script": "dist/bin-script-deprecated.js", "ts-node-cwd": "dist/bin-cwd.js", "ts-node-esm": "dist/bin-esm.js", "ts-node-script": "dist/bin-script.js", "ts-node-transpile-only": "dist/bin-transpile.js" } }, "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ=="],
+
"tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
@@ -2272,19 +2635,7 @@
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
- "turbo": ["turbo@2.8.9", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.9", "turbo-darwin-arm64": "2.8.9", "turbo-linux-64": "2.8.9", "turbo-linux-arm64": "2.8.9", "turbo-windows-64": "2.8.9", "turbo-windows-arm64": "2.8.9" }, "bin": { "turbo": "bin/turbo" } }, "sha512-G+Mq8VVQAlpz/0HTsxiNNk/xywaHGl+dk1oiBREgOEVCCDjXInDlONWUn5srRnC9s5tdHTFD1bx1N19eR4hI+g=="],
-
- "turbo-darwin-64": ["turbo-darwin-64@2.8.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-KnCw1ZI9KTnEAhdI9avZrnZ/z4wsM++flMA1w8s8PKOqi5daGpFV36qoPafg4S8TmYMe52JPWEoFr0L+lQ5JIw=="],
-
- "turbo-darwin-arm64": ["turbo-darwin-arm64@2.8.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-CbD5Y2NKJKBXTOZ7z7Cc7vGlFPZkYjApA7ri9lH4iFwKV1X7MoZswh9gyRLetXYWImVX1BqIvP8KftulJg/wIA=="],
-
- "turbo-linux-64": ["turbo-linux-64@2.8.9", "", { "os": "linux", "cpu": "x64" }, "sha512-OXC9HdCtsHvyH+5KUoH8ds+p5WU13vdif0OPbsFzZca4cUXMwKA3HWwUuCgQetk0iAE4cscXpi/t8A263n3VTg=="],
-
- "turbo-linux-arm64": ["turbo-linux-arm64@2.8.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-yI5n8jNXiFA6+CxnXG0gO7h5ZF1+19K8uO3/kXPQmyl37AdiA7ehKJQOvf9OPAnmkGDHcF2HSCPltabERNRmug=="],
-
- "turbo-windows-64": ["turbo-windows-64@2.8.9", "", { "os": "win32", "cpu": "x64" }, "sha512-/OztzeGftJAg258M/9vK2ZCkUKUzqrWXJIikiD2pm8TlqHcIYUmepDbyZSDfOiUjMy6NzrLFahpNLnY7b5vNgg=="],
-
- "turbo-windows-arm64": ["turbo-windows-arm64@2.8.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-xZ2VTwVTjIqpFZKN4UBxDHCPM3oJ2J5cpRzCBSmRpJ/Pn33wpiYjs+9FB2E03svKaD04/lSSLlEUej0UYsugfg=="],
+ "turbo": ["turbo@2.8.20", "", { "optionalDependencies": { "@turbo/darwin-64": "2.8.20", "@turbo/darwin-arm64": "2.8.20", "@turbo/linux-64": "2.8.20", "@turbo/linux-arm64": "2.8.20", "@turbo/windows-64": "2.8.20", "@turbo/windows-arm64": "2.8.20" }, "bin": { "turbo": "bin/turbo" } }, "sha512-Rb4qk5YT8RUwwdXtkLpkVhNEe/lor6+WV7S5tTlLpxSz6MjV5Qi8jGNn4gS6NAvrYGA/rNrE6YUQM85sCZUDbQ=="],
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
@@ -2300,12 +2651,20 @@
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
- "typescript-eslint": ["typescript-eslint@8.56.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.56.0", "@typescript-eslint/parser": "8.56.0", "@typescript-eslint/typescript-estree": "8.56.0", "@typescript-eslint/utils": "8.56.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg=="],
+ "typescript-eslint": ["typescript-eslint@8.57.2", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.57.2", "@typescript-eslint/parser": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2", "@typescript-eslint/utils": "8.57.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A=="],
"ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="],
+ "uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="],
+
+ "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
+
+ "unbash": ["unbash@2.2.0", "", {}, "sha512-X2wH19RAPZE3+ldGicOkoj/SIA83OIxcJ6Cuaw23hf8Xc6fQpvZXY0SftE2JgS0QhYLUG4uwodSI3R53keyh7w=="],
+
"unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
+ "undici": ["undici@6.24.1", "", {}, "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA=="],
+
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"unicorn-magic": ["unicorn-magic@0.4.0", "", {}, "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw=="],
@@ -2338,12 +2697,18 @@
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
+ "url-parse": ["url-parse@1.5.10", "", { "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ=="],
+
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
+ "util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="],
+
"uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
+ "v8-compile-cache-lib": ["v8-compile-cache-lib@3.0.1", "", {}, "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="],
+
"validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="],
"validate-npm-package-name": ["validate-npm-package-name@5.0.1", "", {}, "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ=="],
@@ -2370,10 +2735,16 @@
"vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="],
- "vscode-uri": ["vscode-uri@3.0.8", "", {}, "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw=="],
+ "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="],
"walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="],
+ "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="],
+
+ "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
+
+ "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
+
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
@@ -2394,27 +2765,41 @@
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
+ "ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="],
+
+ "xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="],
+
+ "xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
+
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
- "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
+ "yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="],
"yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="],
"yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
+ "yn": ["yn@3.1.1", "", {}, "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q=="],
+
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"yoctocolors-cjs": ["yoctocolors-cjs@2.1.3", "", {}, "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw=="],
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
+ "zod-to-json-schema": ["zod-to-json-schema@3.23.3", "", { "peerDependencies": { "zod": "^3.23.3" } }, "sha512-TYWChTxKQbRJp5ST22o/Irt9KC5nj7CdBKYB/AosCRdj/wxEMvv4NNaj9XVUHDOIp53ZxArGhnw5HMZziPFjog=="],
+
"zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
- "@antfu/install-pkg/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
+ "@antfu/install-pkg/tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="],
+
+ "@anthropic-ai/sdk/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
+
+ "@anthropic-ai/sdk/form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="],
"@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
@@ -2422,8 +2807,6 @@
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
- "@babel/core/@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
-
"@babel/core/json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
@@ -2432,29 +2815,33 @@
"@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
- "@babel/template/@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
+ "@browserbasehq/sdk/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
+
+ "@browserbasehq/sdk/form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="],
- "@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
+ "@browserbasehq/stagehand/zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="],
- "@chevrotain/cst-dts-gen/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
+ "@browserbasehq/stagehand/zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
- "@chevrotain/gast/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
+ "@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
- "@eslint/config-array/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
+ "@eslint/config-array/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
- "@eslint/eslintrc/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
+ "@eslint/eslintrc/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
"@eslint/eslintrc/strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
- "@inquirer/checkbox/@inquirer/core": ["@inquirer/core@11.1.3", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-TBAGPDGvpwFSQ4nkawQzq5/X7DhElANjvKeUtcjpVnBIfuH/OEu4M+79R3+bGPtwxST4DOIGRtF933mUH2bRVw=="],
+ "@ibm-cloud/watsonx-ai/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
- "@inquirer/checkbox/@inquirer/figures": ["@inquirer/figures@2.0.3", "", {}, "sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g=="],
+ "@inquirer/checkbox/@inquirer/core": ["@inquirer/core@11.1.7", "", { "dependencies": { "@inquirer/ansi": "^2.0.4", "@inquirer/figures": "^2.0.4", "@inquirer/type": "^4.0.4", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-1BiBNDk9btIwYIzNZpkikIHXWeNzNncJePPqwDyVMhXhD1ebqbpn1mKGctpoqAbzywZfdG0O4tvmsGIcOevAPQ=="],
- "@inquirer/checkbox/@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="],
+ "@inquirer/checkbox/@inquirer/figures": ["@inquirer/figures@2.0.4", "", {}, "sha512-eLBsjlS7rPS3WEhmOmh1znQ5IsQrxWzxWDxO51e4urv+iVrSnIHbq4zqJIOiyNdYLa+BVjwOtdetcQx1lWPpiQ=="],
+
+ "@inquirer/checkbox/@inquirer/type": ["@inquirer/type@4.0.4", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-PamArxO3cFJZoOzspzo6cxVlLeIftyBsZw/S9bKY5DzxqJVZgjoj1oP8d0rskKtp7sZxBycsoer1g6UeJV1BBA=="],
"@inquirer/core/@inquirer/type": ["@inquirer/type@2.0.0", "", { "dependencies": { "mute-stream": "^1.0.0" } }, "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag=="],
@@ -2462,39 +2849,75 @@
"@inquirer/core/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
- "@inquirer/editor/@inquirer/core": ["@inquirer/core@11.1.3", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-TBAGPDGvpwFSQ4nkawQzq5/X7DhElANjvKeUtcjpVnBIfuH/OEu4M+79R3+bGPtwxST4DOIGRtF933mUH2bRVw=="],
+ "@inquirer/editor/@inquirer/core": ["@inquirer/core@11.1.7", "", { "dependencies": { "@inquirer/ansi": "^2.0.4", "@inquirer/figures": "^2.0.4", "@inquirer/type": "^4.0.4", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-1BiBNDk9btIwYIzNZpkikIHXWeNzNncJePPqwDyVMhXhD1ebqbpn1mKGctpoqAbzywZfdG0O4tvmsGIcOevAPQ=="],
- "@inquirer/editor/@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="],
+ "@inquirer/editor/@inquirer/type": ["@inquirer/type@4.0.4", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-PamArxO3cFJZoOzspzo6cxVlLeIftyBsZw/S9bKY5DzxqJVZgjoj1oP8d0rskKtp7sZxBycsoer1g6UeJV1BBA=="],
- "@inquirer/expand/@inquirer/core": ["@inquirer/core@11.1.3", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-TBAGPDGvpwFSQ4nkawQzq5/X7DhElANjvKeUtcjpVnBIfuH/OEu4M+79R3+bGPtwxST4DOIGRtF933mUH2bRVw=="],
+ "@inquirer/expand/@inquirer/core": ["@inquirer/core@11.1.7", "", { "dependencies": { "@inquirer/ansi": "^2.0.4", "@inquirer/figures": "^2.0.4", "@inquirer/type": "^4.0.4", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-1BiBNDk9btIwYIzNZpkikIHXWeNzNncJePPqwDyVMhXhD1ebqbpn1mKGctpoqAbzywZfdG0O4tvmsGIcOevAPQ=="],
- "@inquirer/expand/@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="],
+ "@inquirer/expand/@inquirer/type": ["@inquirer/type@4.0.4", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-PamArxO3cFJZoOzspzo6cxVlLeIftyBsZw/S9bKY5DzxqJVZgjoj1oP8d0rskKtp7sZxBycsoer1g6UeJV1BBA=="],
"@inquirer/external-editor/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
- "@inquirer/number/@inquirer/core": ["@inquirer/core@11.1.3", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-TBAGPDGvpwFSQ4nkawQzq5/X7DhElANjvKeUtcjpVnBIfuH/OEu4M+79R3+bGPtwxST4DOIGRtF933mUH2bRVw=="],
+ "@inquirer/number/@inquirer/core": ["@inquirer/core@11.1.7", "", { "dependencies": { "@inquirer/ansi": "^2.0.4", "@inquirer/figures": "^2.0.4", "@inquirer/type": "^4.0.4", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-1BiBNDk9btIwYIzNZpkikIHXWeNzNncJePPqwDyVMhXhD1ebqbpn1mKGctpoqAbzywZfdG0O4tvmsGIcOevAPQ=="],
+
+ "@inquirer/number/@inquirer/type": ["@inquirer/type@4.0.4", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-PamArxO3cFJZoOzspzo6cxVlLeIftyBsZw/S9bKY5DzxqJVZgjoj1oP8d0rskKtp7sZxBycsoer1g6UeJV1BBA=="],
+
+ "@inquirer/password/@inquirer/core": ["@inquirer/core@11.1.7", "", { "dependencies": { "@inquirer/ansi": "^2.0.4", "@inquirer/figures": "^2.0.4", "@inquirer/type": "^4.0.4", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-1BiBNDk9btIwYIzNZpkikIHXWeNzNncJePPqwDyVMhXhD1ebqbpn1mKGctpoqAbzywZfdG0O4tvmsGIcOevAPQ=="],
+
+ "@inquirer/password/@inquirer/type": ["@inquirer/type@4.0.4", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-PamArxO3cFJZoOzspzo6cxVlLeIftyBsZw/S9bKY5DzxqJVZgjoj1oP8d0rskKtp7sZxBycsoer1g6UeJV1BBA=="],
+
+ "@inquirer/prompts/@inquirer/confirm": ["@inquirer/confirm@6.0.10", "", { "dependencies": { "@inquirer/core": "^11.1.7", "@inquirer/type": "^4.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-tiNyA73pgpQ0FQ7axqtoLUe4GDYjNCDcVsbgcA5anvwg2z6i+suEngLKKJrWKJolT//GFPZHwN30binDIHgSgQ=="],
+
+ "@inquirer/prompts/@inquirer/input": ["@inquirer/input@5.0.10", "", { "dependencies": { "@inquirer/core": "^11.1.7", "@inquirer/type": "^4.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-nvZ6qEVeX/zVtZ1dY2hTGDQpVGD3R7MYPLODPgKO8Y+RAqxkrP3i/3NwF3fZpLdaMiNuK0z2NaYIx9tPwiSegQ=="],
+
+ "@inquirer/prompts/@inquirer/select": ["@inquirer/select@5.1.2", "", { "dependencies": { "@inquirer/ansi": "^2.0.4", "@inquirer/core": "^11.1.7", "@inquirer/figures": "^2.0.4", "@inquirer/type": "^4.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-kTK8YIkHV+f02y7bWCh7E0u2/11lul5WepVTclr3UMBtBr05PgcZNWfMa7FY57ihpQFQH/spLMHTcr0rXy50tA=="],
+
+ "@inquirer/rawlist/@inquirer/core": ["@inquirer/core@11.1.7", "", { "dependencies": { "@inquirer/ansi": "^2.0.4", "@inquirer/figures": "^2.0.4", "@inquirer/type": "^4.0.4", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-1BiBNDk9btIwYIzNZpkikIHXWeNzNncJePPqwDyVMhXhD1ebqbpn1mKGctpoqAbzywZfdG0O4tvmsGIcOevAPQ=="],
+
+ "@inquirer/rawlist/@inquirer/type": ["@inquirer/type@4.0.4", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-PamArxO3cFJZoOzspzo6cxVlLeIftyBsZw/S9bKY5DzxqJVZgjoj1oP8d0rskKtp7sZxBycsoer1g6UeJV1BBA=="],
+
+ "@inquirer/search/@inquirer/core": ["@inquirer/core@11.1.7", "", { "dependencies": { "@inquirer/ansi": "^2.0.4", "@inquirer/figures": "^2.0.4", "@inquirer/type": "^4.0.4", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-1BiBNDk9btIwYIzNZpkikIHXWeNzNncJePPqwDyVMhXhD1ebqbpn1mKGctpoqAbzywZfdG0O4tvmsGIcOevAPQ=="],
+
+ "@inquirer/search/@inquirer/figures": ["@inquirer/figures@2.0.4", "", {}, "sha512-eLBsjlS7rPS3WEhmOmh1znQ5IsQrxWzxWDxO51e4urv+iVrSnIHbq4zqJIOiyNdYLa+BVjwOtdetcQx1lWPpiQ=="],
+
+ "@inquirer/search/@inquirer/type": ["@inquirer/type@4.0.4", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-PamArxO3cFJZoOzspzo6cxVlLeIftyBsZw/S9bKY5DzxqJVZgjoj1oP8d0rskKtp7sZxBycsoer1g6UeJV1BBA=="],
+
+ "@langchain/classic/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
- "@inquirer/number/@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="],
+ "@langchain/community/@langchain/classic": ["@langchain/classic@1.0.17", "", { "dependencies": { "@langchain/openai": "1.2.7", "@langchain/textsplitters": "1.0.1", "handlebars": "^4.7.8", "js-yaml": "^4.1.1", "jsonpointer": "^5.0.1", "openapi-types": "^12.1.3", "uuid": "^10.0.0", "yaml": "^2.2.1", "zod": "^3.25.76 || ^4" }, "optionalDependencies": { "langsmith": ">=0.4.0 <1.0.0" }, "peerDependencies": { "@langchain/core": "^1.0.0", "cheerio": "*", "peggy": "^3.0.2", "typeorm": "*" }, "optionalPeers": ["cheerio", "peggy", "typeorm"] }, "sha512-GgcmDILxl26E0Oo09Q/fotJB3EZrTnU4MuJGR2zQXPMZnZ1CCQqyecXjKDRdI6sZkfc8Kxg+ezT+0kIMtKV10A=="],
- "@inquirer/password/@inquirer/core": ["@inquirer/core@11.1.3", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-TBAGPDGvpwFSQ4nkawQzq5/X7DhElANjvKeUtcjpVnBIfuH/OEu4M+79R3+bGPtwxST4DOIGRtF933mUH2bRVw=="],
+ "@langchain/community/@langchain/openai": ["@langchain/openai@1.2.7", "", { "dependencies": { "js-tiktoken": "^1.0.12", "openai": "^6.18.0", "zod": "^3.25.76 || ^4" }, "peerDependencies": { "@langchain/core": "^1.0.0" } }, "sha512-vR9zoF0/EZ03X0Tc6woIEWRDSDSr2l64n+MQCW8NduScJtBJs5r/Ng3Lrp2bjtJQywEMQoOhcrV2DMmAIPWgnw=="],
- "@inquirer/password/@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="],
+ "@langchain/community/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
- "@inquirer/prompts/@inquirer/confirm": ["@inquirer/confirm@6.0.6", "", { "dependencies": { "@inquirer/core": "^11.1.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-9ZkrGYiWnOKQPc3xfLIORE3lZW1qvtgRoJcoqopr5zssBn7yk4yONmzGynEOjc16FnUXzkAejj/I29BbfcoUfQ=="],
+ "@langchain/core/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
- "@inquirer/prompts/@inquirer/input": ["@inquirer/input@5.0.6", "", { "dependencies": { "@inquirer/core": "^11.1.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-RZsJcjMJA3QNI9q9OiAi1fAom+Pb8on6alJB1Teh5jjKaiG5C79P69cG955ZRfgPdxTmI4uyhf33+94Xj7xWig=="],
+ "@langchain/core/langsmith": ["langsmith@0.5.15", "", { "dependencies": { "@types/uuid": "^10.0.0", "chalk": "^5.6.2", "console-table-printer": "^2.12.1", "p-queue": "^6.6.2", "semver": "^7.6.3", "uuid": "^10.0.0" }, "peerDependencies": { "@opentelemetry/api": "*", "@opentelemetry/exporter-trace-otlp-proto": "*", "@opentelemetry/sdk-trace-base": "*", "openai": "*", "ws": ">=7" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/exporter-trace-otlp-proto", "@opentelemetry/sdk-trace-base", "openai", "ws"] }, "sha512-S20JnYmIgqGBjA/WEn12ZZJjqd03O5wd8K9KgGBvsKXQBn0bYuFrr1w20L37PpcMmX3/cftpgJ6g2y8KoEmHLw=="],
- "@inquirer/prompts/@inquirer/select": ["@inquirer/select@5.0.6", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/core": "^11.1.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-9DyVbNCo4q0C3CkGd6zW0SW3NQuuk4Hy0NSbP6zErz2YNWF4EHHJCRzcV34/CDQLraeAQXbHYlMofuUrs6BBZQ=="],
+ "@langchain/langgraph/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
- "@inquirer/rawlist/@inquirer/core": ["@inquirer/core@11.1.3", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-TBAGPDGvpwFSQ4nkawQzq5/X7DhElANjvKeUtcjpVnBIfuH/OEu4M+79R3+bGPtwxST4DOIGRtF933mUH2bRVw=="],
+ "@langchain/langgraph-checkpoint/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
- "@inquirer/rawlist/@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="],
+ "@langchain/langgraph-sdk/p-queue": ["p-queue@9.1.0", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^7.0.0" } }, "sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw=="],
- "@inquirer/search/@inquirer/core": ["@inquirer/core@11.1.3", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-TBAGPDGvpwFSQ4nkawQzq5/X7DhElANjvKeUtcjpVnBIfuH/OEu4M+79R3+bGPtwxST4DOIGRtF933mUH2bRVw=="],
+ "@langchain/langgraph-sdk/uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="],
- "@inquirer/search/@inquirer/figures": ["@inquirer/figures@2.0.3", "", {}, "sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g=="],
+ "@n8n/ai-utilities/zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="],
- "@inquirer/search/@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="],
+ "@n8n/config/zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="],
+
+ "@n8n/node-cli/@clack/prompts": ["@clack/prompts@0.11.0", "", { "dependencies": { "@clack/core": "0.5.0", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw=="],
+
+ "@n8n/node-cli/change-case": ["change-case@5.4.4", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="],
+
+ "@n8n/node-cli/eslint-import-resolver-typescript": ["eslint-import-resolver-typescript@4.4.4", "", { "dependencies": { "debug": "^4.4.1", "eslint-import-context": "^0.1.8", "get-tsconfig": "^4.10.1", "is-bun-module": "^2.0.0", "stable-hash-x": "^0.2.0", "tinyglobby": "^0.2.14", "unrs-resolver": "^1.7.11" }, "peerDependencies": { "eslint": "*", "eslint-plugin-import": "*", "eslint-plugin-import-x": "*" }, "optionalPeers": ["eslint-plugin-import", "eslint-plugin-import-x"] }, "sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw=="],
+
+ "@n8n/node-cli/fast-glob": ["fast-glob@3.2.12", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w=="],
+
+ "@n8n/node-cli/picocolors": ["picocolors@1.0.1", "", {}, "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew=="],
+
+ "@n8n/node-cli/prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
"@next/eslint-plugin-next/fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="],
@@ -2504,17 +2927,13 @@
"@pnpm/network.ca-file/graceful-fs": ["graceful-fs@4.2.10", "", {}, "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="],
- "@scalar/openapi-parser/@scalar/helpers": ["@scalar/helpers@0.2.12", "", {}, "sha512-Ig/H1Je8nqcDiY+YwFIpATxF2ko7zKrjIZFWK2gGeNTYK4Np9XnqDHg56jM3Xru439Eh4qHq9P/lX7Se5nnxFA=="],
-
- "@scalar/openapi-parser/@scalar/json-magic": ["@scalar/json-magic@0.11.1", "", { "dependencies": { "@scalar/helpers": "0.2.12", "yaml": "^2.8.0" } }, "sha512-JsugkVpZ9SmKW6fDhamcmkttc9YOPGgb9Azbwc7hXTlZgG6YeYXx8qFvYr5eJE4cfzCqalodS/9w7moZnVG3cw=="],
+ "@scalar/openapi-parser/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
- "@scalar/openapi-parser/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
- "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
- "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
-
- "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
@@ -2526,23 +2945,25 @@
"@types/mdast/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
- "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
+ "@types/node-fetch/form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
- "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
+ "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
- "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.0", "", {}, "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q=="],
+ "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
- "@typespec/compiler/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
+ "@typespec/compiler/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
"@typespec/compiler/change-case": ["change-case@5.4.4", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="],
"@unrs/resolver-binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
- "ajv-formats/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
+ "ajv-formats/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
- "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
+ "axios/form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
- "chevrotain/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
+ "axios/proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="],
+
+ "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"cliui/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
@@ -2558,7 +2979,7 @@
"decompress-response/mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
- "eslint/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
+ "eslint/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
@@ -2566,17 +2987,19 @@
"eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
- "eslint-plugin-import/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
+ "eslint-plugin-import/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
"eslint-plugin-import/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"eslint-plugin-jsx-a11y/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
- "eslint-plugin-jsx-a11y/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
+ "eslint-plugin-jsx-a11y/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
+
+ "eslint-plugin-n8n-nodes-base/@typescript-eslint/utils": ["@typescript-eslint/utils@6.21.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" } }, "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ=="],
- "eslint-plugin-react/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
+ "eslint-plugin-react/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
- "eslint-plugin-react/resolve": ["resolve@2.0.0-next.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="],
+ "eslint-plugin-react/resolve": ["resolve@2.0.0-next.6", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA=="],
"eslint-plugin-react/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
@@ -2584,18 +3007,26 @@
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
- "filelist/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
+ "filelist/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="],
"globby/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"gray-matter/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="],
+ "handlebars/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
"hast-util-to-html/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"hast-util-to-jsx-runtime/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
+ "ibm-cloud-sdk-core/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
+
"katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
+ "langchain/langsmith": ["langsmith@0.5.15", "", { "dependencies": { "@types/uuid": "^10.0.0", "chalk": "^5.6.2", "console-table-printer": "^2.12.1", "p-queue": "^6.6.2", "semver": "^7.6.3", "uuid": "^10.0.0" }, "peerDependencies": { "@opentelemetry/api": "*", "@opentelemetry/exporter-trace-otlp-proto": "*", "@opentelemetry/sdk-trace-base": "*", "openai": "*", "ws": ">=7" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/exporter-trace-otlp-proto", "@opentelemetry/sdk-trace-base", "openai", "ws"] }, "sha512-S20JnYmIgqGBjA/WEn12ZZJjqd03O5wd8K9KgGBvsKXQBn0bYuFrr1w20L37PpcMmX3/cftpgJ6g2y8KoEmHLw=="],
+
+ "langsmith/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
+
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
"mdast-util-find-and-replace/unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="],
@@ -2612,14 +3043,34 @@
"micromark-util-events-to-acorn/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
- "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+ "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
+
+ "n8n-workflow/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
+
+ "n8n-workflow/zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="],
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
+ "node-exports-info/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+
+ "oclif/@oclif/core": ["@oclif/core@4.9.0", "", { "dependencies": { "ansi-escapes": "^4.3.2", "ansis": "^3.17.0", "clean-stack": "^3.0.1", "cli-spinners": "^2.9.2", "debug": "^4.4.3", "ejs": "^3.1.10", "get-package-type": "^0.1.0", "indent-string": "^4.0.0", "is-wsl": "^2.2.0", "lilconfig": "^3.1.3", "minimatch": "^10.2.4", "semver": "^7.7.3", "string-width": "^4.2.3", "supports-color": "^8", "tinyglobby": "^0.2.14", "widest-line": "^3.1.0", "wordwrap": "^1.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-k/ntRgDcUprTT+aaNoF+whk3cY3f9fRD2lkF6ul7JeCUg2MaMXVXZXfbRhJCfsiX51X8/5Pqo0LGdO9SLYXNHg=="],
+
"optionator/fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
+ "path-scurry/lru-cache": ["lru-cache@11.2.7", "", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="],
+
+ "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
+
+ "recast/ast-types": ["ast-types@0.15.2", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg=="],
+
+ "recast/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
"strip-literal/js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
+ "tough-cookie/universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="],
+
+ "transliteration/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
+
"unified/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"unist-util-position/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
@@ -2646,6 +3097,10 @@
"yargs/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
+ "zod-to-json-schema/zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="],
+
+ "@anthropic-ai/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
"@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
@@ -2654,9 +3109,13 @@
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
- "@eslint/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
+ "@browserbasehq/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "@eslint/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="],
+
+ "@eslint/eslintrc/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="],
- "@eslint/eslintrc/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
+ "@ibm-cloud/watsonx-ai/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"@inquirer/checkbox/@inquirer/core/mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="],
@@ -2664,42 +3123,58 @@
"@inquirer/core/wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
- "@inquirer/editor/@inquirer/core/@inquirer/figures": ["@inquirer/figures@2.0.3", "", {}, "sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g=="],
+ "@inquirer/editor/@inquirer/core/@inquirer/figures": ["@inquirer/figures@2.0.4", "", {}, "sha512-eLBsjlS7rPS3WEhmOmh1znQ5IsQrxWzxWDxO51e4urv+iVrSnIHbq4zqJIOiyNdYLa+BVjwOtdetcQx1lWPpiQ=="],
"@inquirer/editor/@inquirer/core/mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="],
- "@inquirer/expand/@inquirer/core/@inquirer/figures": ["@inquirer/figures@2.0.3", "", {}, "sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g=="],
+ "@inquirer/expand/@inquirer/core/@inquirer/figures": ["@inquirer/figures@2.0.4", "", {}, "sha512-eLBsjlS7rPS3WEhmOmh1znQ5IsQrxWzxWDxO51e4urv+iVrSnIHbq4zqJIOiyNdYLa+BVjwOtdetcQx1lWPpiQ=="],
"@inquirer/expand/@inquirer/core/mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="],
- "@inquirer/number/@inquirer/core/@inquirer/figures": ["@inquirer/figures@2.0.3", "", {}, "sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g=="],
+ "@inquirer/number/@inquirer/core/@inquirer/figures": ["@inquirer/figures@2.0.4", "", {}, "sha512-eLBsjlS7rPS3WEhmOmh1znQ5IsQrxWzxWDxO51e4urv+iVrSnIHbq4zqJIOiyNdYLa+BVjwOtdetcQx1lWPpiQ=="],
"@inquirer/number/@inquirer/core/mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="],
- "@inquirer/password/@inquirer/core/@inquirer/figures": ["@inquirer/figures@2.0.3", "", {}, "sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g=="],
+ "@inquirer/password/@inquirer/core/@inquirer/figures": ["@inquirer/figures@2.0.4", "", {}, "sha512-eLBsjlS7rPS3WEhmOmh1znQ5IsQrxWzxWDxO51e4urv+iVrSnIHbq4zqJIOiyNdYLa+BVjwOtdetcQx1lWPpiQ=="],
"@inquirer/password/@inquirer/core/mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="],
- "@inquirer/prompts/@inquirer/confirm/@inquirer/core": ["@inquirer/core@11.1.3", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-TBAGPDGvpwFSQ4nkawQzq5/X7DhElANjvKeUtcjpVnBIfuH/OEu4M+79R3+bGPtwxST4DOIGRtF933mUH2bRVw=="],
+ "@inquirer/prompts/@inquirer/confirm/@inquirer/core": ["@inquirer/core@11.1.7", "", { "dependencies": { "@inquirer/ansi": "^2.0.4", "@inquirer/figures": "^2.0.4", "@inquirer/type": "^4.0.4", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-1BiBNDk9btIwYIzNZpkikIHXWeNzNncJePPqwDyVMhXhD1ebqbpn1mKGctpoqAbzywZfdG0O4tvmsGIcOevAPQ=="],
- "@inquirer/prompts/@inquirer/confirm/@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="],
+ "@inquirer/prompts/@inquirer/confirm/@inquirer/type": ["@inquirer/type@4.0.4", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-PamArxO3cFJZoOzspzo6cxVlLeIftyBsZw/S9bKY5DzxqJVZgjoj1oP8d0rskKtp7sZxBycsoer1g6UeJV1BBA=="],
- "@inquirer/prompts/@inquirer/input/@inquirer/core": ["@inquirer/core@11.1.3", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-TBAGPDGvpwFSQ4nkawQzq5/X7DhElANjvKeUtcjpVnBIfuH/OEu4M+79R3+bGPtwxST4DOIGRtF933mUH2bRVw=="],
+ "@inquirer/prompts/@inquirer/input/@inquirer/core": ["@inquirer/core@11.1.7", "", { "dependencies": { "@inquirer/ansi": "^2.0.4", "@inquirer/figures": "^2.0.4", "@inquirer/type": "^4.0.4", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-1BiBNDk9btIwYIzNZpkikIHXWeNzNncJePPqwDyVMhXhD1ebqbpn1mKGctpoqAbzywZfdG0O4tvmsGIcOevAPQ=="],
- "@inquirer/prompts/@inquirer/input/@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="],
+ "@inquirer/prompts/@inquirer/input/@inquirer/type": ["@inquirer/type@4.0.4", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-PamArxO3cFJZoOzspzo6cxVlLeIftyBsZw/S9bKY5DzxqJVZgjoj1oP8d0rskKtp7sZxBycsoer1g6UeJV1BBA=="],
- "@inquirer/prompts/@inquirer/select/@inquirer/core": ["@inquirer/core@11.1.3", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-TBAGPDGvpwFSQ4nkawQzq5/X7DhElANjvKeUtcjpVnBIfuH/OEu4M+79R3+bGPtwxST4DOIGRtF933mUH2bRVw=="],
+ "@inquirer/prompts/@inquirer/select/@inquirer/core": ["@inquirer/core@11.1.7", "", { "dependencies": { "@inquirer/ansi": "^2.0.4", "@inquirer/figures": "^2.0.4", "@inquirer/type": "^4.0.4", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-1BiBNDk9btIwYIzNZpkikIHXWeNzNncJePPqwDyVMhXhD1ebqbpn1mKGctpoqAbzywZfdG0O4tvmsGIcOevAPQ=="],
- "@inquirer/prompts/@inquirer/select/@inquirer/figures": ["@inquirer/figures@2.0.3", "", {}, "sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g=="],
+ "@inquirer/prompts/@inquirer/select/@inquirer/figures": ["@inquirer/figures@2.0.4", "", {}, "sha512-eLBsjlS7rPS3WEhmOmh1znQ5IsQrxWzxWDxO51e4urv+iVrSnIHbq4zqJIOiyNdYLa+BVjwOtdetcQx1lWPpiQ=="],
- "@inquirer/prompts/@inquirer/select/@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="],
+ "@inquirer/prompts/@inquirer/select/@inquirer/type": ["@inquirer/type@4.0.4", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-PamArxO3cFJZoOzspzo6cxVlLeIftyBsZw/S9bKY5DzxqJVZgjoj1oP8d0rskKtp7sZxBycsoer1g6UeJV1BBA=="],
- "@inquirer/rawlist/@inquirer/core/@inquirer/figures": ["@inquirer/figures@2.0.3", "", {}, "sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g=="],
+ "@inquirer/rawlist/@inquirer/core/@inquirer/figures": ["@inquirer/figures@2.0.4", "", {}, "sha512-eLBsjlS7rPS3WEhmOmh1znQ5IsQrxWzxWDxO51e4urv+iVrSnIHbq4zqJIOiyNdYLa+BVjwOtdetcQx1lWPpiQ=="],
"@inquirer/rawlist/@inquirer/core/mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="],
"@inquirer/search/@inquirer/core/mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="],
+ "@langchain/community/@langchain/classic/langsmith": ["langsmith@0.5.15", "", { "dependencies": { "@types/uuid": "^10.0.0", "chalk": "^5.6.2", "console-table-printer": "^2.12.1", "p-queue": "^6.6.2", "semver": "^7.6.3", "uuid": "^10.0.0" }, "peerDependencies": { "@opentelemetry/api": "*", "@opentelemetry/exporter-trace-otlp-proto": "*", "@opentelemetry/sdk-trace-base": "*", "openai": "*", "ws": ">=7" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/exporter-trace-otlp-proto", "@opentelemetry/sdk-trace-base", "openai", "ws"] }, "sha512-S20JnYmIgqGBjA/WEn12ZZJjqd03O5wd8K9KgGBvsKXQBn0bYuFrr1w20L37PpcMmX3/cftpgJ6g2y8KoEmHLw=="],
+
+ "@langchain/core/langsmith/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
+
+ "@langchain/core/langsmith/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
+
+ "@langchain/langgraph-sdk/p-queue/eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
+
+ "@langchain/langgraph-sdk/p-queue/p-timeout": ["p-timeout@7.0.1", "", {}, "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg=="],
+
+ "@n8n/node-cli/@clack/prompts/@clack/core": ["@clack/core@0.5.0", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow=="],
+
+ "@n8n/node-cli/@clack/prompts/picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
+
+ "@n8n/node-cli/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
+
"@next/eslint-plugin-next/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"@oclif/core/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
@@ -2726,48 +3201,64 @@
"@scalar/openapi-parser/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
- "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
-
"@typespec/compiler/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"cliui/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
- "cliui/string-width/get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="],
-
"cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
"cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="],
+ "d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="],
+
"d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="],
- "eslint-plugin-import/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
+ "eslint-plugin-import/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="],
+
+ "eslint-plugin-jsx-a11y/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="],
+
+ "eslint-plugin-n8n-nodes-base/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0" } }, "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg=="],
- "eslint-plugin-jsx-a11y/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
+ "eslint-plugin-n8n-nodes-base/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@6.21.0", "", {}, "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg=="],
- "eslint-plugin-react/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
+ "eslint-plugin-n8n-nodes-base/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" } }, "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ=="],
- "eslint/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
+ "eslint-plugin-react/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="],
- "filelist/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
+ "eslint/minimatch/brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="],
+
+ "filelist/minimatch/brace-expansion": ["brace-expansion@2.0.3", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA=="],
"gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
+ "ibm-cloud-sdk-core/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "langchain/langsmith/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
+
+ "langchain/langsmith/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
+
"mdast-util-find-and-replace/unist-util-is/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"mdast-util-find-and-replace/unist-util-visit-parents/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"mdast-util-phrasing/unist-util-is/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
+ "oclif/@oclif/core/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+ "transliteration/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
+
+ "transliteration/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+ "transliteration/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
+
"widest-line/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"yargs/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
- "yargs/string-width/get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="],
-
"@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
@@ -2778,16 +3269,18 @@
"@eslint/eslintrc/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
- "@inquirer/prompts/@inquirer/confirm/@inquirer/core/@inquirer/figures": ["@inquirer/figures@2.0.3", "", {}, "sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g=="],
+ "@inquirer/prompts/@inquirer/confirm/@inquirer/core/@inquirer/figures": ["@inquirer/figures@2.0.4", "", {}, "sha512-eLBsjlS7rPS3WEhmOmh1znQ5IsQrxWzxWDxO51e4urv+iVrSnIHbq4zqJIOiyNdYLa+BVjwOtdetcQx1lWPpiQ=="],
"@inquirer/prompts/@inquirer/confirm/@inquirer/core/mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="],
- "@inquirer/prompts/@inquirer/input/@inquirer/core/@inquirer/figures": ["@inquirer/figures@2.0.3", "", {}, "sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g=="],
+ "@inquirer/prompts/@inquirer/input/@inquirer/core/@inquirer/figures": ["@inquirer/figures@2.0.4", "", {}, "sha512-eLBsjlS7rPS3WEhmOmh1znQ5IsQrxWzxWDxO51e4urv+iVrSnIHbq4zqJIOiyNdYLa+BVjwOtdetcQx1lWPpiQ=="],
"@inquirer/prompts/@inquirer/input/@inquirer/core/mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="],
"@inquirer/prompts/@inquirer/select/@inquirer/core/mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="],
+ "@langchain/community/@langchain/classic/langsmith/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
+
"@oclif/core/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"@oclif/plugin-not-found/@inquirer/prompts/@inquirer/checkbox/@inquirer/ansi": ["@inquirer/ansi@1.0.2", "", {}, "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ=="],
@@ -2838,18 +3331,32 @@
"@oclif/plugin-not-found/@inquirer/prompts/@inquirer/select/@inquirer/type": ["@inquirer/type@3.0.10", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA=="],
- "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
-
"eslint-plugin-import/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"eslint-plugin-jsx-a11y/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+ "eslint-plugin-n8n-nodes-base/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" } }, "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A=="],
+
+ "eslint-plugin-n8n-nodes-base/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" } }, "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A=="],
+
+ "eslint-plugin-n8n-nodes-base/@typescript-eslint/utils/@typescript-eslint/typescript-estree/globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="],
+
+ "eslint-plugin-n8n-nodes-base/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.3", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg=="],
+
+ "eslint-plugin-n8n-nodes-base/@typescript-eslint/utils/@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@1.4.3", "", { "peerDependencies": { "typescript": ">=4.2.0" } }, "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw=="],
+
"eslint-plugin-react/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"eslint/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"filelist/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+ "oclif/@oclif/core/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "transliteration/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "transliteration/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
"widest-line/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"@oclif/plugin-not-found/@inquirer/prompts/@inquirer/checkbox/@inquirer/core/mute-stream": ["mute-stream@2.0.0", "", {}, "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA=="],
@@ -2908,6 +3415,20 @@
"@oclif/plugin-not-found/@inquirer/prompts/@inquirer/select/@inquirer/core/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
+ "eslint-plugin-n8n-nodes-base/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
+
+ "eslint-plugin-n8n-nodes-base/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
+
+ "eslint-plugin-n8n-nodes-base/@typescript-eslint/utils/@typescript-eslint/typescript-estree/globby/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
+
+ "eslint-plugin-n8n-nodes-base/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.3", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA=="],
+
+ "oclif/@oclif/core/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "transliteration/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "transliteration/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
"@oclif/plugin-not-found/@inquirer/prompts/@inquirer/checkbox/@inquirer/core/wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"@oclif/plugin-not-found/@inquirer/prompts/@inquirer/checkbox/@inquirer/core/wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
@@ -2948,6 +3469,8 @@
"@oclif/plugin-not-found/@inquirer/prompts/@inquirer/select/@inquirer/core/wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+ "eslint-plugin-n8n-nodes-base/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
"@oclif/plugin-not-found/@inquirer/prompts/@inquirer/checkbox/@inquirer/core/wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"@oclif/plugin-not-found/@inquirer/prompts/@inquirer/confirm/@inquirer/core/wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
diff --git a/integrations/n8n/.gitignore b/integrations/n8n/.gitignore
new file mode 100644
index 00000000..4390784d
--- /dev/null
+++ b/integrations/n8n/.gitignore
@@ -0,0 +1,11 @@
+# Build output (covered by root .gitignore, explicit for clarity)
+dist/
+
+# E2e tests (local only, not run in CI)
+e2e/
+
+# Test artifacts
+test-results/
+
+# npm pack output
+*.tgz
diff --git a/integrations/n8n/.npmrc b/integrations/n8n/.npmrc
new file mode 100644
index 00000000..521a9f7c
--- /dev/null
+++ b/integrations/n8n/.npmrc
@@ -0,0 +1 @@
+legacy-peer-deps=true
diff --git a/integrations/n8n/CHANGELOG.md b/integrations/n8n/CHANGELOG.md
new file mode 100644
index 00000000..781d3e60
--- /dev/null
+++ b/integrations/n8n/CHANGELOG.md
@@ -0,0 +1,40 @@
+# Changelog
+
+## 2.0.0 (2026-04-03)
+
+### New
+
+- **Auto-generated from OpenAPI** with full v1 backward compatibility
+- **New resources:** Member, Read Member, Link Preview, Chat Export, Search, Security
+- **Task resource** now supports full CRUD (was create-only)
+- **Auto-pagination** with Return All / Limit (cursor-based)
+- **AI Tool Use** support (`usableAsTool: true`)
+- **Pachca Trigger** node with automatic webhook registration
+- **English descriptions** for common fields
+- **Bot update** with dedicated webhookUrl field
+
+### Security
+
+- **Webhook signature verification** — HMAC-SHA256 via `pachca-signature` header
+- **IP allowlist** — restrict incoming webhooks by source IP (`Webhook Allowed IPs` credential field)
+- **Replay protection** — reject events older than 5 minutes or timestamped >1 minute in the future
+- **Filename sanitization** — strip control characters and null bytes, truncate to 255 chars
+
+### Bug Fixes
+
+- Fix `do-while` + `continue` retry bug in pagination — retries on 429/502/503 no longer silently exit the loop when cursor is undefined
+- Fix `resolveResourceLocator` — throw on null/undefined/empty values instead of passing them downstream
+- Fix `splitAndValidateCommaList` — reject float values (e.g. `3.14`) in integer mode (`Number.isInteger` instead of `isNaN`)
+- Fix replay protection — reject far-future timestamps (>1 minute ahead), not just old ones
+
+### v1 Compatibility
+
+All v1 workflows continue to work without changes:
+
+- Resource values preserved: `reactions`, `status`, `customFields`
+- Operation values preserved: `send`, `getById`, `addReaction`, etc.
+- Parameter names preserved: `reactionsMessageId`, `threadMessageId`, etc.
+- Legacy pagination (`per`/`page`) supported alongside cursor-based
+- v1 alias operations: `getMembers`, `addUsers`, `removeUser`, `updateRole`, `leaveChat`, `getReadMembers`, `unfurl`
+- v1 collections: `paginationOptions`, `filterOptions`, `additionalOptions`
+- v1 hidden params: `getAllUsersNoLimit`, `buttonLayout`
diff --git a/integrations/n8n/LICENSE b/integrations/n8n/LICENSE
new file mode 100644
index 00000000..a69869f3
--- /dev/null
+++ b/integrations/n8n/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 Pachca
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/integrations/n8n/README.md b/integrations/n8n/README.md
new file mode 100644
index 00000000..f26cae17
--- /dev/null
+++ b/integrations/n8n/README.md
@@ -0,0 +1,280 @@
+# n8n-nodes-pachca
+
+Community node for [n8n](https://n8n.io/) to interact with the [Pachca API](https://dev.pachca.com/).
+
+Auto-generated from the [OpenAPI spec](https://github.com/pachca/openapi) — always in sync with the latest API.
+
+## Installation
+
+In your n8n instance:
+
+1. Go to **Settings > Community Nodes**
+2. Enter `n8n-nodes-pachca`
+3. Click **Install**
+
+Or install via CLI:
+
+```bash
+npm install n8n-nodes-pachca
+```
+
+Or install from archive:
+
+```bash
+# Via npm (recommended)
+wget https://github.com/pachca/openapi/releases/latest/download/n8n-nodes-pachca.tgz
+cd ~/.n8n/nodes && npm install ./n8n-nodes-pachca.tgz
+
+# Or extract directly (Docker, no npm needed)
+wget https://github.com/pachca/openapi/releases/latest/download/n8n-nodes-pachca.tgz
+tar -xzf n8n-nodes-pachca.tgz -C ~/.n8n/nodes/
+
+# Restart n8n
+```
+
+## Nodes
+
+### Pachca
+
+Main node for interacting with the Pachca API. Supports 18 resources and 65+ operations:
+
+| Resource | Operations |
+|----------|-----------|
+| Bot | Update, Get Many Events, Remove Events |
+| Chat | Create, Get, Get Many, Update, Archive, Unarchive |
+| Chat Export | Create, Get |
+| Chat Member | Get Many, Create, Delete, Update, Leave, Add/Remove Group Tags |
+| Custom Property | Get |
+| File | Create (S3 two-stage upload) |
+| Form | Create (visual builder, templates, or JSON) |
+| Group Tag | Create, Get, Get Many, Update, Delete, Get Many Users |
+| Link Preview | Create |
+| Message | Create, Get, Get Many, Update, Delete, Pin, Unpin |
+| Profile | Get Info, Get, Update/Delete Avatar, Get/Update/Delete Status |
+| Reaction | Create, Delete, Get Many |
+| Read Member | Get Many |
+| Search | Chats, Messages, Users |
+| Security | Get Many |
+| Task | Create, Get, Get Many, Update, Delete |
+| Thread | Create, Get |
+| User | Create, Get, Get Many, Update, Delete, Update/Delete Avatar, Get/Update/Delete Status |
+
+### Pachca Trigger
+
+Webhook-based trigger that listens for 16 Pachca event types:
+
+| Category | Events |
+|----------|--------|
+| Messages | new, updated, deleted |
+| Reactions | new, deleted |
+| Interactive | button pressed, form submitted, link shared |
+| Chat members | added, removed |
+| Users | invited, confirmed, activated, suspended, updated, deleted |
+| Wildcard | all events |
+
+Automatically registers webhooks via the Bot API when workflow is activated. Deactivation removes the webhook.
+
+## Credentials
+
+Create a **Pachca API** credential with:
+
+| Field | Required | Description |
+|-------|----------|-------------|
+| **Base URL** | no | Default: `https://api.pachca.com/api/shared/v1`. Change only for on-premise. |
+| **Access Token** | yes | Bot or personal API token |
+| **Bot ID** | no | For automatic webhook registration in Trigger. Auto-detected from bot tokens. Set explicitly for admin tokens. |
+| **Signing Secret** | no | For HMAC-SHA256 verification of incoming webhooks (`pachca-signature` header) |
+| **Webhook Allowed IPs** | no | Comma-separated IPs allowed to send webhooks. Pachca sends from `37.200.70.177`. Empty = allow all. |
+
+**Where to get tokens:**
+
+- **Bot token** — Bot settings > API tab
+- **Personal token** — Settings > Automations > API
+
+Credentials are tested by calling `GET /oauth/token/info`.
+
+## Key Features
+
+### Pagination
+
+All list operations (Get Many) support automatic cursor-based pagination:
+
+- **Return All** = true — fetches all pages automatically
+- **Return All** = false — returns up to **Limit** results (default: 50)
+
+### Simplify
+
+Toggle **Simplify** (on by default) to return only key fields from API responses. Turn off for full response.
+
+- **Message** — id, entity_id, chat_id, content, user_id, created_at
+- **Chat** — id, name, channel, public, members_count, created_at
+- **User** — id, first_name, last_name, nickname, email, role, suspended
+- **Task** — id, content, kind, status, priority, due_at, created_at
+
+### Searchable Dropdowns
+
+Chat ID and User ID fields support search by name — type to find instead of entering numeric IDs.
+
+### Message Buttons
+
+Create messages with interactive buttons via JSON:
+
+```json
+[[{"text": "Approve", "data": "approve"}, {"text": "Reject", "data": "reject"}]]
+```
+
+Button types: **URL** (opens link) and **Data** (sends `button_pressed` webhook event).
+
+### Forms
+
+Create modal forms with three builder modes:
+
+- **Visual Builder** — add blocks (text input, select, radio, checkboxes, date/time, file upload)
+- **Templates** — predefined forms (Feedback, Time Off, Survey, Bug Report)
+- **JSON** — paste blocks from the visual playground
+
+Forms require a `trigger_id` from a `button_pressed` webhook event (valid for 3 seconds).
+
+### Avatar Upload
+
+Upload avatar images for profiles and users via `multipart/form-data`. Uses binary data from a previous node (HTTP Request, Read Binary File). The **Input Binary Field** parameter (default: `data`) specifies which binary property contains the image.
+
+### File Upload
+
+Two-stage S3 upload with automatic retry. Sources: **URL** or **Binary Data** from previous workflow nodes.
+
+### AI Tool Use
+
+Both nodes have `usableAsTool: true` — they can be used as tools for AI Agent nodes in n8n.
+
+### Error Handling
+
+- **5xx/429 retry** — exponential backoff with jitter, respects `Retry-After` header (up to 5 attempts)
+- **continueOnFail** — supported on all operations
+
+## v1 Compatibility
+
+Version 2 is fully backward compatible with v1. Existing v1 workflows continue to work without modification.
+
+The node uses the **VersionedNodeType** pattern with `defaultVersion: 2`:
+
+- Existing nodes keep `typeVersion: 1` with v1 UI and parameter names
+- New nodes get `typeVersion: 2` with cleaner naming and new resources
+- A shared router translates v1 names to v2 at runtime
+
+**Renamed resources:** `reactions` → `reaction`, `status` → `profile`, `customFields` → `customProperty`
+
+**Renamed operations:** `send` → `create`, `getById` → `get`, `addReaction` → `create`, etc.
+
+**New v2 resources:** Chat Member, Custom Property, Read Member, Link Preview, Search, Chat Export, Security
+
+To upgrade a v1 node: delete it, add a new Pachca node (defaults to v2), reconfigure with v2 names. API calls are identical.
+
+## Usage Examples
+
+### Send a message
+
+Set **Resource** = Message, **Operation** = Create:
+
+- **Entity ID** — chat ID (use searchable dropdown or enter number)
+- **Content** — message text (supports Markdown)
+
+### Send a message with buttons
+
+Set **Resource** = Message, **Operation** = Create, then in **Additional Fields** add **Buttons**:
+
+```json
+[[{"text": "Approve", "data": "approve"}, {"text": "Reject", "data": "reject"}]]
+```
+
+When a user clicks a Data button, the Pachca Trigger node receives a `button_pressed` event with the button's `data` value.
+
+### Upload a file and attach to message
+
+1. **Pachca** (File > Create) — upload file from URL or binary data, get `key` in response
+2. **Pachca** (Message > Create) — send message with the file key in the `files` field
+
+### Search and process results
+
+Set **Resource** = Search, **Operation** = Get Many Messages:
+
+- **Query** — search text
+- **Return All** = false, **Limit** = 10
+
+### Open a modal form
+
+1. **Pachca Trigger** — event `Button Pressed` (provides `trigger_id`)
+2. **Pachca** (Form > Create) — set **Title**, **Trigger ID**, choose builder mode
+3. **Pachca Trigger** — event `Form Submitted` (in a separate workflow)
+
+### Bot echo workflow
+
+1. **Pachca Trigger** — event `New Message`
+2. **IF** — filter: `message.user_id` ≠ bot ID
+3. **Pachca** (Message > Create) — echo the message back
+
+## Troubleshooting
+
+### 401 Unauthorized
+
+Token is invalid or expired. Check **Access Token** in Credentials. For bot tokens: Bot settings > API tab. For personal tokens: Settings > Automations > API.
+
+### 403 Forbidden
+
+Token lacks required scopes for this operation. Check available scopes in your token settings. Admin operations (managing users, tags, security log) require admin-level scopes.
+
+### 429 Too Many Requests
+
+Rate limit exceeded (~4 req/sec for messages, ~50 req/sec for other operations). The node retries automatically with exponential backoff. For bulk operations, add a **Wait** node between steps.
+
+### Webhook not received (Trigger)
+
+1. **Bot not in chat** — bot only receives events from chats it's a member of
+2. **Workflow not activated** — click **Activate** in the top right
+3. **Bot ID missing** — set Bot ID in Credentials for auto-registration, or configure webhook URL manually in bot settings
+4. **n8n not reachable** — Pachca can't send webhooks to `localhost`. Use a tunnel (ngrok, Cloudflare Tunnel) or deploy to a server with a public IP
+
+### Form not opening
+
+`trigger_id` from a `button_pressed` event expires in **3 seconds**. Place the Form > Create node immediately after the Trigger, with no Wait nodes or long operations in between.
+
+### Node not showing in n8n
+
+1. Restart n8n after installing the package
+2. Check n8n logs for loading errors
+3. Verify the package is in the correct directory (`~/.n8n/nodes/` or `~/.n8n/custom/node_modules/`)
+
+## Support
+
+- **GitHub Issues** — [github.com/pachca/openapi/issues](https://github.com/pachca/openapi/issues)
+- **Pachca API docs** — [dev.pachca.com](https://dev.pachca.com)
+- **n8n Community** — [community.n8n.io](https://community.n8n.io)
+
+## Development
+
+This node is auto-generated from the OpenAPI specification:
+
+```bash
+# Generate node files from OpenAPI
+bun run integrations/n8n/scripts/generate-n8n.ts
+
+# Run tests
+cd integrations/n8n && npx vitest run
+
+# Type check
+cd integrations/n8n && npx tsc --noEmit
+
+# Build for distribution
+cd integrations/n8n && npx n8n-node build
+
+# Full CI check (from repo root)
+npx turbo check
+```
+
+See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for architecture, local testing, and adding new endpoints.
+
+See [docs/CONTRIBUTORS.md](docs/CONTRIBUTORS.md) for project structure and contribution guidelines.
+
+## License
+
+MIT
diff --git a/integrations/n8n/changelog.json b/integrations/n8n/changelog.json
new file mode 100644
index 00000000..cf3b23f4
--- /dev/null
+++ b/integrations/n8n/changelog.json
@@ -0,0 +1,16 @@
+[
+ {
+ "version": "2.0.0",
+ "date": "2026-04-03",
+ "changes": [
+ { "type": "+", "scope": "all", "description": "Auto-generated from OpenAPI with full v1 backward compatibility" },
+ { "type": "+", "scope": "resource", "description": "New: Member, Read Member, Link Preview, Search, Security" },
+ { "type": "+", "scope": "resource", "description": "Task resource now supports full CRUD (was create-only)" },
+ { "type": "+", "scope": "feature", "description": "Auto-pagination with Return All / Limit (cursor-based)" },
+ { "type": "+", "scope": "feature", "description": "AI Tool Use support (usableAsTool: true)" },
+ { "type": "+", "scope": "trigger", "description": "Pachca Trigger with automatic webhook registration" },
+ { "type": "+", "scope": "feature", "description": "English descriptions for common fields" },
+ { "type": "+", "scope": "feature", "description": "Bot update with dedicated webhookUrl field" }
+ ]
+ }
+]
diff --git a/integrations/n8n/credentials/PachcaApi.credentials.ts b/integrations/n8n/credentials/PachcaApi.credentials.ts
new file mode 100644
index 00000000..76bbbfe3
--- /dev/null
+++ b/integrations/n8n/credentials/PachcaApi.credentials.ts
@@ -0,0 +1,73 @@
+import type {
+ IAuthenticateGeneric,
+ ICredentialTestRequest,
+ ICredentialType,
+ INodeProperties,
+} from 'n8n-workflow';
+
+export class PachcaApi implements ICredentialType {
+ name = 'pachcaApi';
+ displayName = 'Pachca API';
+ icon = { light: 'file:pachca.svg', dark: 'file:pachca.dark.svg' } as const;
+ documentationUrl = 'https://dev.pachca.com/api/authorization';
+
+ properties: INodeProperties[] = [
+ {
+ displayName: 'Base URL',
+ name: 'baseUrl',
+ type: 'string',
+ default: 'https://api.pachca.com/api/shared/v1',
+ description: 'Base URL of the Pachca API. Change only for on-premise installations or API proxies.',
+ },
+ {
+ displayName: 'Access Token',
+ name: 'accessToken',
+ type: 'string',
+ typeOptions: { password: true },
+ default: '',
+ },
+ {
+ displayName: 'Bot ID',
+ name: 'botId',
+ type: 'number',
+ default: 0,
+ description: 'Bot ID for automatic webhook registration (found in bot settings). Leave empty to auto-detect from token. Only needed for Pachca Trigger node.',
+ hint: 'Only required when using a bot token with the Pachca Trigger node',
+ },
+ {
+ displayName: 'Webhook Signing Secret',
+ name: 'signingSecret',
+ type: 'string',
+ typeOptions: { password: true },
+ default: '',
+ description: 'Used to verify incoming webhook requests from Pachca. Found in bot settings under the Webhook section.',
+ hint: 'Only required when using the Pachca Trigger node',
+ },
+ {
+ displayName: 'Webhook Allowed IPs',
+ name: 'webhookAllowedIps',
+ type: 'string',
+ default: '',
+ description: 'Comma-separated list of IP addresses allowed to send webhooks. Pachca sends from 37.200.70.177. Leave empty to allow all.',
+ placeholder: '37.200.70.177',
+ hint: 'Only used with the Pachca Trigger node',
+ },
+ ];
+
+ authenticate: IAuthenticateGeneric = {
+ type: 'generic',
+ properties: {
+ headers: {
+ Authorization: '=Bearer {{$credentials.accessToken}}',
+ },
+ },
+ };
+
+ test: ICredentialTestRequest = {
+ request: {
+ baseURL: '={{$credentials.baseUrl}}',
+ url: '/oauth/token/info',
+ method: 'GET',
+ },
+ };
+}
diff --git a/integrations/n8n/credentials/pachca.dark.svg b/integrations/n8n/credentials/pachca.dark.svg
new file mode 100644
index 00000000..b34b4743
--- /dev/null
+++ b/integrations/n8n/credentials/pachca.dark.svg
@@ -0,0 +1,3 @@
+
diff --git a/integrations/n8n/credentials/pachca.svg b/integrations/n8n/credentials/pachca.svg
new file mode 100644
index 00000000..21b5d0d2
--- /dev/null
+++ b/integrations/n8n/credentials/pachca.svg
@@ -0,0 +1,3 @@
+
diff --git a/integrations/n8n/docs/CONTRIBUTORS.md b/integrations/n8n/docs/CONTRIBUTORS.md
new file mode 100644
index 00000000..be0779ef
--- /dev/null
+++ b/integrations/n8n/docs/CONTRIBUTORS.md
@@ -0,0 +1,72 @@
+# Contributors
+
+## How to Contribute
+
+1. Fork the repository
+2. Create a feature branch from `main`
+3. Make your changes
+4. Run `npx turbo build && npx turbo check` to verify
+5. Submit a pull request
+
+## Project Structure
+
+```
+integrations/n8n/
+├── scripts/ # Generator code
+│ ├── generate-n8n.ts # Main generator (~1000 lines)
+│ ├── freeze-v1.ts # Captures V1 snapshot from npm
+│ └── utils.ts # Body field extraction helpers
+├── nodes/Pachca/ # Node files
+│ ├── Pachca.node.ts # VersionedNodeType wrapper (generated)
+│ ├── SharedRouter.ts # Shared router with V1→V2 translation (generated)
+│ ├── GenericFunctions.ts # Utilities (hand-written)
+│ ├── PachcaTrigger.node.ts # Trigger node (hand-written)
+│ ├── V1/
+│ │ ├── PachcaV1.node.ts # Frozen V1 class
+│ │ └── *Description.ts # 12 frozen V1 descriptions
+│ └── V2/
+│ ├── PachcaV2.node.ts # Generated V2 class
+│ ├── FormDescription.ts # Form resource (hand-written)
+│ └── *Description.ts # 18 generated V2 descriptions
+├── credentials/ # Credential type (generated)
+├── icons/ # Node icons (SVG adaptive + PNG)
+├── tests/ # Unit + contract tests (vitest)
+├── e2e/ # Playwright E2E tests
+├── examples/ # Example workflow JSON files
+├── docs/ # Documentation
+└── changelog.json # Version history
+```
+
+## Key Guidelines
+
+- **Do not manually edit generated files** — modify the generator instead
+- **Do not edit V1/ files** — they are frozen from npm v1.0.27 and ensure backward compatibility
+- **GenericFunctions.ts**, **V2/FormDescription.ts**, and **PachcaTrigger.node.ts** are hand-written — edit directly
+- **Always run tests** after changes: `cd integrations/n8n && npx vitest run`
+- **Follow n8n conventions**: ESLint with `@n8n/node-cli` rules
+- **Update changelog.json** when making changes that warrant a release
+
+## Generated vs Hand-Written
+
+The generator (`scripts/generate-n8n.ts`) reads the OpenAPI spec and produces:
+- VersionedNodeType wrapper (`Pachca.node.ts`) with V1/V2 class references
+- Shared router (`SharedRouter.ts`) with V1 name translation maps and route table
+- V2 resource descriptions with operations, parameters, and routing
+- Credential type with fields and authentication
+
+Hand-written files handle logic that can't be expressed declaratively:
+- Cursor-based pagination with retry
+- S3 two-stage file upload
+- Webhook signature verification
+- Form template resolution
+- Button layout transformation
+- Error message extraction and formatting
+- 5xx/429 retry with exponential backoff and jitter
+
+## Maintainers
+
+- [Pachca](https://github.com/pachca) — API specification and node generator
+
+## License
+
+MIT License — see [LICENSE](../LICENSE) for details.
diff --git a/integrations/n8n/docs/DEVELOPMENT.md b/integrations/n8n/docs/DEVELOPMENT.md
new file mode 100644
index 00000000..9c17f156
--- /dev/null
+++ b/integrations/n8n/docs/DEVELOPMENT.md
@@ -0,0 +1,136 @@
+# Development Guide
+
+## Architecture
+
+The n8n node is **auto-generated** from the Pachca OpenAPI specification using the **VersionedNodeType** pattern (same as Slack, Gmail, HTTP Request in n8n-nodes-base):
+
+```
+TypeSpec (typespec.tsp)
+ |
+OpenAPI YAML (openapi.yaml + overlay.en.yaml)
+ |
+n8n Node Generator (scripts/generate-n8n.ts)
+ |
+nodes/Pachca/
+|- Pachca.node.ts <- VersionedNodeType wrapper (defaultVersion: 2)
+|- SharedRouter.ts <- Shared router with V1->V2 name translation
+|- V1/
+| |- PachcaV1.node.ts <- Frozen V1 class (from npm v1.0.27)
+| +- *Description.ts <- 12 frozen V1 description files
++- V2/
+ |- PachcaV2.node.ts <- Generated V2 class
+ +- *Description.ts <- 18 generated V2 description files
+```
+
+- Existing workflows keep `typeVersion: 1` -> load PachcaV1 with V1 UI
+- New workflows get `typeVersion: 2` -> load PachcaV2 with clean V2 UI
+- Node Creator shows only V2 operations (no duplicates)
+- V1 nodes show a yellow "New node version available" banner
+
+### Generated Files (do not edit manually)
+
+| File | What is generated |
+|------|-------------------|
+| `Pachca.node.ts` | VersionedNodeType wrapper with V1/V2 classes |
+| `SharedRouter.ts` | Shared router with V1_RESOURCE_MAP, V1_OP_MAP, and ROUTES |
+| `V2/PachcaV2.node.ts` | V2 node class with resource list |
+| `V2/*Description.ts` | 18 V2 resource descriptions (operations, fields, routing) |
+| `PachcaApi.credentials.ts` | Credential type with fields and test endpoint |
+
+### Frozen V1 Files (do not edit)
+
+| File | What it contains |
+|------|-----------------|
+| `V1/PachcaV1.node.ts` | Frozen V1 node class from npm v1.0.27 |
+| `V1/*Description.ts` | 12 frozen V1 resource descriptions |
+
+These files were captured by `scripts/freeze-v1.ts` and must not be modified. They ensure existing V1 workflows continue to work.
+
+### Hand-Written Files
+
+| File | Purpose |
+|------|---------|
+| `GenericFunctions.ts` | Cursor paginator, body wrapper, S3 upload, error handler, button transformer, form resolver, webhook signature verification, 5xx/429 retry with backoff |
+| `PachcaTrigger.node.ts` | Webhook trigger with auto-registration, 16 event types, signature verification |
+| `V2/FormDescription.ts` | Form resource with template/JSON builder modes |
+
+### Source Data
+
+| Source | What it provides |
+|--------|-----------------|
+| `@pachca/openapi-parser` | API endpoints, schemas, parameters |
+| `packages/spec/workflows.ts` | English descriptions for operations |
+| `packages/spec/examples.ts` | Form templates (Feedback, Time Off, Survey, Bug Report) |
+| `packages/generator/src/naming.ts` | Case conversion utilities |
+| `apps/docs/lib/openapi/mapper.ts` | URL generation for API docs |
+
+## Local Development
+
+```bash
+# Install dependencies
+bun install
+
+# Generate the node from OpenAPI
+bun run integrations/n8n/scripts/generate-n8n.ts
+
+# Run unit + contract tests (excludes e2e)
+cd integrations/n8n && npx vitest run
+
+# Type check
+cd integrations/n8n && npx tsc --noEmit
+
+# Lint (n8n community node rules)
+cd integrations/n8n && npx eslint nodes/ credentials/
+
+# Build for distribution
+cd integrations/n8n && npx n8n-node build
+
+# Full CI check (from repo root)
+npx turbo check
+```
+
+## Testing Locally in n8n
+
+```bash
+# Build the node
+cd integrations/n8n && npx n8n-node build
+
+# Symlink into n8n's custom extensions directory
+mkdir -p ~/.n8n/custom/node_modules
+ln -sf "$(pwd)" ~/.n8n/custom/node_modules/n8n-nodes-pachca
+
+# Restart n8n
+pkill -f n8n; npx n8n start
+```
+
+> **Note:** `~/.n8n/custom/node_modules/` is scanned on startup without needing a DB record.
+> Community nodes installed via UI (`~/.n8n/node_modules/`) require a record in `installed_packages` table — manual `npm install` there won't work.
+
+## Adding New API Endpoints
+
+1. Add the endpoint to `packages/spec/typespec.tsp`
+2. Run `npx turbo build --filter=@pachca/spec --force` to regenerate OpenAPI YAML
+3. Run `bun run integrations/n8n/scripts/generate-n8n.ts` to regenerate V2 node files
+4. Add English descriptions to `packages/spec/workflows.ts` if needed
+5. Run tests: `cd integrations/n8n && npx vitest run`
+6. Run full check: `npx turbo check`
+
+> V1 files are frozen and never regenerated. New endpoints appear only in V2.
+
+## Test Structure
+
+| Test File | What it covers |
+|-----------|---------------|
+| `tests/contract.test.ts` | Generated output matches OpenAPI spec (endpoints, methods, parameters) |
+| `tests/compatibility.test.ts` | V1 backward compatibility (operations, parameters, pagination) |
+| `tests/generic-functions.test.ts` | Unit tests for GenericFunctions.ts (paginator, buttons, forms, upload) |
+| `e2e/` | Playwright E2E tests against a real n8n instance (mock + integration) |
+
+## Versioning
+
+- **npm version**: SemVer (`MAJOR.MINOR.PATCH`), current: `2.0.0`
+- **n8n node version**: VersionedNodeType with `defaultVersion: 2`
+ - `typeVersion: 1` — loads PachcaV1 (frozen V1 class)
+ - `typeVersion: 2` — loads PachcaV2 (generated V2 class)
+- **Source of truth**: `changelog.json`
+- **CI**: Reads version from `changelog.json[0].version`, compares with npm, publishes if new
diff --git a/integrations/n8n/eslint.config.mjs b/integrations/n8n/eslint.config.mjs
new file mode 100644
index 00000000..c2643cfe
--- /dev/null
+++ b/integrations/n8n/eslint.config.mjs
@@ -0,0 +1,26 @@
+import { config } from '@n8n/node-cli/eslint';
+
+export default [
+ ...config,
+ { ignores: ['scripts/', 'tests/', 'dist/', 'e2e/', 'nodes/Pachca/V1/'] },
+ {
+ rules: {
+ // Optional filter query params use empty default to mean "no filter"
+ 'n8n-nodes-base/node-param-default-wrong-for-options': 'off',
+ },
+ },
+ {
+ files: ['nodes/Pachca/V2/PachcaV2.node.ts'],
+ rules: {
+ // Icon is set in baseDescription (VersionedNodeType wrapper), not in version class
+ '@n8n/community-nodes/icon-validation': 'off',
+ },
+ },
+ {
+ files: ['credentials/**/*.ts'],
+ rules: {
+ // tokenType is a dropdown (options), not a secret — false positive on "token" in name
+ '@n8n/community-nodes/credential-password-field': 'off',
+ },
+ },
+];
diff --git a/integrations/n8n/examples/basic-usage.json b/integrations/n8n/examples/basic-usage.json
new file mode 100644
index 00000000..09d308a7
--- /dev/null
+++ b/integrations/n8n/examples/basic-usage.json
@@ -0,0 +1,69 @@
+{
+ "name": "Pachca Basic Usage",
+ "nodes": [
+ {
+ "parameters": {},
+ "id": "trigger",
+ "name": "When clicking 'Test workflow'",
+ "type": "n8n-nodes-base.manualTrigger",
+ "typeVersion": 1,
+ "position": [0, 0]
+ },
+ {
+ "parameters": {
+ "resource": "message",
+ "operation": "create",
+ "entityId": 123,
+ "content": "Hello from n8n!"
+ },
+ "id": "send-message",
+ "name": "Send Message",
+ "type": "n8n-nodes-pachca.pachca",
+ "typeVersion": 2,
+ "position": [220, 0],
+ "credentials": {
+ "pachcaApi": {
+ "id": "1",
+ "name": "Pachca API"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "resource": "chat",
+ "operation": "getAll",
+ "returnAll": false,
+ "limit": 10
+ },
+ "id": "list-chats",
+ "name": "List Chats",
+ "type": "n8n-nodes-pachca.pachca",
+ "typeVersion": 2,
+ "position": [220, 200],
+ "credentials": {
+ "pachcaApi": {
+ "id": "1",
+ "name": "Pachca API"
+ }
+ }
+ }
+ ],
+ "connections": {
+ "When clicking 'Test workflow'": {
+ "main": [
+ [
+ {
+ "node": "Send Message",
+ "type": "main",
+ "index": 0
+ },
+ {
+ "node": "List Chats",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ }
+}
diff --git a/integrations/n8n/icons/pachca.dark.svg b/integrations/n8n/icons/pachca.dark.svg
new file mode 100644
index 00000000..b34b4743
--- /dev/null
+++ b/integrations/n8n/icons/pachca.dark.svg
@@ -0,0 +1,3 @@
+
diff --git a/integrations/n8n/icons/pachca.svg b/integrations/n8n/icons/pachca.svg
new file mode 100644
index 00000000..21b5d0d2
--- /dev/null
+++ b/integrations/n8n/icons/pachca.svg
@@ -0,0 +1,3 @@
+
diff --git a/integrations/n8n/index.js b/integrations/n8n/index.js
new file mode 100644
index 00000000..f053ebf7
--- /dev/null
+++ b/integrations/n8n/index.js
@@ -0,0 +1 @@
+module.exports = {};
diff --git a/integrations/n8n/nodes/Pachca/GenericFunctions.ts b/integrations/n8n/nodes/Pachca/GenericFunctions.ts
new file mode 100644
index 00000000..37f24d38
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/GenericFunctions.ts
@@ -0,0 +1,931 @@
+import type {
+ ICredentialDataDecryptedObject,
+ IDataObject,
+ IExecuteFunctions,
+ IHookFunctions,
+ IHttpRequestMethods,
+ IHttpRequestOptions,
+ ILoadOptionsFunctions,
+ INodeExecutionData,
+ INodeListSearchResult,
+ INodePropertyOptions,
+ IN8nHttpFullResponse,
+ JsonObject,
+} from 'n8n-workflow';
+import { NodeApiError, NodeOperationError } from 'n8n-workflow';
+import * as crypto from 'crypto';
+
+// ============================================================================
+// SHARED CONSTANTS
+// ============================================================================
+
+/** Human-readable field name mapping for error messages */
+const FIELD_DISPLAY_NAMES: Record = {
+ entity_id: 'Entity ID',
+ entity_type: 'Entity Type',
+ member_ids: 'Member IDs',
+ group_tag_ids: 'Group Tag IDs',
+ performer_ids: 'Performer IDs',
+ parent_message_id: 'Parent Message ID',
+ chat_id: 'Chat ID',
+ user_id: 'User ID',
+ first_name: 'First Name',
+ last_name: 'Last Name',
+ file_type: 'File Type',
+ due_at: 'Due Date',
+ callback_id: 'Callback ID',
+ trigger_id: 'Trigger ID',
+ outgoing_url: 'Webhook URL',
+};
+
+/** Friendly descriptions for common HTTP status codes */
+const STATUS_HINTS: Record = {
+ 401: 'Authentication failed. Check your API token in Pachca credentials.',
+ 403: 'Insufficient permissions. This operation may require an admin token or additional scopes.',
+ 404: 'The requested resource was not found. Check the ID value.',
+ 422: 'Validation error. Check the field values below.',
+ 429: 'Rate limit exceeded. Please wait and try again.',
+};
+
+/** Key fields per resource for Simplify mode — only these fields are returned */
+const SIMPLIFY_FIELDS: Record = {
+ message: ['id', 'entity_id', 'chat_id', 'content', 'user_id', 'created_at'],
+ chat: ['id', 'name', 'channel', 'public', 'member_ids', 'created_at'],
+ user: ['id', 'first_name', 'last_name', 'nickname', 'email', 'role', 'suspended'],
+ task: ['id', 'content', 'kind', 'status', 'priority', 'due_at', 'created_at'],
+ bot: ['id', 'webhook'],
+ groupTag: ['id', 'name', 'users_count'],
+ reaction: ['code', 'name', 'user_id', 'created_at'],
+ export: ['id', 'status', 'created_at'],
+};
+
+// ============================================================================
+// WEBHOOK SIGNATURE VERIFICATION
+// ============================================================================
+
+/**
+ * Verifies HMAC-SHA256 signature of incoming webhook requests.
+ * Uses timing-safe comparison to prevent timing attacks.
+ */
+export function verifyWebhookSignature(
+ body: string,
+ signature: string,
+ secret: string,
+): boolean {
+ const hmac = crypto.createHmac('sha256', secret);
+ hmac.update(body);
+ const expected = hmac.digest('hex');
+ try {
+ return crypto.timingSafeEqual(
+ Buffer.from(signature, 'hex'),
+ Buffer.from(expected, 'hex'),
+ );
+ } catch {
+ return false;
+ }
+}
+
+// ============================================================================
+// S3 FILE UPLOAD UTILITIES
+// ============================================================================
+
+/** MIME type lookup table (module-level to avoid re-creation per call) */
+const MIME_TYPES: Record = {
+ pdf: 'application/pdf',
+ jpg: 'image/jpeg',
+ jpeg: 'image/jpeg',
+ png: 'image/png',
+ gif: 'image/gif',
+ webp: 'image/webp',
+ heic: 'image/heic',
+ avif: 'image/avif',
+ svg: 'image/svg+xml',
+ doc: 'application/msword',
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ xls: 'application/vnd.ms-excel',
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ zip: 'application/zip',
+ mp4: 'video/mp4',
+ mov: 'video/quicktime',
+ mp3: 'audio/mpeg',
+ ogg: 'audio/ogg',
+ txt: 'text/plain',
+ csv: 'text/csv',
+ json: 'application/json',
+};
+
+/** Detect MIME type from file extension */
+export function detectMimeType(filename: string): string {
+ const ext = filename.split('.').pop()?.toLowerCase() ?? '';
+ return MIME_TYPES[ext] ?? 'application/octet-stream';
+}
+
+/**
+ * Build multipart/form-data body for S3 upload.
+ * Field order matters for S3: all policy fields first, then file last.
+ */
+export function buildMultipartBody(
+ fields: Record,
+ fileBuffer: Buffer,
+ fileName: string,
+ contentType: string,
+ fileFieldName = 'file',
+): { body: Buffer; contentType: string } {
+ const boundary = `----WebKitFormBoundary${crypto.randomBytes(16).toString('hex')}`;
+ const parts: Buffer[] = [];
+ // Sanitize filename: strip CRLF and quotes to prevent header injection
+ // eslint-disable-next-line no-control-regex
+ const safeName = fileName.replace(/[\x00-\x1f\x7f\\"]/g, '_').slice(0, 255);
+
+ // Policy fields first (order matters for S3)
+ for (const [key, value] of Object.entries(fields)) {
+ parts.push(
+ Buffer.from(
+ `--${boundary}\r\nContent-Disposition: form-data; name="${key}"\r\n\r\n${value}\r\n`,
+ ),
+ );
+ }
+
+ // File last
+ parts.push(
+ Buffer.from(
+ `--${boundary}\r\nContent-Disposition: form-data; name="${fileFieldName}"; filename="${safeName}"\r\nContent-Type: ${contentType}\r\n\r\n`,
+ ),
+ );
+ parts.push(fileBuffer);
+ parts.push(Buffer.from(`\r\n--${boundary}--\r\n`));
+
+ return {
+ body: Buffer.concat(parts),
+ contentType: `multipart/form-data; boundary=${boundary}`,
+ };
+}
+
+/**
+ * Upload avatar image via multipart/form-data.
+ * Reads binary data from the input and sends it to the avatar endpoint.
+ */
+export async function uploadAvatar(
+ ctx: IExecuteFunctions,
+ itemIndex: number,
+ url: string,
+): Promise {
+ const binaryProperty = ctx.getNodeParameter('image', itemIndex, 'data') as string;
+ const binaryData = ctx.helpers.assertBinaryData(itemIndex, binaryProperty);
+ const fileBuffer = await ctx.helpers.getBinaryDataBuffer(itemIndex, binaryProperty);
+ const fileName = binaryData.fileName || 'avatar.jpg';
+ const mimeType = binaryData.mimeType || detectMimeType(fileName);
+
+ const multipart = buildMultipartBody({}, fileBuffer, fileName, mimeType, 'image');
+
+ const credentials = await ctx.getCredentials('pachcaApi');
+ const base = sanitizeBaseUrl(credentials.baseUrl as string);
+
+ const response = await ctx.helpers.httpRequestWithAuthentication.call(ctx, 'pachcaApi', {
+ method: 'PUT',
+ url: `${base}${url}`,
+ body: multipart.body,
+ headers: { 'Content-Type': multipart.contentType },
+ });
+
+ if (typeof response === 'object' && response !== null) {
+ const data = (response as IDataObject).data;
+ return (data as IDataObject) ?? (response as IDataObject);
+ }
+ return { success: true } as unknown as IDataObject;
+}
+
+// ============================================================================
+// BOT ID AUTO-DETECTION
+// ============================================================================
+
+/**
+ * Resolve bot ID: use explicit credential value, or auto-detect via /oauth/token/info.
+ * Returns the bot's user_id if the token belongs to a bot, or 0 if it's a personal token.
+ * Throws on network/auth errors so callers can distinguish "not a bot" from "failed to check".
+ */
+export async function resolveBotId(
+ context: IHookFunctions,
+ credentials: ICredentialDataDecryptedObject,
+): Promise {
+ if (credentials.botId && Number(credentials.botId) > 0) {
+ return Number(credentials.botId);
+ }
+ const response = (await context.helpers.httpRequestWithAuthentication.call(
+ context,
+ 'pachcaApi',
+ {
+ method: 'GET',
+ url: `${credentials.baseUrl}/oauth/token/info`,
+ },
+ )) as IDataObject;
+ const data = response.data as IDataObject | undefined;
+ // Bot tokens have name: null, personal tokens have a user-defined name
+ if (data && data.name === null && data.user_id) {
+ return Number(data.user_id);
+ }
+ return 0;
+}
+
+/** Sanitize baseUrl: strip trailing slashes, validate protocol */
+export function sanitizeBaseUrl(url: string): string {
+ const trimmed = url.replace(/\/+$/, '');
+ if (!/^https?:\/\//.test(trimmed)) {
+ throw new Error(`Invalid Base URL: must start with http:// or https://. Got: ${trimmed}`);
+ }
+ return trimmed;
+}
+
+// ============================================================================
+// EXECUTE() MODE HELPERS
+// ============================================================================
+
+/**
+ * Make an authenticated API request to Pachca.
+ * Handles error responses with rich context (field names, status hints).
+ */
+export async function makeApiRequest(
+ this: IExecuteFunctions,
+ method: IHttpRequestMethods,
+ endpoint: string,
+ body?: IDataObject,
+ qs?: IDataObject,
+ itemIndex?: number,
+): Promise {
+ const credentials = await this.getCredentials('pachcaApi');
+ const baseUrl = sanitizeBaseUrl(credentials.baseUrl as string);
+ const headers: Record = { Accept: 'application/json' };
+ if (method !== 'GET' && method !== 'DELETE') {
+ headers['Content-Type'] = 'application/json';
+ }
+ // Separate array params from scalar params — arrays need []= format for Rails
+ let scalarQs: IDataObject | undefined = qs;
+ let arrayQuerySuffix = '';
+ if (qs) {
+ const scalarEntries: [string, unknown][] = [];
+ const arrayParts: string[] = [];
+ for (const [key, value] of Object.entries(qs)) {
+ if (Array.isArray(value)) {
+ for (const v of value) {
+ arrayParts.push(`${encodeURIComponent(key + '[]')}=${encodeURIComponent(String(v))}`);
+ }
+ } else {
+ scalarEntries.push([key, value]);
+ }
+ }
+ scalarQs = scalarEntries.length > 0 ? Object.fromEntries(scalarEntries) as IDataObject : undefined;
+ arrayQuerySuffix = arrayParts.join('&');
+ }
+
+ let url = `${baseUrl}${endpoint}`;
+ if (arrayQuerySuffix) {
+ url += (url.includes('?') ? '&' : '?') + arrayQuerySuffix;
+ }
+
+ const options: IHttpRequestOptions = {
+ method,
+ url,
+ headers,
+ qs: scalarQs,
+ body: (method !== 'GET' && method !== 'DELETE') ? body : undefined,
+ returnFullResponse: true,
+ ignoreHttpStatusErrors: true,
+ };
+
+ const response = (await this.helpers.httpRequestWithAuthentication.call(
+ this, 'pachcaApi', options,
+ )) as IN8nHttpFullResponse;
+
+ // Handle empty/null body (e.g. 204 No Content)
+ if (!response.body || typeof response.body !== 'object') {
+ if (response.statusCode >= 200 && response.statusCode < 300) {
+ return { success: true } as unknown as IDataObject;
+ }
+ }
+
+ if (response.statusCode >= 400) {
+ const resBody = (response.body ?? {}) as IDataObject;
+ const errors = resBody.errors as Array<{ key: string; value: string }> | undefined;
+
+ let message: string;
+ if (errors?.length) {
+ message = errors.map(e => {
+ const displayName = FIELD_DISPLAY_NAMES[e.key] || e.key;
+ return `${displayName}: ${e.value}`;
+ }).join('; ');
+ } else {
+ message = ((resBody.message || resBody.error || `Request failed with status ${response.statusCode}`) as string);
+ }
+
+ const hint = STATUS_HINTS[response.statusCode];
+ const description = hint ? `${hint}\n${message}` : message;
+
+ const apiError = new NodeApiError(this.getNode(), resBody as JsonObject, {
+ message,
+ httpCode: String(response.statusCode),
+ description,
+ itemIndex,
+ });
+ // Attach Retry-After for pagination retry logic
+ const headers = response.headers as Record | undefined;
+ if (headers?.['retry-after']) {
+ (apiError as NodeApiError & { retryAfter?: number }).retryAfter = parseInt(headers['retry-after'], 10) || 2;
+ }
+ throw apiError;
+ }
+
+ return response.body as IDataObject;
+}
+
+/**
+ * Paginated API request with cursor-based pagination and 429 retry.
+ * Handles returnAll/limit, v1 legacy per/page, and simplify.
+ */
+export async function makeApiRequestAllPages(
+ this: IExecuteFunctions,
+ method: IHttpRequestMethods,
+ endpoint: string,
+ qs: IDataObject,
+ itemIndex: number,
+ resource: string,
+ nodeVersion: number,
+): Promise {
+ // v1 legacy offset-based pagination
+ if (nodeVersion === 1) {
+ let legacyPer: number | undefined;
+ let legacyPage: number | undefined;
+ try {
+ const paginationOptions = this.getNodeParameter('paginationOptions', itemIndex, null) as
+ | { per?: number; page?: number }
+ | null;
+ if (paginationOptions) {
+ legacyPer = paginationOptions.per;
+ legacyPage = paginationOptions.page;
+ }
+ } catch { /* parameter doesn't exist */ }
+
+ if (legacyPer || legacyPage) {
+ const pageQs = { ...qs, per: legacyPer ?? 25, page: legacyPage ?? 1 };
+ const response = await makeApiRequest.call(this, method, endpoint, undefined, pageQs, itemIndex);
+ const items = (response.data as IDataObject[]) ?? [];
+ return items.map(item => ({ json: item }));
+ }
+
+ // V1-specific: collection pagination params already in qs (via optionalQueryMap + v1Collection).
+ // Original V1 used single-page manual pagination for these endpoints.
+ const hasV1CollectionPagination = ('per' in qs || 'page' in qs ||
+ (qs.cursor !== undefined && qs.cursor !== '') ||
+ (qs.limit !== undefined && qs.limit !== ''));
+ if (hasV1CollectionPagination) {
+ const response = await makeApiRequest.call(this, method, endpoint, undefined, qs, itemIndex);
+ const items = (response.data as IDataObject[]) ?? [];
+ return items.map(item => ({
+ json: typeof item === 'object' && item !== null ? item : { value: item } as unknown as IDataObject,
+ }));
+ }
+ }
+
+ // Check returnAll / getAllUsersNoLimit (v1 alias)
+ let returnAll = false;
+ try { returnAll = this.getNodeParameter('returnAll', itemIndex, false) as boolean; } catch { /* */ }
+ if (!returnAll) {
+ try { returnAll = this.getNodeParameter('getAllUsersNoLimit', itemIndex, false) as boolean; } catch { /* */ }
+ }
+
+ const limit = returnAll ? 0 : ((this.getNodeParameter('limit', itemIndex, 50) as number) || 50);
+ const results: IDataObject[] = [];
+ let cursor: string | undefined;
+ let totalRetries = 0;
+ const MAX_RETRIES = 5;
+ const MAX_PAGES = 1000;
+ let pageCount = 0;
+
+ do {
+ const perPage = returnAll ? 200 : limit;
+ const pageQs: IDataObject = { ...qs, limit: perPage };
+ if (cursor) pageQs.cursor = cursor;
+
+ // Inner retry loop: avoids `continue` on the outer do-while, which
+ // would re-evaluate `while(cursor && ...)` and exit prematurely when
+ // cursor is undefined (e.g. 429 on the very first page).
+ let response: IDataObject;
+ for (;;) {
+ try {
+ response = await makeApiRequest.call(this, method, endpoint, undefined, pageQs, itemIndex);
+ break;
+ } catch (error: unknown) {
+ const err = error instanceof NodeApiError ? error : null;
+ const code = err?.httpCode;
+ if ((code === '429' || code === '502' || code === '503') && totalRetries < MAX_RETRIES) {
+ totalRetries++;
+ const retryAfter = (err as NodeApiError & { retryAfter?: number })?.retryAfter;
+ const waitSec = retryAfter ?? (code === '429' ? 2 : 1);
+ // eslint-disable-next-line @n8n/community-nodes/no-restricted-globals
+ await new Promise(r => setTimeout(r, waitSec * 1000));
+ continue;
+ }
+ throw error;
+ }
+ }
+
+ const items = (response.data as IDataObject[]) ?? [];
+ results.push(...items);
+ const meta = response.meta as IDataObject | undefined;
+ const paginate = meta?.paginate as IDataObject | undefined;
+ const nextCursor = (paginate?.next_page as string) ?? undefined;
+
+ // Guard against infinite loops: server returned the same cursor we just sent
+ if (nextCursor && nextCursor === cursor) break;
+ cursor = nextCursor;
+ pageCount++;
+ } while (cursor && (returnAll || results.length < limit) && pageCount < MAX_PAGES);
+
+ const finalResults = returnAll ? results : results.slice(0, limit);
+
+ // Apply simplify if enabled (v2 only)
+ if (nodeVersion >= 2) {
+ let simplify = false;
+ try { simplify = this.getNodeParameter('simplify', itemIndex, false) as boolean; } catch { /* */ }
+ if (simplify) {
+ const keyFields = SIMPLIFY_FIELDS[resource];
+ if (keyFields) {
+ return finalResults.map(item => ({
+ json: Object.fromEntries(Object.entries(item).filter(([k]) => keyFields.includes(k))),
+ }));
+ }
+ }
+ }
+
+ return finalResults.map(item => ({
+ json: typeof item === 'object' && item !== null ? item : { value: item } as unknown as IDataObject,
+ }));
+}
+
+/** Simplify a single item by keeping only key fields for the resource */
+export function simplifyItem(item: IDataObject, resource: string): IDataObject {
+ const keyFields = SIMPLIFY_FIELDS[resource];
+ if (!keyFields) return item;
+ const simplified = Object.fromEntries(Object.entries(item).filter(([k]) => keyFields.includes(k)));
+ // If no fields matched (e.g. status response with user resource), return original
+ return Object.keys(simplified).length > 0 ? simplified : item;
+}
+
+/**
+ * Extract value from resourceLocator parameter (v2) or plain number/string (v1).
+ * ResourceLocator returns { mode, value, __rl: true }.
+ */
+export function resolveResourceLocator(
+ ctx: IExecuteFunctions,
+ paramName: string,
+ itemIndex: number,
+ fallbackParam?: string,
+): number | string {
+ let value: unknown;
+ try {
+ value = ctx.getNodeParameter(paramName, itemIndex);
+ } catch {
+ if (fallbackParam) {
+ value = ctx.getNodeParameter(fallbackParam, itemIndex);
+ } else {
+ throw new NodeOperationError(ctx.getNode(), `Missing required parameter: ${paramName}`, { itemIndex });
+ }
+ }
+ if (typeof value === 'object' && value !== null && (value as IDataObject).__rl) {
+ const rlValue = (value as IDataObject).value;
+ if (rlValue === null || rlValue === undefined || rlValue === '') {
+ throw new NodeOperationError(ctx.getNode(), `Parameter "${paramName}" is empty`, { itemIndex });
+ }
+ return rlValue as number | string;
+ }
+ if (value === null || value === undefined || value === '') {
+ throw new NodeOperationError(ctx.getNode(), `Parameter "${paramName}" is empty`, { itemIndex });
+ }
+ return value as number | string;
+}
+
+/**
+ * Build button rows from visual builder or raw JSON parameters.
+ * Returns Button[][] ready for API body.
+ */
+export function buildButtonRows(
+ ctx: IExecuteFunctions,
+ itemIndex: number,
+): IDataObject[][] {
+ let buttonLayout: string;
+ try { buttonLayout = ctx.getNodeParameter('buttonLayout', itemIndex, 'none') as string; } catch { return []; }
+ if (buttonLayout === 'none') return [];
+
+ if (buttonLayout === 'raw_json') {
+ let rawJson: string;
+ try { rawJson = ctx.getNodeParameter('rawJsonButtons', itemIndex, '') as string; } catch { return []; }
+ if (!rawJson || rawJson.trim() === '' || rawJson.trim() === '[]') return [];
+
+ let parsed: unknown;
+ try { parsed = JSON.parse(rawJson); } catch {
+ throw new NodeOperationError(ctx.getNode(),
+ 'The buttons JSON is not valid. Expected format: [{"text": "Click me"}] or [[{"text": "Row 1"}, {"text": "Row 2"}]]',
+ { itemIndex },
+ );
+ }
+ if (!Array.isArray(parsed)) {
+ throw new NodeOperationError(ctx.getNode(), 'Buttons JSON must be an array', { itemIndex });
+ }
+ if (parsed.length > 0 && parsed.every((item: unknown) => Array.isArray(item))) {
+ return parsed as IDataObject[][];
+ }
+ return [parsed as IDataObject[]];
+ }
+
+ // Visual mode (single_row / multiple_rows)
+ let buttonsParam: { button?: IDataObject[]; buttonRow?: IDataObject[] } | undefined;
+ try { buttonsParam = ctx.getNodeParameter('buttons', itemIndex, {}) as typeof buttonsParam; } catch { return []; }
+ const items = buttonsParam?.button ?? buttonsParam?.buttonRow ?? [];
+ if (items.length === 0) return [];
+
+ if (buttonLayout === 'single_row') {
+ return [items.map(btn => buildButton(btn))];
+ }
+ // multiple_rows
+ return items.map(btn => [buildButton(btn)]);
+}
+
+function buildButton(btn: IDataObject): IDataObject {
+ const result: IDataObject = { text: btn.text as string };
+ if (btn.type === 'url' && btn.url) {
+ result.url = btn.url as string;
+ } else if (btn.data) {
+ result.data = btn.data as string;
+ }
+ return result;
+}
+
+/**
+ * Clean up file attachments from fixedCollection format.
+ * Removes empty/zero fields and maps camelCase to snake_case.
+ */
+export function cleanFileAttachments(
+ ctx: IExecuteFunctions,
+ itemIndex: number,
+): IDataObject[] {
+ let filesRaw: unknown;
+ try {
+ const additional = ctx.getNodeParameter('additionalFields', itemIndex, {}) as IDataObject;
+ filesRaw = additional.files;
+ } catch { return []; }
+ if (!filesRaw) return [];
+
+ let files: IDataObject[];
+ if (Array.isArray(filesRaw)) {
+ files = filesRaw as IDataObject[];
+ } else if (typeof filesRaw === 'object' && (filesRaw as IDataObject).file) {
+ files = (filesRaw as IDataObject).file as IDataObject[];
+ } else {
+ return [];
+ }
+
+ const keyMap: Record = { fileType: 'file_type' };
+ return files.map(f => {
+ const clean: IDataObject = {};
+ for (const [k, v] of Object.entries(f)) {
+ if ((k === 'height' || k === 'width') && (v === 0 || v === '')) continue;
+ if (v === '' || v === undefined || v === null) continue;
+ clean[keyMap[k] || k] = v;
+ }
+ return clean;
+ });
+}
+
+/**
+ * Resolve form blocks from builder mode or JSON input.
+ * Works with IExecuteFunctions context (execute() mode).
+ */
+export function resolveFormBlocksFromParams(
+ ctx: IExecuteFunctions,
+ itemIndex: number,
+): IDataObject[] {
+ let builderMode: string;
+ try { builderMode = ctx.getNodeParameter('formBuilderMode', itemIndex, 'json') as string; } catch { builderMode = 'json'; }
+
+ if (builderMode === 'builder') {
+ let rawBlocks: IDataObject | undefined;
+ try { rawBlocks = ctx.getNodeParameter('formBlocks', itemIndex, {}) as IDataObject; } catch { return []; }
+ const blockEntries = (rawBlocks?.block ?? []) as IDataObject[];
+ if (blockEntries.length === 0) return [];
+
+ const blocks: IDataObject[] = [];
+ for (const entry of blockEntries) {
+ const block: IDataObject = { type: entry.type };
+ const blockType = entry.type as string;
+
+ if (['header', 'plain_text', 'markdown'].includes(blockType)) {
+ if (entry.text) block.text = entry.text;
+ }
+ if (blockType === 'divider') { blocks.push(block); continue; }
+
+ if (['input', 'select', 'radio', 'checkbox', 'date', 'time', 'file_input'].includes(blockType)) {
+ if (entry.name) block.name = entry.name;
+ if (entry.label) block.label = entry.label;
+ if (entry.required === true) block.required = true;
+ if (entry.hint && (entry.hint as string).trim()) block.hint = entry.hint;
+ }
+
+ if (blockType === 'input') {
+ if (entry.placeholder && (entry.placeholder as string).trim()) block.placeholder = entry.placeholder;
+ if (entry.multiline === true) block.multiline = true;
+ if (entry.initial_value && (entry.initial_value as string).trim()) block.initial_value = entry.initial_value;
+ if (entry.min_length && (entry.min_length as number) > 0) block.min_length = entry.min_length;
+ if (entry.max_length && (entry.max_length as number) > 0) block.max_length = entry.max_length;
+ }
+
+ if (['select', 'radio', 'checkbox'].includes(blockType) && entry.options) {
+ const optionsData = entry.options as IDataObject;
+ const optionEntries = (optionsData?.option ?? []) as IDataObject[];
+ if (optionEntries.length > 0) {
+ block.options = optionEntries.map(opt => {
+ const cleanOpt: IDataObject = { text: opt.text, value: opt.value };
+ if (opt.description && (opt.description as string).trim()) cleanOpt.description = opt.description;
+ if (blockType === 'select' && opt.selected === true) cleanOpt.selected = true;
+ if (['radio', 'checkbox'].includes(blockType) && opt.checked === true) cleanOpt.checked = true;
+ return cleanOpt;
+ });
+ }
+ }
+
+ if (blockType === 'date' && entry.initial_date && (entry.initial_date as string).trim()) {
+ block.initial_date = entry.initial_date;
+ }
+ if (blockType === 'time' && entry.initial_time && (entry.initial_time as string).trim()) {
+ block.initial_time = entry.initial_time;
+ }
+
+ if (blockType === 'file_input') {
+ if (entry.filetypes && (entry.filetypes as string).trim()) {
+ block.filetypes = (entry.filetypes as string).split(',').map(s => s.trim()).filter(Boolean);
+ }
+ if (entry.max_files && (entry.max_files as number) > 0) block.max_files = entry.max_files;
+ }
+
+ blocks.push(block);
+ }
+ return blocks;
+ }
+
+ // JSON mode
+ let rawBlocks: string;
+ try { rawBlocks = ctx.getNodeParameter('formBlocks', itemIndex, '') as string; } catch { rawBlocks = ''; }
+ // v1 compat: old parameter name was customFormJson
+ if (!rawBlocks || !rawBlocks.trim()) {
+ try { rawBlocks = ctx.getNodeParameter('customFormJson', itemIndex, '') as string; } catch { rawBlocks = ''; }
+ }
+ if (!rawBlocks || !rawBlocks.trim()) return [];
+
+ let parsed: unknown;
+ try { parsed = JSON.parse(rawBlocks); } catch {
+ throw new NodeOperationError(ctx.getNode(),
+ 'The JSON is not valid. Paste an array of blocks or the full form JSON from the playground at https://dev.pachca.com/guides/forms/overview',
+ { itemIndex },
+ );
+ }
+ if (Array.isArray(parsed)) return parsed as IDataObject[];
+ if (parsed && typeof parsed === 'object' && Array.isArray((parsed as IDataObject).blocks)) {
+ return (parsed as IDataObject).blocks as IDataObject[];
+ }
+ throw new NodeOperationError(ctx.getNode(),
+ 'Expected a JSON array of blocks or a form object with a "blocks" array',
+ { itemIndex },
+ );
+}
+
+/**
+ * Upload file to S3 using presigned params from POST /uploads.
+ * Supports URL and binary data sources. Retries on S3 failure.
+ */
+export async function uploadFileToS3(
+ ctx: IExecuteFunctions,
+ itemIndex: number,
+): Promise {
+ // Step 1: Get presigned S3 params
+ const uploadParams = await makeApiRequest.call(ctx, 'POST', '/uploads', undefined, undefined, itemIndex);
+ const presigned = uploadParams as IDataObject;
+
+ const fileSource = ctx.getNodeParameter('fileSource', itemIndex, 'binary') as string;
+ // V2 stores in additionalFields collection; V1 stores as top-level params
+ const additional = ctx.getNodeParameter('additionalFields', itemIndex, {}) as IDataObject;
+ const userFileName = (additional.fileName ?? ctx.getNodeParameter('fileName', itemIndex, '')) as string;
+ const userContentType = (additional.contentType ?? ctx.getNodeParameter('contentType', itemIndex, '')) as string;
+
+ let fileBuffer: Buffer;
+ let resolvedFileName: string;
+
+ let binaryMimeType: string | undefined;
+ if (fileSource === 'url') {
+ const fileUrl = ctx.getNodeParameter('fileUrl', itemIndex, '') as string;
+ fileBuffer = (await ctx.helpers.httpRequest({
+ method: 'GET',
+ url: fileUrl,
+ encoding: 'arraybuffer',
+ })) as Buffer;
+ const rawName = fileUrl.split('/').pop()?.split('?')[0]?.split('#')[0] || 'file';
+ resolvedFileName = userFileName || decodeURIComponent(rawName);
+ } else {
+ const binaryProperty = ctx.getNodeParameter('binaryProperty', itemIndex, 'data') as string;
+ const binaryData = ctx.helpers.assertBinaryData(itemIndex, binaryProperty);
+ fileBuffer = await ctx.helpers.getBinaryDataBuffer(itemIndex, binaryProperty);
+ resolvedFileName = userFileName || binaryData.fileName || 'file';
+ binaryMimeType = binaryData.mimeType;
+ }
+
+ // Treat 'application/octet-stream' as unset (V1 default) — fall through to auto-detection
+ const effectiveUserType = (userContentType && userContentType !== 'application/octet-stream') ? userContentType : '';
+ const contentType = effectiveUserType || binaryMimeType || detectMimeType(resolvedFileName);
+
+ // Step 2: Upload to S3 with retry
+ const MAX_RETRIES = 3;
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
+ const s3Fields: Record = {
+ 'Content-Type': contentType,
+ 'Content-Disposition': String(presigned['Content-Disposition']),
+ acl: String(presigned.acl),
+ policy: String(presigned.policy),
+ 'x-amz-credential': String(presigned['x-amz-credential']),
+ 'x-amz-algorithm': String(presigned['x-amz-algorithm']),
+ 'x-amz-date': String(presigned['x-amz-date']),
+ 'x-amz-signature': String(presigned['x-amz-signature']),
+ key: String(presigned.key).replace('${filename}', () => resolvedFileName),
+ };
+
+ const multipart = buildMultipartBody(s3Fields, fileBuffer, resolvedFileName, contentType);
+
+ try {
+ await ctx.helpers.httpRequest({
+ method: 'POST',
+ url: String(presigned.direct_url),
+ body: multipart.body,
+ headers: { 'Content-Type': multipart.contentType },
+ ignoreHttpStatusErrors: false,
+ });
+ break;
+ } catch (error) {
+ if (attempt === MAX_RETRIES - 1) throw error;
+ // Re-request presigned URL on retry (it may have expired)
+ const retryParams = await makeApiRequest.call(ctx, 'POST', '/uploads', undefined, undefined, itemIndex);
+ Object.assign(presigned, retryParams);
+ }
+ }
+
+ const fileKey = String(presigned.key).replace('${filename}', () => resolvedFileName);
+ return {
+ key: fileKey,
+ file_name: resolvedFileName,
+ content_type: contentType,
+ size: fileBuffer.length,
+ };
+}
+
+/**
+ * Validate and split a comma-separated string into a typed array.
+ * Throws NodeOperationError with descriptive message for invalid values.
+ */
+export function splitAndValidateCommaList(
+ ctx: IExecuteFunctions,
+ value: string,
+ fieldName: string,
+ type: 'int' | 'string',
+ itemIndex: number,
+): (number | string)[] {
+ const arr = value.split(',').map(s => s.trim()).filter(Boolean);
+ if (type === 'int') {
+ const invalid = arr.filter(id => !Number.isInteger(Number(id)));
+ if (invalid.length) {
+ throw new NodeOperationError(ctx.getNode(),
+ `${fieldName} must be numbers. Invalid values: ${invalid.join(', ')}`,
+ { itemIndex },
+ );
+ }
+ return arr.map(Number);
+ }
+ return arr;
+}
+
+// ============================================================================
+// SHARED LIST-SEARCH & LOAD-OPTIONS METHODS (used by V2 node)
+// ============================================================================
+
+/** Format user name for display in resource locator dropdowns */
+export function formatUserName(u: { first_name: string; last_name: string; nickname: string }): string {
+ const fullName = [u.first_name, u.last_name]
+ .filter((v) => v != null && v !== '' && v !== 'null')
+ .join(' ');
+ const display = fullName || u.nickname || 'User';
+ return u.nickname ? `${display} (@${u.nickname})` : display;
+}
+
+/**
+ * Search chats with cursor-based pagination.
+ * When filtering — uses search endpoint (returns all matches).
+ * When listing — fetches multiple pages up to 200 results.
+ */
+export async function searchChats(
+ this: ILoadOptionsFunctions,
+ filter?: string,
+ paginationToken?: unknown,
+): Promise {
+ const credentials = await this.getCredentials('pachcaApi');
+
+ if (filter) {
+ const url = `${credentials.baseUrl}/search/chats?query=${encodeURIComponent(filter)}`;
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'pachcaApi', {
+ method: 'GET',
+ url,
+ });
+ const items = response.data ?? [];
+ return {
+ results: items.map((c: { id: number; name: string }) => ({
+ name: c.name,
+ value: c.id,
+ })),
+ };
+ }
+
+ // No filter — paginated listing
+ const cursor = paginationToken as string | undefined;
+ const qs = cursor ? `per=50&cursor=${cursor}` : 'per=50';
+ const url = `${credentials.baseUrl}/chats?${qs}`;
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'pachcaApi', {
+ method: 'GET',
+ url,
+ });
+ const items = response.data ?? [];
+ const nextCursor = response.meta?.paginate?.next_page as string | undefined;
+ return {
+ results: items.map((c: { id: number; name: string }) => ({
+ name: c.name,
+ value: c.id,
+ })),
+ paginationToken: nextCursor,
+ };
+}
+
+/** Search users by name/nickname */
+export async function searchUsers(
+ this: ILoadOptionsFunctions,
+ filter?: string,
+): Promise {
+ const credentials = await this.getCredentials('pachcaApi');
+ if (!filter) return { results: [] };
+ const url = `${credentials.baseUrl}/search/users?query=${encodeURIComponent(filter)}`;
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'pachcaApi', {
+ method: 'GET',
+ url,
+ });
+ const items = response.data ?? [];
+ return {
+ results: items.map((u: { id: number; first_name: string; last_name: string; nickname: string }) => ({
+ name: formatUserName(u),
+ value: u.id,
+ })),
+ };
+}
+
+/** Search entities — dispatches to chats or users based on entityType parameter */
+export async function searchEntities(
+ this: ILoadOptionsFunctions,
+ filter?: string,
+ paginationToken?: unknown,
+): Promise {
+ let entityType = 'discussion';
+ try {
+ entityType = (this.getNodeParameter('entityType') as string) || 'discussion';
+ } catch {
+ try {
+ entityType = (this.getCurrentNodeParameter('entityType') as string) || 'discussion';
+ } catch { /* parameter may not exist yet */ }
+ }
+
+ if (entityType === 'user') {
+ return searchUsers.call(this, filter);
+ }
+ if (entityType === 'thread') {
+ return { results: [] };
+ }
+ return searchChats.call(this, filter, paginationToken);
+}
+
+/** Load custom properties for the current resource (User or Task) */
+export async function getCustomProperties(
+ this: ILoadOptionsFunctions,
+): Promise {
+ const credentials = await this.getCredentials('pachcaApi');
+ const resource = this.getNodeParameter('resource') as string;
+ const entityType = resource === 'task' ? 'Task' : 'User';
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'pachcaApi', {
+ method: 'GET',
+ url: `${credentials.baseUrl}/custom_properties?entity_type=${entityType}`,
+ });
+ const items = response.data ?? [];
+ return items.map((p: { id: number; name: string }) => ({
+ name: p.name,
+ value: p.id,
+ }));
+}
diff --git a/integrations/n8n/nodes/Pachca/Pachca.node.json b/integrations/n8n/nodes/Pachca/Pachca.node.json
new file mode 100644
index 00000000..df9dc174
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/Pachca.node.json
@@ -0,0 +1,24 @@
+{
+ "categories": [
+ "Communication"
+ ],
+ "subcategories": {
+ "Communication": [
+ "Team Messaging"
+ ]
+ },
+ "resources": {
+ "primaryDocumentation": [
+ {
+ "url": "https://dev.pachca.com/guides/n8n/overview"
+ }
+ ]
+ },
+ "alias": [
+ "pachca",
+ "messenger",
+ "chat",
+ "team",
+ "corporate messenger"
+ ]
+}
diff --git a/integrations/n8n/nodes/Pachca/Pachca.node.ts b/integrations/n8n/nodes/Pachca/Pachca.node.ts
new file mode 100644
index 00000000..159ab020
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/Pachca.node.ts
@@ -0,0 +1,25 @@
+import { VersionedNodeType } from 'n8n-workflow';
+import type { INodeTypeBaseDescription } from 'n8n-workflow';
+import { PachcaV1 } from './V1/PachcaV1.node';
+import { PachcaV2 } from './V2/PachcaV2.node';
+
+export class Pachca extends VersionedNodeType {
+ constructor() {
+ const baseDescription: INodeTypeBaseDescription = {
+ displayName: 'Pachca',
+ name: 'pachca',
+ icon: { light: 'file:pachca.svg', dark: 'file:pachca.dark.svg' },
+ group: ['transform'],
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
+ description: 'Interact with Pachca API',
+ defaultVersion: 2,
+ };
+
+ const nodeVersions = {
+ 1: new PachcaV1(baseDescription),
+ 2: new PachcaV2(baseDescription),
+ };
+
+ super(nodeVersions, baseDescription);
+ }
+}
diff --git a/integrations/n8n/nodes/Pachca/PachcaTrigger.node.json b/integrations/n8n/nodes/Pachca/PachcaTrigger.node.json
new file mode 100644
index 00000000..df9dc174
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/PachcaTrigger.node.json
@@ -0,0 +1,24 @@
+{
+ "categories": [
+ "Communication"
+ ],
+ "subcategories": {
+ "Communication": [
+ "Team Messaging"
+ ]
+ },
+ "resources": {
+ "primaryDocumentation": [
+ {
+ "url": "https://dev.pachca.com/guides/n8n/overview"
+ }
+ ]
+ },
+ "alias": [
+ "pachca",
+ "messenger",
+ "chat",
+ "team",
+ "corporate messenger"
+ ]
+}
diff --git a/integrations/n8n/nodes/Pachca/PachcaTrigger.node.ts b/integrations/n8n/nodes/Pachca/PachcaTrigger.node.ts
new file mode 100644
index 00000000..b64bbfef
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/PachcaTrigger.node.ts
@@ -0,0 +1,218 @@
+import type {
+ IDataObject,
+ IHookFunctions,
+ INodeType,
+ INodeTypeDescription,
+ IWebhookFunctions,
+ IWebhookResponseData,
+} from 'n8n-workflow';
+import { NodeConnectionTypes } from 'n8n-workflow';
+import { verifyWebhookSignature, resolveBotId } from './GenericFunctions';
+
+/** Maps n8n event value to webhook payload { type, event } for filtering */
+const EVENT_FILTER: Record = {
+ 'button_pressed': { type: 'button', event: 'click' },
+ 'chat_member_added': { type: 'chat_member', event: 'add' },
+ 'chat_member_removed': { type: 'chat_member', event: 'remove' },
+ 'form_submitted': { type: 'view', event: 'submit' },
+ 'link_shared': { type: 'message', event: 'link_shared' },
+ 'message_deleted': { type: 'message', event: 'delete' },
+ 'message_updated': { type: 'message', event: 'update' },
+ 'new_message': { type: 'message', event: 'new' },
+ 'new_reaction': { type: 'reaction', event: 'new' },
+ 'reaction_deleted': { type: 'reaction', event: 'delete' },
+ 'company_member_activate': { type: 'company_member', event: 'activate' },
+ 'company_member_confirm': { type: 'company_member', event: 'confirm' },
+ 'company_member_delete': { type: 'company_member', event: 'delete' },
+ 'company_member_invite': { type: 'company_member', event: 'invite' },
+ 'company_member_suspend': { type: 'company_member', event: 'suspend' },
+ 'company_member_update': { type: 'company_member', event: 'update' },
+};
+
+export class PachcaTrigger implements INodeType {
+ description: INodeTypeDescription = {
+ displayName: 'Pachca Trigger',
+ name: 'pachcaTrigger',
+ icon: { light: 'file:pachca.svg', dark: 'file:pachca.dark.svg' },
+ group: ['trigger'],
+ version: 1,
+ subtitle: '={{$parameter["event"]}}',
+ description: 'Starts workflow when Pachca events occur',
+ defaults: { name: 'Pachca Trigger' },
+ inputs: [],
+ outputs: [NodeConnectionTypes.Main],
+ credentials: [{ name: 'pachcaApi', required: true }],
+ webhooks: [
+ {
+ name: 'default',
+ httpMethod: 'POST',
+ responseMode: 'onReceived',
+ path: 'webhook',
+ rawBody: true,
+ },
+ ],
+ properties: [
+ {
+ displayName: 'Event',
+ name: 'event',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ { name: 'All Events', value: '*' },
+ { name: 'Button Pressed', value: 'button_pressed' },
+ { name: 'Chat Member Added', value: 'chat_member_added' },
+ { name: 'Chat Member Removed', value: 'chat_member_removed' },
+ { name: 'Form Submitted', value: 'form_submitted' },
+ { name: 'Link Shared', value: 'link_shared' },
+ { name: 'Message Deleted', value: 'message_deleted' },
+ { name: 'Message Updated', value: 'message_updated' },
+ { name: 'New Message', value: 'new_message' },
+ { name: 'New Reaction', value: 'new_reaction' },
+ { name: 'Reaction Deleted', value: 'reaction_deleted' },
+ { name: 'User Activated', value: 'company_member_activate' },
+ { name: 'User Confirmed', value: 'company_member_confirm' },
+ { name: 'User Deleted', value: 'company_member_delete' },
+ { name: 'User Invited', value: 'company_member_invite' },
+ { name: 'User Suspended', value: 'company_member_suspend' },
+ { name: 'User Updated', value: 'company_member_update' },
+ ],
+ default: 'new_message',
+ description: 'The event to listen for',
+ },
+ ],
+ usableAsTool: true,
+ };
+
+ webhookMethods = {
+ default: {
+ async checkExists(this: IHookFunctions): Promise {
+ const credentials = await this.getCredentials('pachcaApi');
+ let botId: number;
+ try {
+ botId = await resolveBotId(this, credentials);
+ } catch {
+ return false; // Network error → treat as not exists, will trigger create
+ }
+ if (!botId) return false;
+ const webhookUrl = this.getNodeWebhookUrl('default');
+ try {
+ const response = (await this.helpers.httpRequestWithAuthentication.call(
+ this,
+ 'pachcaApi',
+ {
+ method: 'GET',
+ url: `${credentials.baseUrl}/bots/${botId}`,
+ },
+ )) as IDataObject;
+ const data = response.data as IDataObject | undefined;
+ const webhook = data?.webhook as IDataObject | undefined;
+ return webhook?.outgoing_url === webhookUrl;
+ } catch {
+ return false;
+ }
+ },
+
+ async create(this: IHookFunctions): Promise {
+ const credentials = await this.getCredentials('pachcaApi');
+ const botId = await resolveBotId(this, credentials);
+ if (!botId) {
+ this.logger.warn('Pachca Trigger: token is not a bot token. Webhook was NOT registered automatically. Configure webhook URL manually in Pachca bot settings.');
+ return true;
+ }
+ const webhookUrl = this.getNodeWebhookUrl('default');
+ await this.helpers.httpRequestWithAuthentication.call(this, 'pachcaApi', {
+ method: 'PUT',
+ url: `${credentials.baseUrl}/bots/${botId}`,
+ body: { bot: { webhook: { outgoing_url: webhookUrl } } },
+ });
+ return true;
+ },
+
+ async delete(this: IHookFunctions): Promise {
+ const credentials = await this.getCredentials('pachcaApi');
+ let botId: number;
+ try {
+ botId = await resolveBotId(this, credentials);
+ } catch {
+ return true; // Can't resolve bot → nothing to clean up
+ }
+ if (!botId) return true;
+ try {
+ await this.helpers.httpRequestWithAuthentication.call(this, 'pachcaApi', {
+ method: 'PUT',
+ url: `${credentials.baseUrl}/bots/${botId}`,
+ body: { bot: { webhook: { outgoing_url: '' } } },
+ });
+ } catch {
+ // Ignore errors on cleanup
+ }
+ return true;
+ },
+ },
+ };
+
+ async webhook(this: IWebhookFunctions): Promise {
+ const body = this.getBodyData() as IDataObject;
+ const headerData = this.getHeaderData() as IDataObject;
+ const credentials = await this.getCredentials('pachcaApi');
+ const event = this.getNodeParameter('event') as string;
+
+ // IP allowlist check
+ const allowedIps = ((credentials.webhookAllowedIps as string) || '').split(',').map(s => s.trim()).filter(Boolean);
+ if (allowedIps.length > 0) {
+ const request = this.getRequestObject();
+ const clientIp = (request.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() || request.socket?.remoteAddress || '';
+ const normalizedIp = clientIp.replace(/^::ffff:/, '');
+ if (!allowedIps.includes(normalizedIp)) {
+ return { webhookResponse: 'Forbidden' };
+ }
+ }
+
+ // Signing secret verification (use raw body bytes for accurate HMAC)
+ const signingSecret = ((credentials.signingSecret as string) || '').trim();
+ if (signingSecret) {
+ const signature = headerData['pachca-signature'] as string;
+ if (!signature) {
+ return { webhookResponse: 'Rejected' };
+ }
+ const request = this.getRequestObject();
+ const rawBody = request.rawBody
+ ? request.rawBody.toString()
+ : JSON.stringify(body);
+ if (
+ !verifyWebhookSignature(
+ rawBody,
+ signature,
+ signingSecret,
+ )
+ ) {
+ return { webhookResponse: 'Rejected' };
+ }
+ }
+
+ // Replay protection — reject events older than 5 minutes
+ const webhookTs = body.webhook_timestamp as number | undefined;
+ if (webhookTs) {
+ const ageMs = Date.now() - webhookTs * 1000;
+ if (ageMs < -60_000 || ageMs > 5 * 60 * 1000) {
+ return { webhookResponse: 'Rejected' };
+ }
+ }
+
+ // Event filtering using type+event from payload
+ if (event !== '*') {
+ const filter = EVENT_FILTER[event];
+ if (filter) {
+ const bodyType = body.type as string | undefined;
+ const bodyEvent = body.event as string | undefined;
+ if (bodyType !== filter.type || bodyEvent !== filter.event) {
+ return { webhookResponse: 'Event filtered' };
+ }
+ }
+ }
+
+ return {
+ workflowData: [this.helpers.returnJsonArray(body)],
+ };
+ }
+}
diff --git a/integrations/n8n/nodes/Pachca/SharedRouter.ts b/integrations/n8n/nodes/Pachca/SharedRouter.ts
new file mode 100644
index 00000000..883160ae
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/SharedRouter.ts
@@ -0,0 +1,1034 @@
+// ============================================================================
+// SharedRouter.ts — Generated execute() dispatcher for Pachca node (shared by V1 and V2)
+// DO NOT EDIT — this file is auto-generated by generate-n8n.ts
+// ============================================================================
+
+import type { IExecuteFunctions, INodeExecutionData, IDataObject, IHttpRequestMethods } from 'n8n-workflow';
+import {
+ makeApiRequest,
+ makeApiRequestAllPages,
+ resolveResourceLocator,
+ buildButtonRows,
+ cleanFileAttachments,
+ resolveFormBlocksFromParams,
+ uploadFileToS3,
+ uploadAvatar,
+ splitAndValidateCommaList,
+ simplifyItem,
+ sanitizeBaseUrl,
+} from './GenericFunctions';
+
+// ============================================================================
+// Types
+// ============================================================================
+
+interface PathParam {
+ api: string;
+ n8n: string;
+ locator?: boolean;
+ v1Fallback?: string;
+}
+
+interface FieldMap {
+ api: string;
+ n8n: string;
+ isArray?: boolean;
+ arrayType?: 'int' | 'string';
+ locator?: boolean;
+ subKey?: string;
+}
+
+interface QueryMap {
+ api: string;
+ n8n: string;
+ locator?: boolean;
+ required?: boolean;
+ isArray?: boolean;
+ arrayType?: 'int' | 'string';
+}
+
+interface RouteConfig {
+ method: IHttpRequestMethods;
+ path: string;
+ pathParams?: PathParam[];
+ wrapperKey?: string;
+ siblingFields?: string[];
+ paginated?: boolean;
+ noDataWrapper?: boolean;
+ bodyMap?: FieldMap[];
+ optionalBodyMap?: FieldMap[];
+ queryMap?: QueryMap[];
+ optionalQueryMap?: QueryMap[];
+ v1Collection?: string;
+ special?: string;
+}
+
+// ============================================================================
+// V1 Compatibility Maps
+// ============================================================================
+
+/** v1 resource name → v2 resource name */
+const V1_RESOURCE_MAP: Record = {
+ customFields: 'customProperty',
+ status: 'profile',
+ reactions: 'reaction',
+};
+
+/** v1 operation name → v2 operation name (per v2 resource) */
+const V1_OP_MAP: Record> = {
+ message: { send: 'create', getById: 'get' },
+ user: { getById: 'get' },
+ chat: { getById: 'get' },
+ groupTag: { getById: 'get', getUsers: 'getAllUsers' },
+ profile: { getProfile: 'get' },
+ customProperty: { getCustomProperties: 'get' },
+ reaction: { addReaction: 'create', deleteReaction: 'delete', getReactions: 'getAll' },
+ thread: { createThread: 'create', getThread: 'get' },
+ form: { createView: 'create' },
+ file: { upload: 'create' },
+};
+
+// ============================================================================
+// Routes Table
+// ============================================================================
+
+const ROUTES: Record> = {
+ security: {
+ getAll: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/audit_events',
+ paginated: true,
+ optionalQueryMap: [{ api: 'start_time', n8n: 'startTime' }, { api: 'end_time', n8n: 'endTime' }, { api: 'event_key', n8n: 'eventKey' }, { api: 'actor_id', n8n: 'actorId' }, { api: 'actor_type', n8n: 'actorType' }, { api: 'entity_id', n8n: 'entityId' }, { api: 'entity_type', n8n: 'entityType' }],
+ },
+ },
+ bot: {
+ update: {
+ method: 'PUT' as IHttpRequestMethods,
+ path: '/bots/{id}',
+ pathParams: [{ api: 'id', n8n: 'botId' }],
+ wrapperKey: 'bot',
+ special: 'botWebhook',
+ },
+ getAllEvents: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/webhooks/events',
+ paginated: true,
+ },
+ removeEvents: {
+ method: 'DELETE' as IHttpRequestMethods,
+ path: '/webhooks/events/{id}',
+ pathParams: [{ api: 'id', n8n: 'id' }],
+ },
+ },
+ chat: {
+ create: {
+ method: 'POST' as IHttpRequestMethods,
+ path: '/chats',
+ wrapperKey: 'chat',
+ bodyMap: [
+ { api: 'name', n8n: 'chatName' },
+ ],
+ optionalBodyMap: [
+ { api: 'member_ids', n8n: 'memberIds', isArray: true, arrayType: 'int' },
+ { api: 'group_tag_ids', n8n: 'groupTagIds', isArray: true, arrayType: 'int' },
+ { api: 'channel', n8n: 'channel' },
+ { api: 'public', n8n: 'public' },
+ ],
+ },
+ getAll: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/chats',
+ paginated: true,
+ queryMap: [{ api: 'sort', n8n: 'sort' }, { api: 'order', n8n: 'order' }, { api: 'availability', n8n: 'availability' }, { api: 'last_message_at_after', n8n: 'lastMessageAtAfter' }, { api: 'last_message_at_before', n8n: 'lastMessageAtBefore' }],
+ optionalQueryMap: [{ api: 'personal', n8n: 'personal' }],
+ },
+ get: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/chats/{id}',
+ pathParams: [{ api: 'id', n8n: 'id', locator: true, v1Fallback: 'chatId' }],
+ },
+ update: {
+ method: 'PUT' as IHttpRequestMethods,
+ path: '/chats/{id}',
+ pathParams: [{ api: 'id', n8n: 'id', locator: true, v1Fallback: 'chatId' }],
+ wrapperKey: 'chat',
+ optionalBodyMap: [
+ { api: 'name', n8n: 'chatName' },
+ { api: 'public', n8n: 'public' },
+ ],
+ },
+ archive: {
+ method: 'PUT' as IHttpRequestMethods,
+ path: '/chats/{id}/archive',
+ pathParams: [{ api: 'id', n8n: 'id', locator: true, v1Fallback: 'chatId' }],
+ },
+ unarchive: {
+ method: 'PUT' as IHttpRequestMethods,
+ path: '/chats/{id}/unarchive',
+ pathParams: [{ api: 'id', n8n: 'id', locator: true, v1Fallback: 'chatId' }],
+ },
+ getMembers: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/chats/{chatId}/members',
+ pathParams: [{ api: 'chatId', n8n: 'chatId' }],
+ paginated: true,
+ v1Collection: 'chatMembersOptions',
+ optionalQueryMap: [{ api: 'role', n8n: 'role' }, { api: 'limit', n8n: 'limit' }, { api: 'cursor', n8n: 'cursor' }],
+ },
+ addUsers: {
+ method: 'POST' as IHttpRequestMethods,
+ path: '/chats/{chatId}/members',
+ pathParams: [{ api: 'chatId', n8n: 'chatId' }],
+ bodyMap: [{ api: 'member_ids', n8n: 'memberIds', isArray: true, arrayType: 'int' }],
+ optionalBodyMap: [{ api: 'silent', n8n: 'silent' }],
+ },
+ removeUser: {
+ method: 'DELETE' as IHttpRequestMethods,
+ path: '/chats/{chatId}/members/{userId}',
+ pathParams: [{ api: 'chatId', n8n: 'chatId' }, { api: 'userId', n8n: 'userId' }],
+ },
+ updateRole: {
+ method: 'PUT' as IHttpRequestMethods,
+ path: '/chats/{chatId}/members/{userId}',
+ pathParams: [{ api: 'chatId', n8n: 'chatId' }, { api: 'userId', n8n: 'userId' }],
+ bodyMap: [{ api: 'role', n8n: 'newRole' }],
+ },
+ leaveChat: {
+ method: 'DELETE' as IHttpRequestMethods,
+ path: '/chats/{chatId}/leave',
+ pathParams: [{ api: 'chatId', n8n: 'chatId' }],
+ },
+ },
+ member: {
+ addGroupTags: {
+ method: 'POST' as IHttpRequestMethods,
+ path: '/chats/{id}/group_tags',
+ pathParams: [{ api: 'id', n8n: 'id', locator: true }],
+ bodyMap: [
+ { api: 'group_tag_ids', n8n: 'groupTagIds', isArray: true, arrayType: 'int' },
+ ],
+ },
+ removeGroupTags: {
+ method: 'DELETE' as IHttpRequestMethods,
+ path: '/chats/{id}/group_tags/{tag_id}',
+ pathParams: [{ api: 'id', n8n: 'id', locator: true }, { api: 'tag_id', n8n: 'tagId' }],
+ },
+ leave: {
+ method: 'DELETE' as IHttpRequestMethods,
+ path: '/chats/{id}/leave',
+ pathParams: [{ api: 'id', n8n: 'id', locator: true }],
+ },
+ getAll: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/chats/{id}/members',
+ pathParams: [{ api: 'id', n8n: 'id', locator: true }],
+ paginated: true,
+ queryMap: [{ api: 'role', n8n: 'role' }],
+ },
+ create: {
+ method: 'POST' as IHttpRequestMethods,
+ path: '/chats/{id}/members',
+ pathParams: [{ api: 'id', n8n: 'id', locator: true }],
+ bodyMap: [
+ { api: 'member_ids', n8n: 'memberIds', isArray: true, arrayType: 'int' },
+ ],
+ optionalBodyMap: [
+ { api: 'silent', n8n: 'silent' },
+ ],
+ },
+ delete: {
+ method: 'DELETE' as IHttpRequestMethods,
+ path: '/chats/{id}/members/{user_id}',
+ pathParams: [{ api: 'id', n8n: 'id', locator: true }, { api: 'user_id', n8n: 'userId', locator: true }],
+ },
+ update: {
+ method: 'PUT' as IHttpRequestMethods,
+ path: '/chats/{id}/members/{user_id}',
+ pathParams: [{ api: 'id', n8n: 'id', locator: true }, { api: 'user_id', n8n: 'userId', locator: true }],
+ bodyMap: [
+ { api: 'role', n8n: 'role' },
+ ],
+ },
+ },
+ groupTag: {
+ create: {
+ method: 'POST' as IHttpRequestMethods,
+ path: '/group_tags',
+ wrapperKey: 'group_tag',
+ bodyMap: [
+ { api: 'name', n8n: 'groupTagName' },
+ ],
+ },
+ getAll: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/group_tags',
+ paginated: true,
+ queryMap: [{ api: 'names', n8n: 'names', isArray: true, arrayType: 'string' }],
+ },
+ get: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/group_tags/{id}',
+ pathParams: [{ api: 'id', n8n: 'groupTagId' }],
+ },
+ update: {
+ method: 'PUT' as IHttpRequestMethods,
+ path: '/group_tags/{id}',
+ pathParams: [{ api: 'id', n8n: 'groupTagId' }],
+ wrapperKey: 'group_tag',
+ bodyMap: [
+ { api: 'name', n8n: 'groupTagName' },
+ ],
+ },
+ delete: {
+ method: 'DELETE' as IHttpRequestMethods,
+ path: '/group_tags/{id}',
+ pathParams: [{ api: 'id', n8n: 'groupTagId' }],
+ },
+ getAllUsers: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/group_tags/{id}/users',
+ pathParams: [{ api: 'id', n8n: 'groupTagId' }],
+ paginated: true,
+ },
+ addTags: {
+ method: 'POST' as IHttpRequestMethods,
+ path: '/chats/{groupTagChatId}/group_tags',
+ pathParams: [{ api: 'groupTagChatId', n8n: 'groupTagChatId' }],
+ bodyMap: [{ api: 'group_tag_ids', n8n: 'groupTagIds', isArray: true, arrayType: 'int' }],
+ },
+ removeTag: {
+ method: 'DELETE' as IHttpRequestMethods,
+ path: '/chats/{groupTagChatId}/group_tags/{tagId}',
+ pathParams: [{ api: 'groupTagChatId', n8n: 'groupTagChatId' }, { api: 'tagId', n8n: 'tagId' }],
+ },
+ },
+ message: {
+ create: {
+ method: 'POST' as IHttpRequestMethods,
+ path: '/messages',
+ wrapperKey: 'message',
+ siblingFields: ['link_preview'],
+ special: 'messageButtons',
+ bodyMap: [
+ { api: 'entity_type', n8n: 'entityType' },
+ { api: 'entity_id', n8n: 'entityId', locator: true },
+ { api: 'content', n8n: 'content' },
+ ],
+ optionalBodyMap: [
+ { api: 'files', n8n: 'files', subKey: 'file' },
+ { api: 'parent_message_id', n8n: 'parentMessageId' },
+ { api: 'display_avatar_url', n8n: 'displayAvatarUrl' },
+ { api: 'display_name', n8n: 'displayName' },
+ { api: 'skip_invite_mentions', n8n: 'skipInviteMentions' },
+ { api: 'link_preview', n8n: 'linkPreview' },
+ ],
+ },
+ getAll: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/messages',
+ paginated: true,
+ queryMap: [{ api: 'chat_id', n8n: 'chatId', locator: true, required: true }, { api: 'sort', n8n: 'sort' }, { api: 'order', n8n: 'order' }],
+ },
+ get: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/messages/{id}',
+ pathParams: [{ api: 'id', n8n: 'id', v1Fallback: 'messageId' }],
+ },
+ update: {
+ method: 'PUT' as IHttpRequestMethods,
+ path: '/messages/{id}',
+ pathParams: [{ api: 'id', n8n: 'id', v1Fallback: 'messageId' }],
+ wrapperKey: 'message',
+ special: 'messageButtons',
+ optionalBodyMap: [
+ { api: 'content', n8n: 'content' },
+ { api: 'files', n8n: 'files', subKey: 'file' },
+ { api: 'display_avatar_url', n8n: 'displayAvatarUrl' },
+ { api: 'display_name', n8n: 'displayName' },
+ ],
+ },
+ delete: {
+ method: 'DELETE' as IHttpRequestMethods,
+ path: '/messages/{id}',
+ pathParams: [{ api: 'id', n8n: 'id', v1Fallback: 'messageId' }],
+ },
+ pin: {
+ method: 'POST' as IHttpRequestMethods,
+ path: '/messages/{id}/pin',
+ pathParams: [{ api: 'id', n8n: 'id', v1Fallback: 'messageId' }],
+ },
+ unpin: {
+ method: 'DELETE' as IHttpRequestMethods,
+ path: '/messages/{id}/pin',
+ pathParams: [{ api: 'id', n8n: 'id', v1Fallback: 'messageId' }],
+ },
+ getReadMembers: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/messages/{messageId}/read_member_ids',
+ pathParams: [{ api: 'messageId', n8n: 'messageId' }],
+ paginated: true,
+ v1Collection: 'readMembersOptions',
+ optionalQueryMap: [{ api: 'per', n8n: 'readMembersPer' }, { api: 'page', n8n: 'readMembersPage' }],
+ },
+ unfurl: {
+ method: 'POST' as IHttpRequestMethods,
+ path: '/messages/{messageId}/link_previews',
+ pathParams: [{ api: 'messageId', n8n: 'messageId' }],
+ special: 'unfurlLinkPreviews',
+ bodyMap: [{ api: 'link_previews', n8n: 'linkPreviews' }],
+ },
+ },
+ linkPreview: {
+ create: {
+ method: 'POST' as IHttpRequestMethods,
+ path: '/messages/{id}/link_previews',
+ pathParams: [{ api: 'id', n8n: 'id' }],
+ bodyMap: [
+ { api: 'link_previews', n8n: 'linkPreviews' },
+ ],
+ },
+ },
+ reaction: {
+ create: {
+ method: 'POST' as IHttpRequestMethods,
+ path: '/messages/{id}/reactions',
+ pathParams: [{ api: 'id', n8n: 'reactionsMessageId' }],
+ bodyMap: [
+ { api: 'code', n8n: 'reactionsReactionCode' },
+ ],
+ optionalBodyMap: [
+ { api: 'name', n8n: 'name' },
+ ],
+ },
+ delete: {
+ method: 'DELETE' as IHttpRequestMethods,
+ path: '/messages/{id}/reactions',
+ pathParams: [{ api: 'id', n8n: 'reactionsMessageId' }],
+ queryMap: [{ api: 'code', n8n: 'reactionsReactionCode', required: true }, { api: 'name', n8n: 'name' }],
+ },
+ getAll: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/messages/{id}/reactions',
+ pathParams: [{ api: 'id', n8n: 'reactionsMessageId' }],
+ paginated: true,
+ v1Collection: 'reactionsOptions',
+ optionalQueryMap: [{ api: 'limit', n8n: 'reactionsPer' }, { api: 'cursor', n8n: 'reactionsCursor' }],
+ },
+ },
+ readMember: {
+ getAllReadMemberIds: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/messages/{id}/read_member_ids',
+ pathParams: [{ api: 'id', n8n: 'id' }],
+ paginated: true,
+ },
+ },
+ thread: {
+ create: {
+ method: 'POST' as IHttpRequestMethods,
+ path: '/messages/{id}/thread',
+ pathParams: [{ api: 'id', n8n: 'threadMessageId' }],
+ },
+ get: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/threads/{id}',
+ pathParams: [{ api: 'id', n8n: 'threadThreadId' }],
+ },
+ },
+ profile: {
+ getInfo: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/oauth/token/info',
+ },
+ get: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/profile',
+ },
+ updateAvatar: {
+ method: 'PUT' as IHttpRequestMethods,
+ path: '/profile/avatar',
+ special: 'avatarUpload',
+ },
+ deleteAvatar: {
+ method: 'DELETE' as IHttpRequestMethods,
+ path: '/profile/avatar',
+ },
+ getStatus: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/profile/status',
+ },
+ updateStatus: {
+ method: 'PUT' as IHttpRequestMethods,
+ path: '/profile/status',
+ wrapperKey: 'status',
+ bodyMap: [
+ { api: 'emoji', n8n: 'statusEmoji' },
+ { api: 'title', n8n: 'statusTitle' },
+ ],
+ optionalBodyMap: [
+ { api: 'expires_at', n8n: 'statusExpiresAt' },
+ { api: 'is_away', n8n: 'isAway' },
+ { api: 'away_message', n8n: 'awayMessage' },
+ ],
+ },
+ deleteStatus: {
+ method: 'DELETE' as IHttpRequestMethods,
+ path: '/profile/status',
+ },
+ },
+ search: {
+ getAllChats: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/search/chats',
+ paginated: true,
+ queryMap: [{ api: 'query', n8n: 'query' }],
+ optionalQueryMap: [{ api: 'order', n8n: 'order' }, { api: 'created_from', n8n: 'createdFrom' }, { api: 'created_to', n8n: 'createdTo' }, { api: 'active', n8n: 'active' }, { api: 'chat_subtype', n8n: 'chatSubtype' }, { api: 'personal', n8n: 'personal' }],
+ },
+ getAllMessages: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/search/messages',
+ paginated: true,
+ queryMap: [{ api: 'query', n8n: 'query' }],
+ optionalQueryMap: [{ api: 'order', n8n: 'order' }, { api: 'created_from', n8n: 'createdFrom' }, { api: 'created_to', n8n: 'createdTo' }, { api: 'chat_ids', n8n: 'chatIds', isArray: true, arrayType: 'int' }, { api: 'user_ids', n8n: 'userIds', isArray: true, arrayType: 'int' }, { api: 'active', n8n: 'active' }],
+ },
+ getAllUsers: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/search/users',
+ paginated: true,
+ queryMap: [{ api: 'query', n8n: 'query' }],
+ optionalQueryMap: [{ api: 'sort', n8n: 'sort' }, { api: 'order', n8n: 'order' }, { api: 'created_from', n8n: 'createdFrom' }, { api: 'created_to', n8n: 'createdTo' }, { api: 'company_roles', n8n: 'companyRoles', isArray: true, arrayType: 'string' }],
+ },
+ },
+ task: {
+ create: {
+ method: 'POST' as IHttpRequestMethods,
+ path: '/tasks',
+ wrapperKey: 'task',
+ bodyMap: [
+ { api: 'kind', n8n: 'taskKind' },
+ ],
+ optionalBodyMap: [
+ { api: 'content', n8n: 'taskContent' },
+ { api: 'due_at', n8n: 'taskDueAt' },
+ { api: 'priority', n8n: 'taskPriority' },
+ { api: 'performer_ids', n8n: 'performerIds', isArray: true, arrayType: 'int' },
+ { api: 'chat_id', n8n: 'chatId' },
+ { api: 'all_day', n8n: 'allDay' },
+ { api: 'custom_properties', n8n: 'customProperties', subKey: 'property' },
+ ],
+ },
+ getAll: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/tasks',
+ paginated: true,
+ },
+ get: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/tasks/{id}',
+ pathParams: [{ api: 'id', n8n: 'id' }],
+ },
+ update: {
+ method: 'PUT' as IHttpRequestMethods,
+ path: '/tasks/{id}',
+ pathParams: [{ api: 'id', n8n: 'id' }],
+ wrapperKey: 'task',
+ optionalBodyMap: [
+ { api: 'kind', n8n: 'kind' },
+ { api: 'content', n8n: 'content' },
+ { api: 'due_at', n8n: 'dueAt' },
+ { api: 'priority', n8n: 'priority' },
+ { api: 'performer_ids', n8n: 'performerIds', isArray: true, arrayType: 'int' },
+ { api: 'status', n8n: 'status' },
+ { api: 'all_day', n8n: 'allDay' },
+ { api: 'done_at', n8n: 'doneAt' },
+ { api: 'custom_properties', n8n: 'customProperties', subKey: 'property' },
+ ],
+ },
+ delete: {
+ method: 'DELETE' as IHttpRequestMethods,
+ path: '/tasks/{id}',
+ pathParams: [{ api: 'id', n8n: 'id' }],
+ },
+ },
+ user: {
+ create: {
+ method: 'POST' as IHttpRequestMethods,
+ path: '/users',
+ wrapperKey: 'user',
+ siblingFields: ['skip_email_notify'],
+ bodyMap: [
+ { api: 'email', n8n: 'email' },
+ ],
+ optionalBodyMap: [
+ { api: 'first_name', n8n: 'firstName' },
+ { api: 'last_name', n8n: 'lastName' },
+ { api: 'phone_number', n8n: 'phoneNumber' },
+ { api: 'nickname', n8n: 'nickname' },
+ { api: 'department', n8n: 'department' },
+ { api: 'title', n8n: 'title' },
+ { api: 'role', n8n: 'role' },
+ { api: 'suspended', n8n: 'suspended' },
+ { api: 'list_tags', n8n: 'listTags', isArray: true, arrayType: 'string' },
+ { api: 'custom_properties', n8n: 'customProperties', subKey: 'property' },
+ { api: 'skip_email_notify', n8n: 'skipEmailNotify' },
+ ],
+ },
+ getAll: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/users',
+ paginated: true,
+ special: 'userGetAllFilters',
+ v1Collection: 'additionalOptions',
+ optionalQueryMap: [{ api: 'query', n8n: 'query' }],
+ },
+ get: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/users/{id}',
+ pathParams: [{ api: 'id', n8n: 'id', locator: true, v1Fallback: 'userId' }],
+ },
+ update: {
+ method: 'PUT' as IHttpRequestMethods,
+ path: '/users/{id}',
+ pathParams: [{ api: 'id', n8n: 'id', locator: true, v1Fallback: 'userId' }],
+ wrapperKey: 'user',
+ optionalBodyMap: [
+ { api: 'first_name', n8n: 'firstName' },
+ { api: 'last_name', n8n: 'lastName' },
+ { api: 'email', n8n: 'email' },
+ { api: 'phone_number', n8n: 'phoneNumber' },
+ { api: 'nickname', n8n: 'nickname' },
+ { api: 'department', n8n: 'department' },
+ { api: 'title', n8n: 'title' },
+ { api: 'role', n8n: 'role' },
+ { api: 'suspended', n8n: 'suspended' },
+ { api: 'list_tags', n8n: 'listTags', isArray: true, arrayType: 'string' },
+ { api: 'custom_properties', n8n: 'customProperties', subKey: 'property' },
+ ],
+ },
+ delete: {
+ method: 'DELETE' as IHttpRequestMethods,
+ path: '/users/{id}',
+ pathParams: [{ api: 'id', n8n: 'id', locator: true, v1Fallback: 'userId' }],
+ },
+ updateAvatar: {
+ method: 'PUT' as IHttpRequestMethods,
+ path: '/users/{user_id}/avatar',
+ pathParams: [{ api: 'user_id', n8n: 'userId' }],
+ special: 'avatarUpload',
+ },
+ deleteAvatar: {
+ method: 'DELETE' as IHttpRequestMethods,
+ path: '/users/{user_id}/avatar',
+ pathParams: [{ api: 'user_id', n8n: 'userId' }],
+ },
+ getStatus: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/users/{user_id}/status',
+ pathParams: [{ api: 'user_id', n8n: 'userId' }],
+ },
+ updateStatus: {
+ method: 'PUT' as IHttpRequestMethods,
+ path: '/users/{user_id}/status',
+ pathParams: [{ api: 'user_id', n8n: 'userId' }],
+ wrapperKey: 'status',
+ bodyMap: [
+ { api: 'emoji', n8n: 'emoji' },
+ { api: 'title', n8n: 'title' },
+ ],
+ optionalBodyMap: [
+ { api: 'expires_at', n8n: 'expiresAt' },
+ { api: 'is_away', n8n: 'isAway' },
+ { api: 'away_message', n8n: 'awayMessage' },
+ ],
+ },
+ deleteStatus: {
+ method: 'DELETE' as IHttpRequestMethods,
+ path: '/users/{user_id}/status',
+ pathParams: [{ api: 'user_id', n8n: 'userId' }],
+ },
+ },
+ form: {
+ create: {
+ method: 'POST' as IHttpRequestMethods,
+ path: '/views/open',
+ wrapperKey: 'view',
+ siblingFields: ['type', 'trigger_id', 'private_metadata', 'callback_id'],
+ special: 'formBlocks',
+ bodyMap: [
+ { api: 'title', n8n: 'formTitle' },
+ { api: 'type', n8n: 'type' },
+ { api: 'trigger_id', n8n: 'triggerId' },
+ ],
+ optionalBodyMap: [
+ { api: 'close_text', n8n: 'closeText' },
+ { api: 'submit_text', n8n: 'submitText' },
+ { api: 'private_metadata', n8n: 'privateMetadata' },
+ { api: 'callback_id', n8n: 'callbackId' },
+ ],
+ },
+ processSubmission: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/profile',
+ special: 'formProcessSubmission',
+ },
+ },
+ export: {
+ create: {
+ method: 'POST' as IHttpRequestMethods,
+ path: '/chats/exports',
+ noDataWrapper: true,
+ bodyMap: [
+ { api: 'start_at', n8n: 'startAt' },
+ { api: 'end_at', n8n: 'endAt' },
+ { api: 'webhook_url', n8n: 'webhookUrl' },
+ ],
+ optionalBodyMap: [
+ { api: 'chat_ids', n8n: 'chatIds', isArray: true, arrayType: 'int' },
+ { api: 'skip_chats_file', n8n: 'skipChatsFile' },
+ ],
+ },
+ get: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/chats/exports/{id}',
+ pathParams: [{ api: 'id', n8n: 'id' }],
+ noDataWrapper: true,
+ special: 'exportDownload',
+ },
+ },
+ customProperty: {
+ get: {
+ method: 'GET' as IHttpRequestMethods,
+ path: '/custom_properties',
+ queryMap: [{ api: 'entity_type', n8n: 'entityType', required: true }],
+ },
+ },
+ file: {
+ create: {
+ method: 'POST' as IHttpRequestMethods,
+ path: '/uploads',
+ special: 'fileUpload',
+ },
+ },
+};
+
+// ============================================================================
+// Router Entry Point
+// ============================================================================
+
+export async function router(this: IExecuteFunctions): Promise {
+ const items = this.getInputData();
+ const returnData: INodeExecutionData[] = [];
+ const nodeVersion = this.getNode().typeVersion;
+
+ for (let i = 0; i < items.length; i++) {
+ try {
+ let resource = this.getNodeParameter('resource', i) as string;
+ let operation = this.getNodeParameter('operation', i) as string;
+
+ // v1 compat: map old resource/operation names to v2
+ if (nodeVersion === 1) {
+ resource = V1_RESOURCE_MAP[resource] ?? resource;
+ operation = V1_OP_MAP[resource]?.[operation] ?? operation;
+ }
+
+ const route = ROUTES[resource]?.[operation];
+ if (!route) {
+ throw new Error(`Unknown operation: ${resource}.${operation}`);
+ }
+
+ const result = await executeRoute.call(this, route, resource, i, nodeVersion);
+ returnData.push(...result.map(item => ({ ...item, pairedItem: { item: i } })));
+ } catch (error) {
+ if (this.continueOnFail()) {
+ returnData.push({ json: { error: (error as Error).message }, pairedItem: { item: i } });
+ } else {
+ throw error;
+ }
+ }
+ }
+
+ return [returnData];
+}
+
+// ============================================================================
+// Generic Route Executor
+// ============================================================================
+
+async function executeRoute(
+ this: IExecuteFunctions,
+ route: RouteConfig,
+ resource: string,
+ i: number,
+ nodeVersion: number,
+): Promise {
+ // === Special-only operations (no API call needed) ===
+ if (route.special === 'fileUpload') {
+ const result = await uploadFileToS3(this, i);
+ return [{ json: result }];
+ }
+ if (route.special === 'formProcessSubmission') {
+ // v1 only: pass through form submission data from webhook input
+ const inputData = this.getInputData()[i].json;
+ return [{ json: inputData }];
+ }
+ if (route.special === 'exportDownload') {
+ const exportId = this.getNodeParameter('id', i) as number;
+ const credentials = await this.getCredentials('pachcaApi');
+ const base = sanitizeBaseUrl(credentials.baseUrl as string);
+ const resp = await this.helpers.httpRequestWithAuthentication.call(this, 'pachcaApi', {
+ method: 'GET',
+ url: `${base}/chats/exports/${exportId}`,
+ ignoreHttpStatusErrors: true,
+ returnFullResponse: true,
+ disableFollowRedirect: true,
+ }) as { statusCode: number; headers: Record; body: unknown };
+ const location = resp.headers?.location;
+ if (location) {
+ return [{ json: { id: exportId, url: location } as unknown as IDataObject }];
+ }
+ if (typeof resp.body === 'object' && resp.body) {
+ return [{ json: resp.body as IDataObject }];
+ }
+ return [{ json: { id: exportId, success: true } as unknown as IDataObject }];
+ }
+ if (route.special === 'avatarUpload') {
+ let avatarUrl = route.path;
+ for (const pp of route.pathParams ?? []) {
+ const value = this.getNodeParameter(pp.n8n, i) as number;
+ avatarUrl = avatarUrl.replace(`{${pp.api}}`, String(value));
+ }
+ const result = await uploadAvatar(this, i, avatarUrl);
+ return [{ json: result }];
+ }
+ // === Build URL with path params ===
+ let url = route.path;
+ for (const pp of route.pathParams ?? []) {
+ let value: number | string;
+ if (pp.locator) {
+ value = resolveResourceLocator(this, pp.n8n, i, pp.v1Fallback);
+ } else {
+ try {
+ value = this.getNodeParameter(pp.n8n, i) as number;
+ } catch (e) {
+ if (pp.v1Fallback) {
+ value = this.getNodeParameter(pp.v1Fallback, i) as number;
+ } else {
+ throw e;
+ }
+ }
+ }
+ if (value === undefined || value === null || value === '') {
+ throw new Error(`Missing required path parameter: ${pp.n8n}`);
+ }
+ url = url.replace(`{${pp.api}}`, String(value));
+ }
+
+ // === Build body from required fields ===
+ const body: IDataObject = {};
+ for (const fm of route.bodyMap ?? []) {
+ let raw: unknown;
+ if (fm.locator) {
+ raw = resolveResourceLocator(this, fm.n8n, i);
+ } else {
+ raw = this.getNodeParameter(fm.n8n, i);
+ }
+ if (fm.isArray && typeof raw === 'string') {
+ body[fm.api] = splitAndValidateCommaList(this, raw, fm.n8n, fm.arrayType!, i);
+ } else {
+ body[fm.api] = raw as IDataObject;
+ }
+ }
+
+ // === Read optional body fields from collection ===
+ const collectionName = (nodeVersion === 1 && route.v1Collection) ? route.v1Collection : 'additionalFields';
+ let additional: IDataObject = {};
+ try { additional = this.getNodeParameter(collectionName, i, {}) as IDataObject; } catch { /* no collection */ }
+
+ for (const fm of route.optionalBodyMap ?? []) {
+ let val: unknown = additional[fm.n8n];
+
+ // If not in collection, try as top-level param (v1 compat for V1_TOP_LEVEL_PARAMS)
+ if (val === undefined) {
+ try { val = this.getNodeParameter(fm.n8n, i, undefined); } catch { /* not present */ }
+ }
+
+ if (val === undefined || val === null || val === '') continue;
+
+ // fixedCollection: extract inner array using subKey
+ if (fm.subKey && typeof val === 'object' && !Array.isArray(val)) {
+ val = (val as IDataObject)[fm.subKey] ?? val;
+ }
+ if (fm.isArray && typeof val === 'string') {
+ body[fm.api] = splitAndValidateCommaList(this, val, fm.n8n, fm.arrayType!, i);
+ } else if (fm.locator && typeof val === 'object' && val !== null && (val as IDataObject).__rl) {
+ body[fm.api] = (val as IDataObject).value;
+ } else {
+ body[fm.api] = val as IDataObject;
+ }
+ }
+
+ // === Read top-level query params ===
+ const qs: IDataObject = {};
+ for (const qm of route.queryMap ?? []) {
+ try {
+ let val: unknown;
+ if (qm.locator) {
+ val = resolveResourceLocator(this, qm.n8n, i);
+ } else {
+ val = this.getNodeParameter(qm.n8n, i);
+ }
+ if (val !== undefined && val !== null && val !== '') {
+ if (qm.isArray && typeof val === 'string') {
+ qs[qm.api] = splitAndValidateCommaList(this, val, qm.n8n, qm.arrayType!, i);
+ } else {
+ qs[qm.api] = val as IDataObject;
+ }
+ }
+ } catch (e) {
+ if (qm.required) throw e;
+ }
+ }
+
+ // Read query params from collection, with top-level fallback (same pattern as optionalBodyMap)
+ for (const qm of route.optionalQueryMap ?? []) {
+ let val: unknown = additional[qm.n8n];
+ if (val === undefined) {
+ try { val = this.getNodeParameter(qm.n8n, i, undefined); } catch { /* not present */ }
+ }
+ if (val !== undefined && val !== null && val !== '') {
+ if (qm.isArray && typeof val === 'string') {
+ qs[qm.api] = splitAndValidateCommaList(this, val, qm.n8n, qm.arrayType!, i);
+ } else {
+ qs[qm.api] = val;
+ }
+ }
+ }
+
+ // === Special handlers ===
+ if (route.special === 'messageButtons') {
+ const buttons = buildButtonRows(this, i);
+ if (buttons.length) body.buttons = buttons;
+ const files = cleanFileAttachments(this, i);
+ if (files.length) body.files = files;
+ }
+ if (route.special === 'formBlocks') {
+ const blocks = resolveFormBlocksFromParams(this, i);
+ if (blocks.length) body.blocks = blocks;
+ }
+ if (route.special === 'unfurlLinkPreviews') {
+ // v1 compat: linkPreviews was a fixedCollection { preview: [{ url, title, description, imageUrl }] }
+ // v2: linkPreviews is a JSON string. API expects { "url": { title, description, image_url } }
+ const raw = body.link_previews;
+ if (raw && typeof raw === 'object' && !Array.isArray(raw) && (raw as IDataObject).preview) {
+ const previews = (raw as IDataObject).preview as IDataObject[];
+ const converted: IDataObject = {};
+ for (const p of previews) {
+ if (!p.url) continue;
+ const entry: IDataObject = {};
+ if (p.title) entry.title = p.title;
+ if (p.description) entry.description = p.description;
+ if (p.imageUrl) entry.image_url = p.imageUrl;
+ converted[p.url as string] = entry;
+ }
+ body.link_previews = converted;
+ } else if (typeof raw === 'string') {
+ try { body.link_previews = JSON.parse(raw); } catch { /* leave as-is */ }
+ }
+ }
+ if (route.special === 'botWebhook') {
+ let webhookUrl: string | undefined;
+ try { webhookUrl = this.getNodeParameter('webhookUrl', i, '') as string; } catch { /* */ }
+ if (webhookUrl) {
+ body.webhook = { outgoing_url: webhookUrl };
+ }
+ }
+
+ // === Wrap body in key ===
+ let finalBody: IDataObject | undefined;
+ if (Object.keys(body).length > 0) {
+ if (route.wrapperKey) {
+ const inner: IDataObject = {};
+ const outer: IDataObject = {};
+ const siblingSet = new Set(route.siblingFields ?? []);
+ for (const [k, v] of Object.entries(body)) {
+ if (siblingSet.has(k)) {
+ outer[k] = v;
+ } else {
+ inner[k] = v;
+ }
+ }
+ finalBody = { [route.wrapperKey]: inner, ...outer };
+ } else {
+ finalBody = body;
+ }
+ }
+
+ // === Execute API call ===
+ if (route.paginated) {
+ let results = await makeApiRequestAllPages.call(
+ this, route.method, url, qs, i, resource, nodeVersion,
+ );
+
+ // v1 client-side post-filters for user.getAll
+ if (route.special === 'userGetAllFilters' && nodeVersion === 1) {
+ let filterOptions: IDataObject = {};
+ try { filterOptions = this.getNodeParameter('filterOptions', i, {}) as IDataObject; } catch { /* */ }
+
+ const filterRole = filterOptions.filterRole as string[] | undefined;
+ const filterBot = filterOptions.filterBot as string | undefined;
+ const filterSuspended = filterOptions.filterSuspended as string | undefined;
+ const filterInviteStatus = filterOptions.filterInviteStatus as string[] | undefined;
+
+ if (filterRole?.length || (filterBot && filterBot !== 'all') ||
+ (filterSuspended && filterSuspended !== 'all') || filterInviteStatus?.length) {
+ results = results.filter(item => {
+ const d = item.json;
+ if (filterRole?.length && !filterRole.includes(d.role as string)) return false;
+ if (filterBot === 'users' && d.bot === true) return false;
+ if (filterBot === 'bots' && d.bot !== true) return false;
+ if (filterSuspended === 'active' && d.suspended === true) return false;
+ if (filterSuspended === 'suspended' && d.suspended !== true) return false;
+ if (filterInviteStatus?.length && !filterInviteStatus.includes(d.invite_status as string)) return false;
+ return true;
+ });
+ }
+ }
+
+ return results;
+ }
+
+ const response = await makeApiRequest.call(
+ this, route.method, url, finalBody,
+ Object.keys(qs).length > 0 ? qs : undefined, i,
+ );
+
+ // === Handle response ===
+ if (route.method === 'DELETE') {
+ return [{ json: { success: true } }];
+ }
+
+ // Handle 204 No Content for non-DELETE methods (archive, unarchive, pin, addMembers, etc.)
+ if (!response || (typeof response === 'object' && Object.keys(response).length === 0)) {
+ return [{ json: { success: true } }];
+ }
+
+ if (route.noDataWrapper) {
+ if (!response || (typeof response === 'object' && Object.keys(response).length === 0)) {
+ return [{ json: { success: true } as unknown as IDataObject }];
+ }
+ return [{ json: response }];
+ }
+
+ const data = (response.data as IDataObject) ?? response;
+
+ // Simplify for GET single item (v2 only)
+ if (nodeVersion >= 2 && route.method === 'GET' && !route.paginated) {
+ let doSimplify = false;
+ try { doSimplify = this.getNodeParameter('simplify', i, false) as boolean; } catch { /* */ }
+ if (doSimplify) {
+ return [{ json: simplifyItem(data, resource) }];
+ }
+ }
+
+ return [{ json: data }];
+}
diff --git a/integrations/n8n/nodes/Pachca/V1/BotDescription.ts b/integrations/n8n/nodes/Pachca/V1/BotDescription.ts
new file mode 100644
index 00000000..aff3e9a0
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V1/BotDescription.ts
@@ -0,0 +1,58 @@
+// ============================================================================
+// BotDescription.ts — FROZEN V1 description (from n8n-nodes-pachca@1.0.27)
+// DO NOT EDIT — this file is frozen for backward compatibility
+// ============================================================================
+
+import type { INodeProperties } from 'n8n-workflow';
+
+export const botOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Update a bot',
+ value: 'update',
+ action: 'Update a bot',
+ description: 'Edit bot settings',
+ },
+ ],
+ default: 'update',
+ displayOptions: {
+ show: {
+ resource: ['bot'],
+ },
+ },
+ },
+];
+
+export const botFields: INodeProperties[] = [
+ {
+ displayName: 'Bot ID',
+ name: 'botId',
+ type: 'number',
+ default: 1,
+ description: 'Bot ID to edit',
+ displayOptions: {
+ show: {
+ resource: ['bot'],
+ operation: ['update'],
+ },
+ },
+ },
+ {
+ displayName: 'Webhook URL',
+ name: 'webhookUrl',
+ type: 'string',
+ default: '',
+ description: 'Outgoing webhook URL',
+ displayOptions: {
+ show: {
+ resource: ['bot'],
+ operation: ['update'],
+ },
+ },
+ },
+];
diff --git a/integrations/n8n/nodes/Pachca/V1/ChatDescription.ts b/integrations/n8n/nodes/Pachca/V1/ChatDescription.ts
new file mode 100644
index 00000000..8d1b8eb6
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V1/ChatDescription.ts
@@ -0,0 +1,305 @@
+// ============================================================================
+// ChatDescription.ts — FROZEN V1 description (from n8n-nodes-pachca@1.0.27)
+// DO NOT EDIT — this file is frozen for backward compatibility
+// ============================================================================
+
+import type { INodeProperties } from 'n8n-workflow';
+
+export const chatOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Get all chats',
+ value: 'getAll',
+ action: 'Get all chats',
+ description: 'Get list of all chats',
+ },
+ {
+ name: 'Get a chat',
+ value: 'getById',
+ action: 'Get a chat',
+ description: 'Get chat by ID',
+ },
+ {
+ name: 'Create a chat',
+ value: 'create',
+ action: 'Create a chat',
+ description: 'Create new chat',
+ },
+ {
+ name: 'Update a chat',
+ value: 'update',
+ action: 'Update a chat',
+ description: 'Update chat',
+ },
+ {
+ name: 'Archive a chat',
+ value: 'archive',
+ action: 'Archive a chat',
+ description: 'Archive chat',
+ },
+ {
+ name: 'Unarchive a chat',
+ value: 'unarchive',
+ action: 'Unarchive a chat',
+ description: 'Unarchive chat',
+ },
+ {
+ name: 'Get chat members',
+ value: 'getMembers',
+ action: 'Get chat members',
+ description: 'Get chat members list',
+ },
+ {
+ name: 'Add users to chat',
+ value: 'addUsers',
+ action: 'Add users to chat',
+ description: 'Add users to chat',
+ },
+ {
+ name: 'Remove user from chat',
+ value: 'removeUser',
+ action: 'Remove user from chat',
+ description: 'Remove user from chat',
+ },
+ {
+ name: 'Update user role in chat',
+ value: 'updateRole',
+ action: 'Update user role in chat',
+ description: 'Change user role in chat',
+ },
+ {
+ name: 'Leave a chat',
+ value: 'leaveChat',
+ action: 'Leave a chat',
+ description: 'Leave chat',
+ },
+ ],
+ default: 'getAll',
+ displayOptions: {
+ show: {
+ resource: ['chat'],
+ },
+ },
+ },
+];
+
+export const chatFields: INodeProperties[] = [
+ {
+ displayName: 'Additional Options',
+ name: 'paginationOptions',
+ type: 'collection',
+ options: [
+ {
+ displayName: 'Per Page',
+ name: 'per',
+ type: 'number',
+ default: 25,
+ description: 'Items per page (max 50)',
+ },
+ {
+ displayName: 'Page',
+ name: 'page',
+ type: 'number',
+ default: 1,
+ description: 'Page number',
+ },
+ ],
+ default: {},
+ placeholder: 'Add option',
+ displayOptions: {
+ show: {
+ resource: ['message', 'chat', 'groupTag', 'customFields'],
+ operation: ['getAll', 'getUsers'],
+ },
+ },
+ },
+ {
+ displayName: 'Chat ID',
+ name: 'chatId',
+ type: 'number',
+ default: 1,
+ description: 'Chat ID',
+ displayOptions: {
+ show: {
+ resource: ['chat'],
+ operation: ['getById', 'update', 'archive', 'unarchive', 'getMembers', 'addUsers', 'removeUser', 'updateRole', 'leaveChat'],
+ },
+ },
+ },
+ {
+ displayName: 'Chat Name',
+ name: 'chatName',
+ type: 'string',
+ default: '',
+ description: 'Chat name',
+ displayOptions: {
+ show: {
+ resource: ['chat'],
+ operation: ['create', 'update'],
+ },
+ },
+ },
+ {
+ displayName: 'Channel',
+ name: 'channel',
+ type: 'boolean',
+ default: false,
+ description: 'Create channel (true) or chat (false)',
+ displayOptions: {
+ show: {
+ resource: ['chat'],
+ operation: ['create'],
+ },
+ },
+ },
+ {
+ displayName: 'Public',
+ name: 'public',
+ type: 'boolean',
+ default: false,
+ description: 'Open (true) or closed (false) access',
+ displayOptions: {
+ show: {
+ resource: ['chat'],
+ operation: ['create'],
+ },
+ },
+ },
+ {
+ displayName: 'Additional Options',
+ name: 'chatMembersOptions',
+ type: 'collection',
+ options: [
+ {
+ displayName: 'Role',
+ name: 'role',
+ type: 'options',
+ options: [
+ {
+ name: 'All',
+ value: 'all',
+ description: 'Any role',
+ },
+ {
+ name: 'Owner',
+ value: 'owner',
+ description: 'Creator',
+ },
+ {
+ name: 'Admin',
+ value: 'admin',
+ description: 'Administrator',
+ },
+ {
+ name: 'Editor',
+ value: 'editor',
+ description: 'Editor',
+ },
+ {
+ name: 'Member',
+ value: 'member',
+ description: 'Member/Subscriber',
+ },
+ ],
+ default: 'all',
+ description: 'Chat role filter',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 50,
+ description: 'Number of members to return (max 50)',
+ },
+ {
+ displayName: 'Cursor',
+ name: 'cursor',
+ type: 'string',
+ default: '',
+ description: 'Pagination cursor (from meta.paginate.next_page)',
+ },
+ ],
+ default: {},
+ placeholder: 'Add option',
+ displayOptions: {
+ show: {
+ resource: ['chat'],
+ operation: ['getMembers'],
+ },
+ },
+ },
+ {
+ displayName: 'Member IDs',
+ name: 'memberIds',
+ type: 'string',
+ default: '',
+ description: 'Comma-separated user IDs (e.g. 186,187)',
+ displayOptions: {
+ show: {
+ resource: ['chat'],
+ operation: ['addUsers'],
+ },
+ },
+ },
+ {
+ displayName: 'Silent',
+ name: 'silent',
+ type: 'boolean',
+ default: false,
+ description: 'Do not create system message about adding member',
+ displayOptions: {
+ show: {
+ resource: ['chat'],
+ operation: ['addUsers'],
+ },
+ },
+ },
+ {
+ displayName: 'User ID',
+ name: 'userId',
+ type: 'number',
+ default: 1,
+ description: 'User ID',
+ displayOptions: {
+ show: {
+ resource: ['chat'],
+ operation: ['removeUser', 'updateRole'],
+ },
+ },
+ },
+ {
+ displayName: 'New Role',
+ name: 'newRole',
+ type: 'options',
+ options: [
+ {
+ name: 'Admin',
+ value: 'admin',
+ description: 'Administrator',
+ },
+ {
+ name: 'Editor',
+ value: 'editor',
+ description: 'Editor (channels only)',
+ },
+ {
+ name: 'Member',
+ value: 'member',
+ description: 'Member/Subscriber',
+ },
+ ],
+ default: 'member',
+ description: 'New user role',
+ displayOptions: {
+ show: {
+ resource: ['chat'],
+ operation: ['updateRole'],
+ },
+ },
+ },
+];
diff --git a/integrations/n8n/nodes/Pachca/V1/CustomFieldsDescription.ts b/integrations/n8n/nodes/Pachca/V1/CustomFieldsDescription.ts
new file mode 100644
index 00000000..811ef841
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V1/CustomFieldsDescription.ts
@@ -0,0 +1,86 @@
+// ============================================================================
+// CustomFieldsDescription.ts — FROZEN V1 description (from n8n-nodes-pachca@1.0.27)
+// DO NOT EDIT — this file is frozen for backward compatibility
+// ============================================================================
+
+import type { INodeProperties } from 'n8n-workflow';
+
+export const customFieldsOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Get custom properties',
+ value: 'getCustomProperties',
+ action: 'Get custom properties',
+ description: 'Get list of custom fields for entity',
+ },
+ ],
+ default: 'getCustomProperties',
+ displayOptions: {
+ show: {
+ resource: ['customFields'],
+ },
+ },
+ },
+];
+
+export const customFieldsFields: INodeProperties[] = [
+ {
+ displayName: 'Additional Options',
+ name: 'paginationOptions',
+ type: 'collection',
+ options: [
+ {
+ displayName: 'Per Page',
+ name: 'per',
+ type: 'number',
+ default: 25,
+ description: 'Items per page (max 50)',
+ },
+ {
+ displayName: 'Page',
+ name: 'page',
+ type: 'number',
+ default: 1,
+ description: 'Page number',
+ },
+ ],
+ default: {},
+ placeholder: 'Add option',
+ displayOptions: {
+ show: {
+ resource: ['message', 'chat', 'groupTag', 'customFields'],
+ operation: ['getAll', 'getUsers'],
+ },
+ },
+ },
+ {
+ displayName: 'Entity Type',
+ name: 'entityType',
+ type: 'options',
+ options: [
+ {
+ name: 'User',
+ value: 'User',
+ description: 'Member',
+ },
+ {
+ name: 'Task',
+ value: 'Task',
+ description: 'Reminder',
+ },
+ ],
+ default: 'User',
+ description: 'Entity type for custom fields',
+ displayOptions: {
+ show: {
+ resource: ['customFields'],
+ operation: ['getCustomProperties'],
+ },
+ },
+ },
+];
diff --git a/integrations/n8n/nodes/Pachca/V1/FileDescription.ts b/integrations/n8n/nodes/Pachca/V1/FileDescription.ts
new file mode 100644
index 00000000..dcd47439
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V1/FileDescription.ts
@@ -0,0 +1,111 @@
+// ============================================================================
+// FileDescription.ts — FROZEN V1 description (from n8n-nodes-pachca@1.0.27)
+// DO NOT EDIT — this file is frozen for backward compatibility
+// ============================================================================
+
+import type { INodeProperties } from 'n8n-workflow';
+
+export const fileOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Upload a file',
+ value: 'upload',
+ action: 'Upload a file',
+ description: 'Upload file (full flow: get params + upload)',
+ },
+ ],
+ default: 'upload',
+ displayOptions: {
+ show: {
+ resource: ['file'],
+ },
+ },
+ },
+];
+
+export const fileFields: INodeProperties[] = [
+ {
+ displayName: 'File Source',
+ name: 'fileSource',
+ type: 'options',
+ options: [
+ {
+ name: 'URL',
+ value: 'url',
+ description: 'Download file from URL',
+ },
+ {
+ name: 'Binary Data',
+ value: 'binary',
+ description: 'Use binary data from previous node',
+ },
+ ],
+ default: 'url',
+ description: 'File source for upload',
+ displayOptions: {
+ show: {
+ resource: ['file'],
+ operation: ['upload'],
+ },
+ },
+ },
+ {
+ displayName: 'File URL',
+ name: 'fileUrl',
+ type: 'string',
+ default: '',
+ description: 'File URL to download and upload to Pachca',
+ displayOptions: {
+ show: {
+ resource: ['file'],
+ operation: ['upload'],
+ fileSource: ['url'],
+ },
+ },
+ },
+ {
+ displayName: 'Binary Property',
+ name: 'binaryProperty',
+ type: 'string',
+ default: 'data',
+ description: 'Binary property name from previous node',
+ displayOptions: {
+ show: {
+ resource: ['file'],
+ operation: ['upload'],
+ fileSource: ['binary'],
+ },
+ },
+ },
+ {
+ displayName: 'File Name',
+ name: 'fileName',
+ type: 'string',
+ default: '',
+ description: 'File name (if not set, taken from URL or binary data)',
+ displayOptions: {
+ show: {
+ resource: ['file'],
+ operation: ['upload'],
+ },
+ },
+ },
+ {
+ displayName: 'Content Type',
+ name: 'contentType',
+ type: 'string',
+ default: 'application/octet-stream',
+ description: 'File MIME type (auto-detected if not set)',
+ displayOptions: {
+ show: {
+ resource: ['file'],
+ operation: ['upload'],
+ },
+ },
+ },
+];
diff --git a/integrations/n8n/nodes/Pachca/V1/FormDescription.ts b/integrations/n8n/nodes/Pachca/V1/FormDescription.ts
new file mode 100644
index 00000000..8f11d243
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V1/FormDescription.ts
@@ -0,0 +1,517 @@
+// ============================================================================
+// FormDescription.ts — FROZEN V1 description (from n8n-nodes-pachca@1.0.27)
+// DO NOT EDIT — this file is frozen for backward compatibility
+// ============================================================================
+
+import type { INodeProperties } from 'n8n-workflow';
+
+export const formOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Create a form',
+ value: 'createView',
+ action: 'Create a form',
+ description: 'Create and open modal with form',
+ },
+ {
+ name: 'Process form submission',
+ value: 'processSubmission',
+ action: 'Process form submission',
+ description: 'Handle form submit and send response',
+ },
+ ],
+ default: 'createView',
+ displayOptions: {
+ show: {
+ resource: ['form'],
+ },
+ },
+ },
+];
+
+export const formFields: INodeProperties[] = [
+ {
+ displayName: 'Form Builder Mode',
+ name: 'formBuilderMode',
+ type: 'options',
+ options: [
+ {
+ name: '🎨 Visual Builder',
+ value: 'builder',
+ description: 'Visual form builder',
+ },
+ {
+ name: '🔧 Raw JSON',
+ value: 'json',
+ description: 'Edit JSON directly',
+ },
+ ],
+ default: 'builder',
+ description: 'Form creation method',
+ displayOptions: {
+ show: {
+ resource: ['form'],
+ operation: ['createView'],
+ },
+ },
+ },
+ {
+ displayName: 'Form Title',
+ name: 'formTitle',
+ type: 'string',
+ default: 'My form',
+ description: 'Form title (max 24 characters)',
+ displayOptions: {
+ show: {
+ resource: ['form'],
+ operation: ['createView'],
+ formBuilderMode: ['builder'],
+ },
+ },
+ },
+ {
+ displayName: 'Close Button Text',
+ name: 'closeText',
+ type: 'string',
+ default: 'Cancel',
+ description: 'Close button text (max 24 characters)',
+ displayOptions: {
+ show: {
+ resource: ['form'],
+ operation: ['createView'],
+ formBuilderMode: ['builder'],
+ },
+ },
+ },
+ {
+ displayName: 'Submit Button Text',
+ name: 'submitText',
+ type: 'string',
+ default: 'Submit',
+ description: 'Submit button text (max 24 characters)',
+ displayOptions: {
+ show: {
+ resource: ['form'],
+ operation: ['createView'],
+ formBuilderMode: ['builder'],
+ },
+ },
+ },
+ {
+ displayName: 'Form Blocks',
+ name: 'formBlocks',
+ type: 'fixedCollection',
+ typeOptions: {
+ multipleValues: true,
+ sortable: true,
+ },
+ options: [
+ {
+ name: 'block',
+ displayName: 'Block',
+ values: [
+ {
+ displayName: 'Block Type',
+ name: 'type',
+ type: 'options',
+ options: [
+ {
+ name: '📝 Header',
+ value: 'header',
+ description: 'Section header',
+ },
+ {
+ name: '📄 Plain Text',
+ value: 'plain_text',
+ description: 'Plain text',
+ },
+ {
+ name: '📝 Markdown Text',
+ value: 'markdown',
+ description: 'Formatted text',
+ },
+ {
+ name: '➖ Divider',
+ value: 'divider',
+ description: 'Divider',
+ },
+ {
+ name: '📝 Text Input',
+ value: 'input',
+ description: 'Text input',
+ },
+ {
+ name: '📋 Select Dropdown',
+ value: 'select',
+ description: 'Dropdown',
+ },
+ {
+ name: '🔘 Radio Buttons',
+ value: 'radio',
+ description: 'Radio buttons',
+ },
+ {
+ name: '☑️ Checkboxes',
+ value: 'checkbox',
+ description: 'Checkboxes',
+ },
+ {
+ name: '📅 Date Picker',
+ value: 'date',
+ description: 'Date picker',
+ },
+ {
+ name: '🕐 Time Picker',
+ value: 'time',
+ description: 'Time picker',
+ },
+ {
+ name: '📎 File Upload',
+ value: 'file_input',
+ description: 'File upload',
+ },
+ ],
+ default: 'header',
+ },
+ {
+ displayName: 'Text Content',
+ name: 'text',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ type: ['header', 'plain_text', 'markdown'],
+ },
+ },
+ description: 'Display text',
+ },
+ {
+ displayName: 'Field Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ type: ['input', 'select', 'radio', 'checkbox', 'date', 'time', 'file_input'],
+ },
+ },
+ description: 'Field name (sent in webhook)',
+ },
+ {
+ displayName: 'Field Label',
+ name: 'label',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ type: ['input', 'select', 'radio', 'checkbox', 'date', 'time', 'file_input'],
+ },
+ },
+ description: 'Field label',
+ },
+ {
+ displayName: 'Required',
+ name: 'required',
+ type: 'boolean',
+ default: false,
+ displayOptions: {
+ show: {
+ type: ['input', 'select', 'radio', 'checkbox', 'date', 'time', 'file_input'],
+ },
+ },
+ description: 'Required field',
+ },
+ {
+ displayName: 'Hint',
+ name: 'hint',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ type: ['input', 'select', 'radio', 'checkbox', 'date', 'time', 'file_input'],
+ },
+ },
+ description: 'Hint below field',
+ },
+ {
+ displayName: 'Placeholder',
+ name: 'placeholder',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ type: ['input'],
+ },
+ },
+ description: 'Placeholder text',
+ },
+ {
+ displayName: 'Multiline',
+ name: 'multiline',
+ type: 'boolean',
+ default: false,
+ displayOptions: {
+ show: {
+ type: ['input'],
+ },
+ },
+ description: 'Multiline field',
+ },
+ {
+ displayName: 'Initial Value',
+ name: 'initial_value',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ type: ['input'],
+ },
+ },
+ description: 'Default value',
+ },
+ {
+ displayName: 'Min Length',
+ name: 'min_length',
+ type: 'number',
+ default: 0,
+ displayOptions: {
+ show: {
+ type: ['input'],
+ },
+ },
+ description: 'Min text length',
+ },
+ {
+ displayName: 'Max Length',
+ name: 'max_length',
+ type: 'number',
+ default: 3000,
+ displayOptions: {
+ show: {
+ type: ['input'],
+ },
+ },
+ description: 'Max text length',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'fixedCollection',
+ typeOptions: {
+ multipleValues: true,
+ },
+ default: [],
+ displayOptions: {
+ show: {
+ type: ['select', 'radio', 'checkbox'],
+ },
+ },
+ options: [
+ {
+ name: 'option',
+ displayName: 'Option',
+ values: [
+ {
+ displayName: 'Text',
+ name: 'text',
+ type: 'string',
+ default: '',
+ description: 'Display text',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: '',
+ description: 'Value to submit',
+ },
+ {
+ displayName: 'Description',
+ name: 'description',
+ type: 'string',
+ default: '',
+ description: 'Option description (radio/checkbox)',
+ },
+ {
+ displayName: 'Selected',
+ name: 'selected',
+ type: 'boolean',
+ default: false,
+ description: 'Selected by default (select/radio)',
+ },
+ {
+ displayName: 'Checked',
+ name: 'checked',
+ type: 'boolean',
+ default: false,
+ description: 'Checked by default (checkbox)',
+ },
+ ],
+ },
+ ],
+ description: 'Choice options',
+ },
+ {
+ displayName: 'Initial Date',
+ name: 'initial_date',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ type: ['date'],
+ },
+ },
+ description: 'Initial date (YYYY-MM-DD)',
+ },
+ {
+ displayName: 'Initial Time',
+ name: 'initial_time',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ type: ['time'],
+ },
+ },
+ description: 'Initial time (HH:mm)',
+ },
+ {
+ displayName: 'File Types',
+ name: 'filetypes',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ type: ['file_input'],
+ },
+ },
+ description: 'Allowed file types (comma-separated, e.g. pdf,jpg,png)',
+ },
+ {
+ displayName: 'Max Files',
+ name: 'max_files',
+ type: 'number',
+ default: 10,
+ displayOptions: {
+ show: {
+ type: ['file_input'],
+ },
+ },
+ description: 'Max number of files',
+ },
+ ],
+ },
+ ],
+ default: [],
+ description: 'Form blocks - add elements to build the form',
+ displayOptions: {
+ show: {
+ resource: ['form'],
+ operation: ['createView'],
+ formBuilderMode: ['builder'],
+ },
+ },
+ },
+ {
+ displayName: 'Custom Form JSON',
+ name: 'customFormJson',
+ type: 'json',
+ default: '{\n "title": "My form",\n "close_text": "Cancel",\n "submit_text": "Submit",\n "blocks": [\n {\n "type": "header",\n "text": "Form title"\n },\n {\n "type": "input",\n "name": "field1",\n "label": "Input field",\n "placeholder": "Enter text",\n "required": true\n },\n {\n "type": "select",\n "name": "choice",\n "label": "Choose option",\n "options": [\n {"text": "Option 1", "value": "option1", "selected": true},\n {"text": "Option 2", "value": "option2"}\n ],\n "required": true\n }\n ]\n}',
+ description: 'JSON structure for custom form. Use blocks: header, plain_text, markdown, divider, input, select, radio, checkbox, date, time, file_input',
+ displayOptions: {
+ show: {
+ resource: ['form'],
+ operation: ['createView'],
+ formBuilderMode: ['json'],
+ },
+ },
+ },
+ {
+ displayName: 'Trigger ID',
+ name: 'triggerId',
+ type: 'string',
+ default: '',
+ description: 'Unique event ID (from button webhook)',
+ displayOptions: {
+ show: {
+ resource: ['form'],
+ operation: ['createView'],
+ },
+ },
+ },
+ {
+ displayName: 'Private Metadata',
+ name: 'privateMetadata',
+ type: 'string',
+ default: '',
+ description: 'Extra data to send on form submit (JSON string)',
+ displayOptions: {
+ show: {
+ resource: ['form'],
+ operation: ['createView'],
+ },
+ },
+ },
+ {
+ displayName: 'Callback ID',
+ name: 'callbackId',
+ type: 'string',
+ default: '',
+ description: 'Form identifier for matching submitted results',
+ displayOptions: {
+ show: {
+ resource: ['form'],
+ operation: ['createView'],
+ },
+ },
+ },
+ {
+ displayName: 'Form Type',
+ name: 'formType',
+ type: 'options',
+ options: [
+ {
+ name: '🤖 Auto-detect (recommended)',
+ value: 'auto',
+ },
+ {
+ name: '📋 Timeoff Request',
+ value: 'timeoff_request',
+ },
+ {
+ name: '💬 Feedback Form',
+ value: 'feedback_form',
+ },
+ {
+ name: '📝 Task Request',
+ value: 'task_request',
+ },
+ ],
+ default: 'auto',
+ description: 'Form type for processing data',
+ displayOptions: {
+ show: {
+ resource: ['form'],
+ operation: ['processSubmission'],
+ },
+ },
+ },
+ {
+ displayName: 'Validation Errors',
+ name: 'validationErrors',
+ type: 'json',
+ default: '{}',
+ description: 'Validation errors to send to user (JSON object with field names and messages)',
+ displayOptions: {
+ show: {
+ resource: ['form'],
+ operation: ['processSubmission'],
+ },
+ },
+ },
+];
diff --git a/integrations/n8n/nodes/Pachca/V1/GroupTagDescription.ts b/integrations/n8n/nodes/Pachca/V1/GroupTagDescription.ts
new file mode 100644
index 00000000..29e56075
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V1/GroupTagDescription.ts
@@ -0,0 +1,181 @@
+// ============================================================================
+// GroupTagDescription.ts — FROZEN V1 description (from n8n-nodes-pachca@1.0.27)
+// DO NOT EDIT — this file is frozen for backward compatibility
+// ============================================================================
+
+import type { INodeProperties } from 'n8n-workflow';
+
+export const groupTagOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Get all group tags',
+ value: 'getAll',
+ action: 'Get all group tags',
+ description: 'Get list of all group tags',
+ },
+ {
+ name: 'Get a group tag',
+ value: 'getById',
+ action: 'Get a group tag',
+ description: 'Get group tag by ID',
+ },
+ {
+ name: 'Create a group tag',
+ value: 'create',
+ action: 'Create a group tag',
+ description: 'Create new group tag',
+ },
+ {
+ name: 'Update a group tag',
+ value: 'update',
+ action: 'Update a group tag',
+ description: 'Update group tag',
+ },
+ {
+ name: 'Delete a group tag',
+ value: 'delete',
+ action: 'Delete a group tag',
+ description: 'Delete group tag',
+ },
+ {
+ name: 'Get users in group tag',
+ value: 'getUsers',
+ action: 'Get users in group tag',
+ description: 'Get users in group tag',
+ },
+ {
+ name: 'Add tags to chat',
+ value: 'addTags',
+ action: 'Add tags to chat',
+ description: 'Add tags to chat',
+ },
+ {
+ name: 'Remove tag from chat',
+ value: 'removeTag',
+ action: 'Remove tag from chat',
+ description: 'Remove tag from chat',
+ },
+ ],
+ default: 'getAll',
+ displayOptions: {
+ show: {
+ resource: ['groupTag'],
+ },
+ },
+ },
+];
+
+export const groupTagFields: INodeProperties[] = [
+ {
+ displayName: 'Additional Options',
+ name: 'paginationOptions',
+ type: 'collection',
+ options: [
+ {
+ displayName: 'Per Page',
+ name: 'per',
+ type: 'number',
+ default: 25,
+ description: 'Items per page (max 50)',
+ },
+ {
+ displayName: 'Page',
+ name: 'page',
+ type: 'number',
+ default: 1,
+ description: 'Page number',
+ },
+ ],
+ default: {},
+ placeholder: 'Add option',
+ displayOptions: {
+ show: {
+ resource: ['message', 'chat', 'groupTag', 'customFields'],
+ operation: ['getAll', 'getUsers'],
+ },
+ },
+ },
+ {
+ displayName: 'Group Tag ID',
+ name: 'groupTagId',
+ type: 'number',
+ default: 1,
+ description: 'Group tag ID',
+ displayOptions: {
+ show: {
+ resource: ['groupTag'],
+ operation: ['getById', 'update', 'delete', 'getUsers', 'removeTag'],
+ },
+ },
+ },
+ {
+ displayName: 'Group Tag Name',
+ name: 'groupTagName',
+ type: 'string',
+ default: '',
+ description: 'Group tag name',
+ displayOptions: {
+ show: {
+ resource: ['groupTag'],
+ operation: ['create', 'update'],
+ },
+ },
+ },
+ {
+ displayName: 'Group Tag Color',
+ name: 'groupTagColor',
+ type: 'string',
+ default: '#000000',
+ description: 'Group tag color (hex code)',
+ displayOptions: {
+ show: {
+ resource: ['groupTag'],
+ operation: ['create', 'update'],
+ },
+ },
+ },
+ {
+ displayName: 'Chat ID',
+ name: 'groupTagChatId',
+ type: 'number',
+ default: 1,
+ description: 'Chat ID for tag operations',
+ displayOptions: {
+ show: {
+ resource: ['groupTag'],
+ operation: ['addTags', 'removeTag'],
+ },
+ },
+ },
+ {
+ displayName: 'Group Tag IDs',
+ name: 'groupTagIds',
+ type: 'string',
+ default: '',
+ description: 'Comma-separated tag IDs (e.g. 86,18)',
+ displayOptions: {
+ show: {
+ resource: ['groupTag'],
+ operation: ['addTags'],
+ },
+ },
+ },
+ {
+ displayName: 'Tag ID',
+ name: 'tagId',
+ type: 'number',
+ default: 1,
+ description: 'Tag ID to remove',
+ displayOptions: {
+ show: {
+ resource: ['groupTag'],
+ operation: ['removeTag'],
+ },
+ },
+ },
+];
diff --git a/integrations/n8n/nodes/Pachca/V1/MessageDescription.ts b/integrations/n8n/nodes/Pachca/V1/MessageDescription.ts
new file mode 100644
index 00000000..d945a8aa
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V1/MessageDescription.ts
@@ -0,0 +1,502 @@
+// ============================================================================
+// MessageDescription.ts — FROZEN V1 description (from n8n-nodes-pachca@1.0.27)
+// DO NOT EDIT — this file is frozen for backward compatibility
+// ============================================================================
+
+import type { INodeProperties } from 'n8n-workflow';
+
+export const messageOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Send a message',
+ value: 'send',
+ action: 'Send a message',
+ description: 'Send message',
+ },
+ {
+ name: 'Get a message',
+ value: 'getById',
+ action: 'Get a message',
+ description: 'Get message by ID',
+ },
+ {
+ name: 'Get messages from a chat',
+ value: 'getAll',
+ action: 'Get messages from a chat',
+ description: 'Get chat messages',
+ },
+ {
+ name: 'Update a message',
+ value: 'update',
+ action: 'Update a message',
+ description: 'Edit message',
+ },
+ {
+ name: 'Delete a message',
+ value: 'delete',
+ action: 'Delete a message',
+ description: 'Delete message (Admin/Owner tokens only)',
+ },
+ {
+ name: 'Pin a message',
+ value: 'pin',
+ action: 'Pin a message',
+ description: 'Pin message',
+ },
+ {
+ name: 'Unpin a message',
+ value: 'unpin',
+ action: 'Unpin a message',
+ description: 'Unpin message',
+ },
+ {
+ name: 'Get message readers',
+ value: 'getReadMembers',
+ action: 'Get message readers',
+ description: 'Get list of message readers',
+ },
+ {
+ name: 'Unfurl message links',
+ value: 'unfurl',
+ action: 'Unfurl message links',
+ description: 'Create link previews in message (unfurl)',
+ },
+ ],
+ default: 'send',
+ displayOptions: {
+ show: {
+ resource: ['message'],
+ },
+ },
+ },
+];
+
+export const messageFields: INodeProperties[] = [
+ {
+ displayName: 'Entity Type',
+ name: 'entityType',
+ type: 'options',
+ options: [
+ {
+ name: 'Discussion',
+ value: 'discussion',
+ description: 'Chat or channel',
+ },
+ {
+ name: 'User',
+ value: 'user',
+ description: 'Direct message to user',
+ },
+ {
+ name: 'Thread',
+ value: 'thread',
+ description: 'Thread comment',
+ },
+ ],
+ default: 'discussion',
+ description: 'Entity type for sending message',
+ displayOptions: {
+ show: {
+ resource: ['message'],
+ operation: ['send'],
+ },
+ },
+ },
+ {
+ displayName: 'Entity ID',
+ name: 'entityId',
+ type: 'number',
+ default: '',
+ description: 'Chat, user or thread ID',
+ displayOptions: {
+ show: {
+ resource: ['message'],
+ operation: ['send'],
+ },
+ },
+ },
+ {
+ displayName: 'Content',
+ name: 'content',
+ type: 'string',
+ typeOptions: {
+ rows: 4,
+ },
+ default: '',
+ description: 'Message text',
+ displayOptions: {
+ show: {
+ resource: ['message'],
+ operation: ['send', 'update'],
+ },
+ },
+ },
+ {
+ displayName: 'Files',
+ name: 'files',
+ type: 'fixedCollection',
+ typeOptions: {
+ multipleValues: true,
+ },
+ options: [
+ {
+ name: 'file',
+ displayName: 'File',
+ values: [
+ {
+ displayName: 'File Key',
+ name: 'key',
+ type: 'string',
+ default: '',
+ description: 'File path/key from file upload result',
+ required: true,
+ },
+ {
+ displayName: 'File Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ description: 'File name shown to user',
+ required: true,
+ },
+ {
+ displayName: 'File Type',
+ name: 'fileType',
+ type: 'options',
+ options: [
+ {
+ name: 'File',
+ value: 'file',
+ },
+ {
+ name: 'Image',
+ value: 'image',
+ },
+ ],
+ default: 'file',
+ description: 'File type',
+ required: true,
+ },
+ {
+ displayName: 'File Size (bytes)',
+ name: 'size',
+ type: 'number',
+ default: 0,
+ description: 'File size in bytes',
+ required: true,
+ },
+ {
+ displayName: 'Width (px)',
+ name: 'width',
+ type: 'number',
+ default: '',
+ displayOptions: {
+ show: {
+ fileType: ['image'],
+ },
+ },
+ description: 'Image width in pixels (images only)',
+ },
+ {
+ displayName: 'Height (px)',
+ name: 'height',
+ type: 'number',
+ default: '',
+ displayOptions: {
+ show: {
+ fileType: ['image'],
+ },
+ },
+ description: 'Image height in pixels (images only)',
+ },
+ ],
+ },
+ ],
+ default: [],
+ description: 'Attached files',
+ displayOptions: {
+ show: {
+ resource: ['message'],
+ operation: ['send', 'update'],
+ },
+ },
+ },
+ {
+ displayName: 'Button Layout',
+ name: 'buttonLayout',
+ type: 'options',
+ options: [
+ {
+ name: 'Single Row (all buttons in one row)',
+ value: 'single_row',
+ },
+ {
+ name: 'Multiple Rows (each button on its own row)',
+ value: 'multiple_rows',
+ },
+ {
+ name: 'Raw JSON',
+ value: 'raw_json',
+ description: 'Enter button JSON directly',
+ },
+ ],
+ default: 'single_row',
+ description: 'Button layout style',
+ displayOptions: {
+ show: {
+ resource: ['message'],
+ operation: ['send', 'update'],
+ },
+ },
+ },
+ {
+ displayName: 'Buttons',
+ name: 'buttons',
+ type: 'fixedCollection',
+ typeOptions: {
+ multipleValues: true,
+ },
+ options: [
+ {
+ name: 'button',
+ displayName: 'Button',
+ values: [
+ {
+ displayName: 'Button Text',
+ name: 'text',
+ type: 'string',
+ default: '',
+ description: 'Button text',
+ },
+ {
+ displayName: 'Button Type',
+ name: 'type',
+ type: 'options',
+ options: [
+ {
+ name: 'Data Button (for forms)',
+ value: 'data',
+ },
+ {
+ name: 'URL Button',
+ value: 'url',
+ },
+ ],
+ default: 'data',
+ },
+ {
+ displayName: 'Data Value',
+ name: 'data',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ type: ['data'],
+ },
+ },
+ description: 'Value for Data button (sent in webhook)',
+ },
+ {
+ displayName: 'URL',
+ name: 'url',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ type: ['url'],
+ },
+ },
+ description: 'URL to open',
+ },
+ ],
+ },
+ ],
+ default: [],
+ description: 'Message buttons (Data buttons for forms, URL buttons for links)',
+ displayOptions: {
+ show: {
+ resource: ['message'],
+ operation: ['send', 'update'],
+ buttonLayout: ['single_row', 'multiple_rows'],
+ },
+ },
+ },
+ {
+ displayName: 'Raw JSON Buttons',
+ name: 'rawJsonButtons',
+ type: 'json',
+ default: '[\n [\n {"text": "👍 Agree", "data": "vote_yes"},\n {"text": "❌ Decline", "data": "vote_no"}\n ],\n [\n {"text": "🕒 Postpone by a week", "data": "pause_week"}\n ],\n [\n {"text": "My projects", "url": "https://projects.com/list"}\n ]\n]',
+ description: 'Raw JSON for buttons in API format: array of arrays (each row is an array of buttons). Use button array [{...}, {...}] or rows [[{...}, {...}], [{...}]]. See example above.',
+ displayOptions: {
+ show: {
+ resource: ['message'],
+ operation: ['send', 'update'],
+ buttonLayout: ['raw_json'],
+ },
+ },
+ },
+ {
+ displayName: 'Message ID',
+ name: 'messageId',
+ type: 'number',
+ default: '',
+ description: 'Message ID',
+ displayOptions: {
+ show: {
+ resource: ['message'],
+ operation: ['getById', 'update', 'delete', 'pin', 'unpin', 'getReadMembers'],
+ },
+ },
+ },
+ {
+ displayName: 'Chat ID',
+ name: 'chatId',
+ type: 'number',
+ default: '',
+ description: 'Chat ID to get messages from',
+ displayOptions: {
+ show: {
+ resource: ['message'],
+ operation: ['getAll'],
+ },
+ },
+ },
+ {
+ displayName: 'Additional Options',
+ name: 'paginationOptions',
+ type: 'collection',
+ options: [
+ {
+ displayName: 'Per Page',
+ name: 'per',
+ type: 'number',
+ default: 25,
+ description: 'Items per page (max 50)',
+ },
+ {
+ displayName: 'Page',
+ name: 'page',
+ type: 'number',
+ default: 1,
+ description: 'Page number',
+ },
+ ],
+ default: {},
+ placeholder: 'Add option',
+ displayOptions: {
+ show: {
+ resource: ['message', 'chat', 'groupTag', 'customFields'],
+ operation: ['getAll', 'getUsers'],
+ },
+ },
+ },
+ {
+ displayName: 'Additional Options',
+ name: 'readMembersOptions',
+ type: 'collection',
+ options: [
+ {
+ displayName: 'Per Page',
+ name: 'readMembersPer',
+ type: 'number',
+ default: 300,
+ description: 'Number of users to return (max 300)',
+ },
+ {
+ displayName: 'Page',
+ name: 'readMembersPage',
+ type: 'number',
+ default: 1,
+ description: 'Page of readers to fetch',
+ },
+ ],
+ default: {},
+ placeholder: 'Add option',
+ displayOptions: {
+ show: {
+ resource: ['message'],
+ operation: ['getReadMembers'],
+ },
+ },
+ },
+ {
+ displayName: 'Message ID',
+ name: 'messageId',
+ type: 'number',
+ default: '',
+ description: 'Message ID for creating link previews (unfurl)',
+ displayOptions: {
+ show: {
+ resource: ['message'],
+ operation: ['unfurl'],
+ },
+ },
+ },
+ {
+ displayName: 'Link Previews',
+ name: 'linkPreviews',
+ type: 'fixedCollection',
+ typeOptions: {
+ multipleValues: true,
+ },
+ options: [
+ {
+ name: 'preview',
+ displayName: 'Preview',
+ values: [
+ {
+ displayName: 'URL',
+ name: 'url',
+ type: 'string',
+ default: '',
+ description: 'Link URL for preview (unfurl)',
+ required: true,
+ },
+ {
+ displayName: 'Title',
+ name: 'title',
+ type: 'string',
+ default: '',
+ description: 'Link preview title',
+ required: true,
+ },
+ {
+ displayName: 'Description',
+ name: 'description',
+ type: 'string',
+ default: '',
+ description: 'Link preview description',
+ required: true,
+ },
+ {
+ displayName: 'Image URL',
+ name: 'imageUrl',
+ type: 'string',
+ default: '',
+ description: 'Public image URL (used when no file is provided)',
+ },
+ {
+ displayName: 'Binary Property',
+ name: 'image',
+ type: 'string',
+ default: '',
+ description: 'Binary property with image (overrides Image URL)',
+ },
+ ],
+ },
+ ],
+ default: {},
+ description: 'Link previews to create (unfurl). Each URL must be from the message the preview is created for.',
+ displayOptions: {
+ show: {
+ resource: ['message'],
+ operation: ['unfurl'],
+ },
+ },
+ },
+];
diff --git a/integrations/n8n/nodes/Pachca/V1/PachcaV1.node.ts b/integrations/n8n/nodes/Pachca/V1/PachcaV1.node.ts
new file mode 100644
index 00000000..7aa89e43
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V1/PachcaV1.node.ts
@@ -0,0 +1,206 @@
+// ============================================================================
+// PachcaV1.node.ts — FROZEN V1 node class (from n8n-nodes-pachca@1.0.27)
+// DO NOT EDIT — this file is frozen for backward compatibility
+// ============================================================================
+
+import type {
+ INodeType,
+ INodeTypeBaseDescription,
+ INodeTypeDescription,
+ IExecuteFunctions,
+ INodeExecutionData,
+ ILoadOptionsFunctions,
+ INodeListSearchResult,
+ INodePropertyOptions,
+} from 'n8n-workflow';
+import { NodeConnectionTypes } from 'n8n-workflow';
+import { router } from '../SharedRouter';
+
+import { messageOperations, messageFields } from './MessageDescription';
+import { threadOperations, threadFields } from './ThreadDescription';
+import { reactionsOperations, reactionsFields } from './ReactionsDescription';
+import { chatOperations, chatFields } from './ChatDescription';
+import { userOperations, userFields } from './UserDescription';
+import { groupTagOperations, groupTagFields } from './GroupTagDescription';
+import { statusOperations, statusFields } from './StatusDescription';
+import { customFieldsOperations, customFieldsFields } from './CustomFieldsDescription';
+import { taskOperations, taskFields } from './TaskDescription';
+import { botOperations, botFields } from './BotDescription';
+import { fileOperations, fileFields } from './FileDescription';
+import { formOperations, formFields } from './FormDescription';
+
+function formatUserName(u: { first_name: string; last_name: string; nickname: string }): string {
+ const fullName = [u.first_name, u.last_name]
+ .filter((v) => v != null && v !== '' && v !== 'null')
+ .join(' ');
+ const display = fullName || u.nickname || 'User';
+ return u.nickname ? `${display} (@${u.nickname})` : display;
+}
+
+export class PachcaV1 implements INodeType {
+ description: INodeTypeDescription;
+
+ constructor(baseDescription: INodeTypeBaseDescription) {
+ this.description = {
+ ...baseDescription,
+ version: 1,
+ defaults: { name: 'Pachca' },
+ inputs: [NodeConnectionTypes.Main],
+ outputs: [NodeConnectionTypes.Main],
+ credentials: [{ name: 'pachcaApi', required: true }],
+ properties: [
+ {
+ displayName:
+ 'New node version available: get the latest version with added features from the nodes panel.',
+ name: 'oldVersionNotice',
+ type: 'notice',
+ default: '',
+ },
+ {
+ displayName: 'Resource',
+ name: 'resource',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ { name: 'Message', value: 'message' },
+ { name: 'Thread', value: 'thread' },
+ { name: 'Reactions', value: 'reactions' },
+ { name: 'Chat', value: 'chat' },
+ { name: 'User', value: 'user' },
+ { name: 'Group Tag', value: 'groupTag' },
+ { name: 'Status', value: 'status' },
+ { name: 'Custom Fields', value: 'customFields' },
+ { name: 'Task', value: 'task' },
+ { name: 'Bot', value: 'bot' },
+ { name: 'File', value: 'file' },
+ { name: 'Form', value: 'form' },
+ ],
+ default: 'message',
+ },
+ ...messageOperations,
+ ...messageFields,
+ ...threadOperations,
+ ...threadFields,
+ ...reactionsOperations,
+ ...reactionsFields,
+ ...chatOperations,
+ ...chatFields,
+ ...userOperations,
+ ...userFields,
+ ...groupTagOperations,
+ ...groupTagFields,
+ ...statusOperations,
+ ...statusFields,
+ ...customFieldsOperations,
+ ...customFieldsFields,
+ ...taskOperations,
+ ...taskFields,
+ ...botOperations,
+ ...botFields,
+ ...fileOperations,
+ ...fileFields,
+ ...formOperations,
+ ...formFields,
+ ],
+ };
+ }
+
+ async execute(this: IExecuteFunctions): Promise {
+ return router.call(this);
+ }
+
+ methods = {
+ listSearch: {
+ async searchChats(this: ILoadOptionsFunctions, filter?: string): Promise {
+ const credentials = await this.getCredentials('pachcaApi');
+ const url = filter
+ ? `${credentials.baseUrl}/search/chats?query=${encodeURIComponent(filter)}`
+ : `${credentials.baseUrl}/chats?per=50`;
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'pachcaApi', {
+ method: 'GET',
+ url,
+ });
+ const items = response.data ?? [];
+ return {
+ results: items.map((c: { id: number; name: string }) => ({
+ name: c.name,
+ value: c.id,
+ })),
+ };
+ },
+ async searchUsers(this: ILoadOptionsFunctions, filter?: string): Promise {
+ const credentials = await this.getCredentials('pachcaApi');
+ if (!filter) return { results: [] };
+ const url = `${credentials.baseUrl}/search/users?query=${encodeURIComponent(filter)}`;
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'pachcaApi', {
+ method: 'GET',
+ url,
+ });
+ const items = response.data ?? [];
+ return {
+ results: items.map((u: { id: number; first_name: string; last_name: string; nickname: string }) => ({
+ name: formatUserName(u),
+ value: u.id,
+ })),
+ };
+ },
+ async searchEntities(this: ILoadOptionsFunctions, filter?: string): Promise {
+ let entityType = 'discussion';
+ try {
+ entityType = (this.getNodeParameter('entityType') as string) || 'discussion';
+ } catch {
+ try {
+ entityType = (this.getCurrentNodeParameter('entityType') as string) || 'discussion';
+ } catch { /* parameter may not exist yet */ }
+ }
+ const credentials = await this.getCredentials('pachcaApi');
+ if (entityType === 'user') {
+ if (!filter) return { results: [] };
+ const url = `${credentials.baseUrl}/search/users?query=${encodeURIComponent(filter)}`;
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'pachcaApi', { method: 'GET', url });
+ const items = response.data ?? [];
+ return {
+ results: items.map((u: { id: number; first_name: string; last_name: string; nickname: string }) => ({
+ name: formatUserName(u),
+ value: u.id,
+ })),
+ };
+ }
+ if (entityType === 'thread') {
+ return { results: [] };
+ }
+ const url = filter
+ ? `${credentials.baseUrl}/search/chats?query=${encodeURIComponent(filter)}`
+ : `${credentials.baseUrl}/chats?per=50`;
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'pachcaApi', { method: 'GET', url });
+ const items = response.data ?? [];
+ return {
+ results: items.map((c: { id: number; name: string }) => ({
+ name: c.name,
+ value: c.id,
+ })),
+ };
+ },
+ },
+ loadOptions: {
+ async getCustomProperties(this: ILoadOptionsFunctions): Promise {
+ const credentials = await this.getCredentials('pachcaApi');
+ const resource = this.getNodeParameter('resource') as string;
+ const entityType = resource === 'task' ? 'Task' : 'User';
+ try {
+ const response = await this.helpers.httpRequestWithAuthentication.call(this, 'pachcaApi', {
+ method: 'GET',
+ url: `${credentials.baseUrl}/custom_properties?entity_type=${entityType}`,
+ });
+ const items = response.data ?? [];
+ return items.map((p: { id: number; name: string }) => ({
+ name: p.name,
+ value: p.id,
+ }));
+ } catch {
+ return [];
+ }
+ },
+ },
+ };
+}
diff --git a/integrations/n8n/nodes/Pachca/V1/ReactionsDescription.ts b/integrations/n8n/nodes/Pachca/V1/ReactionsDescription.ts
new file mode 100644
index 00000000..158c6f21
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V1/ReactionsDescription.ts
@@ -0,0 +1,99 @@
+// ============================================================================
+// ReactionsDescription.ts — FROZEN V1 description (from n8n-nodes-pachca@1.0.27)
+// DO NOT EDIT — this file is frozen for backward compatibility
+// ============================================================================
+
+import type { INodeProperties } from 'n8n-workflow';
+
+export const reactionsOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Add a reaction',
+ value: 'addReaction',
+ action: 'Add a reaction',
+ description: 'Add reaction to message',
+ },
+ {
+ name: 'Remove a reaction',
+ value: 'deleteReaction',
+ action: 'Remove a reaction',
+ description: 'Remove reaction from message',
+ },
+ {
+ name: 'Get message reactions',
+ value: 'getReactions',
+ action: 'Get message reactions',
+ description: 'Get list of reactions on message',
+ },
+ ],
+ default: 'addReaction',
+ displayOptions: {
+ show: {
+ resource: ['reactions'],
+ },
+ },
+ },
+];
+
+export const reactionsFields: INodeProperties[] = [
+ {
+ displayName: 'Message ID',
+ name: 'reactionsMessageId',
+ type: 'number',
+ default: '',
+ description: 'Message ID',
+ displayOptions: {
+ show: {
+ resource: ['reactions'],
+ operation: ['addReaction', 'deleteReaction', 'getReactions'],
+ },
+ },
+ },
+ {
+ displayName: 'Reaction Code',
+ name: 'reactionsReactionCode',
+ type: 'string',
+ default: '👍',
+ description: 'Reaction emoji (e.g. 👍, 🔥, ⭐)',
+ displayOptions: {
+ show: {
+ resource: ['reactions'],
+ operation: ['addReaction', 'deleteReaction'],
+ },
+ },
+ },
+ {
+ displayName: 'Additional Options',
+ name: 'reactionsOptions',
+ type: 'collection',
+ options: [
+ {
+ displayName: 'Limit',
+ name: 'reactionsPer',
+ type: 'number',
+ default: 50,
+ description: 'Items per request (API limit, 1–50). See https://dev.pachca.com/api/reactions/list',
+ },
+ {
+ displayName: 'Cursor',
+ name: 'reactionsCursor',
+ type: 'string',
+ default: '',
+ description: 'Pagination cursor from meta.paginate.next_page (optional)',
+ },
+ ],
+ default: {},
+ placeholder: 'Add option',
+ displayOptions: {
+ show: {
+ resource: ['reactions'],
+ operation: ['getReactions'],
+ },
+ },
+ },
+];
diff --git a/integrations/n8n/nodes/Pachca/V1/StatusDescription.ts b/integrations/n8n/nodes/Pachca/V1/StatusDescription.ts
new file mode 100644
index 00000000..9fa3bd0b
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V1/StatusDescription.ts
@@ -0,0 +1,89 @@
+// ============================================================================
+// StatusDescription.ts — FROZEN V1 description (from n8n-nodes-pachca@1.0.27)
+// DO NOT EDIT — this file is frozen for backward compatibility
+// ============================================================================
+
+import type { INodeProperties } from 'n8n-workflow';
+
+export const statusOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Get my profile',
+ value: 'getProfile',
+ action: 'Get my profile',
+ description: 'Get own profile info',
+ },
+ {
+ name: 'Get my status',
+ value: 'getStatus',
+ action: 'Get my status',
+ description: 'Get own status info',
+ },
+ {
+ name: 'Set my status',
+ value: 'updateStatus',
+ action: 'Set my status',
+ description: 'Set new status',
+ },
+ {
+ name: 'Clear my status',
+ value: 'deleteStatus',
+ action: 'Clear my status',
+ description: 'Delete own status',
+ },
+ ],
+ default: 'getProfile',
+ displayOptions: {
+ show: {
+ resource: ['status'],
+ },
+ },
+ },
+];
+
+export const statusFields: INodeProperties[] = [
+ {
+ displayName: 'Status Emoji',
+ name: 'statusEmoji',
+ type: 'string',
+ default: '🎮',
+ description: 'Status emoji',
+ displayOptions: {
+ show: {
+ resource: ['status'],
+ operation: ['updateStatus'],
+ },
+ },
+ },
+ {
+ displayName: 'Status Title',
+ name: 'statusTitle',
+ type: 'string',
+ default: '',
+ description: 'Status text',
+ displayOptions: {
+ show: {
+ resource: ['status'],
+ operation: ['updateStatus'],
+ },
+ },
+ },
+ {
+ displayName: 'Status Expires At',
+ name: 'statusExpiresAt',
+ type: 'dateTime',
+ default: '',
+ description: 'Status TTL (optional)',
+ displayOptions: {
+ show: {
+ resource: ['status'],
+ operation: ['updateStatus'],
+ },
+ },
+ },
+];
diff --git a/integrations/n8n/nodes/Pachca/V1/TaskDescription.ts b/integrations/n8n/nodes/Pachca/V1/TaskDescription.ts
new file mode 100644
index 00000000..d96a769a
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V1/TaskDescription.ts
@@ -0,0 +1,182 @@
+// ============================================================================
+// TaskDescription.ts — FROZEN V1 description (from n8n-nodes-pachca@1.0.27)
+// DO NOT EDIT — this file is frozen for backward compatibility
+// ============================================================================
+
+import type { INodeProperties } from 'n8n-workflow';
+
+export const taskOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Create a task',
+ value: 'create',
+ action: 'Create a task',
+ description: 'Create new reminder',
+ },
+ ],
+ default: 'create',
+ displayOptions: {
+ show: {
+ resource: ['task'],
+ },
+ },
+ },
+];
+
+export const taskFields: INodeProperties[] = [
+ {
+ displayName: 'Task Kind',
+ name: 'taskKind',
+ type: 'options',
+ options: [
+ {
+ name: 'Call',
+ value: 'call',
+ description: 'Call contact',
+ },
+ {
+ name: 'Meeting',
+ value: 'meeting',
+ description: 'Meeting',
+ },
+ {
+ name: 'Reminder',
+ value: 'reminder',
+ description: 'Simple reminder',
+ },
+ {
+ name: 'Event',
+ value: 'event',
+ description: 'Event',
+ },
+ {
+ name: 'Email',
+ value: 'email',
+ description: 'Send email',
+ },
+ ],
+ default: 'reminder',
+ description: 'Reminder type',
+ displayOptions: {
+ show: {
+ resource: ['task'],
+ operation: ['create'],
+ },
+ },
+ },
+ {
+ displayName: 'Content',
+ name: 'taskContent',
+ type: 'string',
+ typeOptions: {
+ rows: 3,
+ },
+ default: '',
+ description: 'Reminder description (uses type name if not set)',
+ displayOptions: {
+ show: {
+ resource: ['task'],
+ operation: ['create'],
+ },
+ },
+ },
+ {
+ displayName: 'Due At',
+ name: 'taskDueAt',
+ type: 'dateTime',
+ default: '',
+ description: 'Reminder due date (ISO-8601 format)',
+ displayOptions: {
+ show: {
+ resource: ['task'],
+ operation: ['create'],
+ },
+ },
+ },
+ {
+ displayName: 'Priority',
+ name: 'taskPriority',
+ type: 'options',
+ options: [
+ {
+ name: 'Normal',
+ value: 1,
+ description: 'Normal priority',
+ },
+ {
+ name: 'Important',
+ value: 2,
+ description: 'Important',
+ },
+ {
+ name: 'Very Important',
+ value: 3,
+ description: 'Very important',
+ },
+ ],
+ default: 1,
+ description: 'Reminder priority',
+ displayOptions: {
+ show: {
+ resource: ['task'],
+ operation: ['create'],
+ },
+ },
+ },
+ {
+ displayName: 'Performer IDs',
+ name: 'performerIds',
+ type: 'string',
+ default: '',
+ description: 'Comma-separated responsible user IDs (if empty, you are set as responsible)',
+ displayOptions: {
+ show: {
+ resource: ['task'],
+ operation: ['create'],
+ },
+ },
+ },
+ {
+ displayName: 'Custom Properties',
+ name: 'customProperties',
+ type: 'fixedCollection',
+ typeOptions: {
+ multipleValues: true,
+ },
+ options: [
+ {
+ name: 'property',
+ displayName: 'Property',
+ values: [
+ {
+ displayName: 'Field ID',
+ name: 'id',
+ type: 'number',
+ default: 0,
+ description: 'Custom field ID',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: '',
+ description: 'Field value',
+ },
+ ],
+ },
+ ],
+ default: [],
+ description: 'Reminder custom fields',
+ displayOptions: {
+ show: {
+ resource: ['task'],
+ operation: ['create'],
+ },
+ },
+ },
+];
diff --git a/integrations/n8n/nodes/Pachca/V1/ThreadDescription.ts b/integrations/n8n/nodes/Pachca/V1/ThreadDescription.ts
new file mode 100644
index 00000000..b3266841
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V1/ThreadDescription.ts
@@ -0,0 +1,64 @@
+// ============================================================================
+// ThreadDescription.ts — FROZEN V1 description (from n8n-nodes-pachca@1.0.27)
+// DO NOT EDIT — this file is frozen for backward compatibility
+// ============================================================================
+
+import type { INodeProperties } from 'n8n-workflow';
+
+export const threadOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Create a thread',
+ value: 'createThread',
+ action: 'Create a thread',
+ description: 'Create thread to message',
+ },
+ {
+ name: 'Get a thread',
+ value: 'getThread',
+ action: 'Get a thread',
+ description: 'Get thread info',
+ },
+ ],
+ default: 'createThread',
+ displayOptions: {
+ show: {
+ resource: ['thread'],
+ },
+ },
+ },
+];
+
+export const threadFields: INodeProperties[] = [
+ {
+ displayName: 'Message ID',
+ name: 'threadMessageId',
+ type: 'number',
+ default: '',
+ description: 'Message ID for creating thread',
+ displayOptions: {
+ show: {
+ resource: ['thread'],
+ operation: ['createThread'],
+ },
+ },
+ },
+ {
+ displayName: 'Thread ID',
+ name: 'threadThreadId',
+ type: 'number',
+ default: '',
+ description: 'Thread ID to get info for',
+ displayOptions: {
+ show: {
+ resource: ['thread'],
+ operation: ['getThread'],
+ },
+ },
+ },
+];
diff --git a/integrations/n8n/nodes/Pachca/V1/UserDescription.ts b/integrations/n8n/nodes/Pachca/V1/UserDescription.ts
new file mode 100644
index 00000000..84111bc4
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V1/UserDescription.ts
@@ -0,0 +1,367 @@
+// ============================================================================
+// UserDescription.ts — FROZEN V1 description (from n8n-nodes-pachca@1.0.27)
+// DO NOT EDIT — this file is frozen for backward compatibility
+// ============================================================================
+
+import type { INodeProperties } from 'n8n-workflow';
+
+export const userOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Get all users',
+ value: 'getAll',
+ action: 'Get all users',
+ description: 'Get list of all users',
+ },
+ {
+ name: 'Get a user',
+ value: 'getById',
+ action: 'Get a user',
+ description: 'Get user by ID',
+ },
+ {
+ name: 'Create a user',
+ value: 'create',
+ action: 'Create a user',
+ description: 'Create new user (Admin/Owner tokens only)',
+ },
+ {
+ name: 'Update a user',
+ value: 'update',
+ action: 'Update a user',
+ description: 'Update user (Admin/Owner tokens only)',
+ },
+ {
+ name: 'Delete a user',
+ value: 'delete',
+ action: 'Delete a user',
+ description: 'Delete user (Admin/Owner tokens only)',
+ },
+ ],
+ default: 'getAll',
+ displayOptions: {
+ show: {
+ resource: ['user'],
+ },
+ },
+ },
+];
+
+export const userFields: INodeProperties[] = [
+ {
+ displayName: 'Get All Users (No Limit)',
+ name: 'getAllUsersNoLimit',
+ type: 'boolean',
+ default: false,
+ description: 'Get all users with full pagination (ignores per/page)',
+ displayOptions: {
+ show: {
+ resource: ['user'],
+ operation: ['getAll'],
+ },
+ },
+ },
+ {
+ displayName: 'Additional Options',
+ name: 'additionalOptions',
+ type: 'collection',
+ options: [
+ {
+ displayName: 'Per Page',
+ name: 'per',
+ type: 'number',
+ default: 25,
+ description: 'Items per page (max 50)',
+ },
+ {
+ displayName: 'Page',
+ name: 'page',
+ type: 'number',
+ default: 1,
+ description: 'Page number',
+ },
+ {
+ displayName: 'Query',
+ name: 'query',
+ type: 'string',
+ default: '',
+ description: 'Search phrase to filter users',
+ },
+ ],
+ default: {},
+ placeholder: 'Add option',
+ displayOptions: {
+ show: {
+ resource: ['user'],
+ operation: ['getAll'],
+ getAllUsersNoLimit: [false],
+ },
+ },
+ },
+ {
+ displayName: 'User ID',
+ name: 'userId',
+ type: 'number',
+ default: 1,
+ description: 'User ID',
+ displayOptions: {
+ show: {
+ resource: ['user'],
+ operation: ['getById', 'update', 'delete'],
+ },
+ },
+ },
+ {
+ displayName: 'Email',
+ name: 'email',
+ type: 'string',
+ default: '',
+ description: 'User email',
+ displayOptions: {
+ show: {
+ resource: ['user'],
+ operation: ['create', 'update'],
+ },
+ },
+ },
+ {
+ displayName: 'First Name',
+ name: 'firstName',
+ type: 'string',
+ default: '',
+ description: 'User first name',
+ displayOptions: {
+ show: {
+ resource: ['user'],
+ operation: ['create', 'update'],
+ },
+ },
+ },
+ {
+ displayName: 'Last Name',
+ name: 'lastName',
+ type: 'string',
+ default: '',
+ description: 'User last name',
+ displayOptions: {
+ show: {
+ resource: ['user'],
+ operation: ['create', 'update'],
+ },
+ },
+ },
+ {
+ displayName: 'Additional Options',
+ name: 'filterOptions',
+ type: 'collection',
+ options: [
+ {
+ displayName: 'Filter Role',
+ name: 'filterRole',
+ type: 'multiOptions',
+ options: [
+ {
+ name: 'Admin',
+ value: 'admin',
+ },
+ {
+ name: 'User',
+ value: 'user',
+ },
+ {
+ name: 'Multi Guest',
+ value: 'multi_guest',
+ },
+ ],
+ default: [],
+ description: 'Filter by user roles (if not set - all roles)',
+ },
+ {
+ displayName: 'Filter Bot',
+ name: 'filterBot',
+ type: 'options',
+ options: [
+ {
+ name: 'All',
+ value: 'all',
+ },
+ {
+ name: 'Users Only',
+ value: 'users',
+ },
+ {
+ name: 'Bots Only',
+ value: 'bots',
+ },
+ ],
+ default: 'all',
+ description: 'Filter by type: users or bots',
+ },
+ {
+ displayName: 'Filter Suspended',
+ name: 'filterSuspended',
+ type: 'options',
+ options: [
+ {
+ name: 'All',
+ value: 'all',
+ },
+ {
+ name: 'Active Only (suspended=false)',
+ value: 'active',
+ },
+ {
+ name: 'Suspended Only (suspended=true)',
+ value: 'suspended',
+ },
+ ],
+ default: 'all',
+ description: 'Filter by block status',
+ },
+ {
+ displayName: 'Filter Invite Status',
+ name: 'filterInviteStatus',
+ type: 'multiOptions',
+ options: [
+ {
+ name: 'Confirmed',
+ value: 'confirmed',
+ },
+ {
+ name: 'Sent',
+ value: 'sent',
+ },
+ ],
+ default: [],
+ description: 'Filter by invitation status (if not set - all statuses)',
+ },
+ ],
+ default: {},
+ placeholder: 'Add option',
+ displayOptions: {
+ show: {
+ resource: ['user'],
+ operation: ['getAll'],
+ getAllUsersNoLimit: [true],
+ },
+ },
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ options: [
+ {
+ displayName: 'Nickname',
+ name: 'nickname',
+ type: 'string',
+ default: '',
+ description: 'User nickname',
+ },
+ {
+ displayName: 'Phone Number',
+ name: 'phoneNumber',
+ type: 'string',
+ default: '',
+ description: 'Phone number',
+ },
+ {
+ displayName: 'Department',
+ name: 'department',
+ type: 'string',
+ default: '',
+ description: 'Department',
+ },
+ {
+ displayName: 'Title',
+ name: 'title',
+ type: 'string',
+ default: '',
+ description: 'Job title',
+ },
+ {
+ displayName: 'Role',
+ name: 'role',
+ type: 'options',
+ options: [
+ {
+ name: 'Admin',
+ value: 'admin',
+ description: 'Administrator',
+ },
+ {
+ name: 'User',
+ value: 'user',
+ description: 'Employee',
+ },
+ {
+ name: 'Multi Guest',
+ value: 'multi_guest',
+ description: 'Multi-guest',
+ },
+ ],
+ default: 'user',
+ description: 'Access level',
+ },
+ {
+ displayName: 'Suspended',
+ name: 'suspended',
+ type: 'boolean',
+ default: false,
+ description: 'User deactivation',
+ },
+ {
+ displayName: 'List Tags',
+ name: 'listTags',
+ type: 'string',
+ default: '',
+ description: 'User tags (comma-separated)',
+ },
+ {
+ displayName: 'Custom Properties',
+ name: 'customProperties',
+ type: 'fixedCollection',
+ typeOptions: {
+ multipleValues: true,
+ },
+ default: {},
+ options: [
+ {
+ name: 'property',
+ displayName: 'Property',
+ values: [
+ {
+ displayName: 'Field ID',
+ name: 'id',
+ type: 'number',
+ default: 0,
+ description: 'Custom field identifier',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: '',
+ description: 'Field value',
+ },
+ ],
+ },
+ ],
+ description: 'User custom fields',
+ },
+ ],
+ default: {},
+ placeholder: 'Add field',
+ displayOptions: {
+ show: {
+ resource: ['user'],
+ operation: ['create', 'update'],
+ },
+ },
+ },
+];
diff --git a/integrations/n8n/nodes/Pachca/V2/BotDescription.ts b/integrations/n8n/nodes/Pachca/V2/BotDescription.ts
new file mode 100644
index 00000000..c54528c9
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V2/BotDescription.ts
@@ -0,0 +1,85 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const botOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: { show: { resource: ['bot'] } },
+ options: [
+ {
+ name: 'Get Many Events',
+ value: 'getAllEvents',
+ action: 'Get many bot events',
+ },
+ {
+ name: 'Remove Events',
+ value: 'removeEvents',
+ action: 'Remove events from bot',
+ },
+ {
+ name: 'Update',
+ value: 'update',
+ action: 'Update a bot',
+ },
+ ],
+ default: 'update',
+ },
+];
+
+export const botFields: INodeProperties[] = [
+ {
+ displayName: 'ID',
+ name: 'botId',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['bot'], operation: ['update'] } },
+ description: 'Bot ID',
+ },
+ {
+ displayName: 'Webhook URL',
+ name: 'webhookUrl',
+ type: 'string',
+ required: true,
+ default: '',
+ placeholder: 'https://example.com/webhook',
+ description: 'URL for the outgoing webhook',
+ displayOptions: { show: { resource: ['bot'], operation: ['update'] } },
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ displayOptions: { show: { resource: ['bot'], operation: ['getAllEvents'] } },
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 50,
+ description: 'Max number of results to return',
+ typeOptions: { minValue: 1, maxValue: 50 },
+ displayOptions: { show: { resource: ['bot'], operation: ['getAllEvents'], returnAll: [false] } },
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['bot'], operation: ['getAllEvents'] } },
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['bot'], operation: ['removeEvents'] } },
+ description: 'Event ID',
+ },
+];
\ No newline at end of file
diff --git a/integrations/n8n/nodes/Pachca/V2/ChatDescription.ts b/integrations/n8n/nodes/Pachca/V2/ChatDescription.ts
new file mode 100644
index 00000000..c48fe7ae
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V2/ChatDescription.ts
@@ -0,0 +1,353 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const chatOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: { show: { resource: ['chat'] } },
+ options: [
+ {
+ name: 'Archive',
+ value: 'archive',
+ action: 'Archive a chat',
+ },
+ {
+ name: 'Create',
+ value: 'create',
+ action: 'Create a chat',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ action: 'Get a chat',
+ },
+ {
+ name: 'Get Many',
+ value: 'getAll',
+ action: 'Get many chats',
+ },
+ {
+ name: 'Unarchive',
+ value: 'unarchive',
+ action: 'Unarchive a chat',
+ },
+ {
+ name: 'Update',
+ value: 'update',
+ action: 'Update a chat',
+ },
+ ],
+ default: 'getAll',
+ },
+];
+
+export const chatFields: INodeProperties[] = [
+ {
+ displayName: 'Name',
+ name: 'chatName',
+ type: 'string',
+ required: true,
+ default: "",
+ displayOptions: { show: { resource: ['chat'], operation: ['create'] } },
+ placeholder: '🤿 aqua',
+ routing: { send: { type: 'body', property: 'name' } },
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: { show: { resource: ['chat'], operation: ['create'] } },
+ options: [
+ {
+ displayName: 'Channel',
+ name: 'channel',
+ type: 'boolean',
+ default: false,
+ description: 'Whether this is a channel',
+ routing: { send: { type: 'body', property: 'channel' } },
+ },
+ {
+ displayName: 'Group Tag IDs',
+ name: 'groupTagIds',
+ type: 'string',
+ default: "",
+ description: 'Array of tag IDs to be added as members',
+ placeholder: '86,18',
+ },
+ {
+ displayName: 'Member IDs',
+ name: 'memberIds',
+ type: 'string',
+ default: "",
+ description: 'Array of user IDs who will become members',
+ placeholder: '186,187',
+ },
+ {
+ displayName: 'Public',
+ name: 'public',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to publicly accessible',
+ routing: { send: { type: 'body', property: 'public' } },
+ },
+ ],
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ displayOptions: { show: { resource: ['chat'], operation: ['getAll'] } },
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 50,
+ description: 'Max number of results to return',
+ typeOptions: { minValue: 1, maxValue: 50 },
+ displayOptions: { show: { resource: ['chat'], operation: ['getAll'], returnAll: [false] } },
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['chat'], operation: ['getAll'] } },
+ },
+ {
+ displayName: 'Sort',
+ name: 'sort',
+ type: 'options',
+ options: [{ name: 'ID', value: 'id', description: 'By chat ID' },
+{ name: 'Last Message At', value: 'last_message_at', description: 'By last message date and time' }],
+ default: "id",
+ description: 'Sort field',
+ displayOptions: { show: { resource: ['chat'], operation: ['getAll'] } },
+ routing: { send: { type: 'query', property: 'sort' } },
+ },
+ {
+ displayName: 'Order',
+ name: 'order',
+ type: 'options',
+ options: [{ name: 'Asc', value: 'asc', description: 'Ascending' },
+{ name: 'Desc', value: 'desc', description: 'Descending' }],
+ default: "desc",
+ description: 'Sort direction',
+ displayOptions: { show: { resource: ['chat'], operation: ['getAll'] } },
+ routing: { send: { type: 'query', property: 'order' } },
+ },
+ {
+ displayName: 'Availability',
+ name: 'availability',
+ type: 'options',
+ options: [{ name: 'Is Member', value: 'is_member', description: 'Chats where the user is a member' },
+{ name: 'Public', value: 'public', description: 'All public chats in the workspace, regardless of user membership' }],
+ default: "is_member",
+ description: 'Parameter that controls chat availability and filtering for the user',
+ displayOptions: { show: { resource: ['chat'], operation: ['getAll'] } },
+ routing: { send: { type: 'query', property: 'availability' } },
+ },
+ {
+ displayName: 'Last Message At After',
+ name: 'lastMessageAtAfter',
+ type: 'dateTime',
+ default: "",
+ description: 'Filter by last message creation time. Returns chats where the last message was created no earlier than the specified time (in YYYY-MM-DDThh:mm:ss.sssZ format).',
+ placeholder: '2025-01-01T00:00:00.000Z',
+ displayOptions: { show: { resource: ['chat'], operation: ['getAll'] } },
+ routing: { send: { type: 'query', property: 'last_message_at_after' } },
+ },
+ {
+ displayName: 'Last Message At Before',
+ name: 'lastMessageAtBefore',
+ type: 'dateTime',
+ default: "",
+ description: 'Filter by last message creation time. Returns chats where the last message was created no later than the specified time (in YYYY-MM-DDThh:mm:ss.sssZ format).',
+ placeholder: '2025-02-01T00:00:00.000Z',
+ displayOptions: { show: { resource: ['chat'], operation: ['getAll'] } },
+ routing: { send: { type: 'query', property: 'last_message_at_before' } },
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: { show: { resource: ['chat'], operation: ['getAll'] } },
+ options: [
+ {
+ displayName: 'Personal',
+ name: 'personal',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to filter by direct and group chats. If not specified, all chats are returned.',
+ routing: { send: { type: 'query', property: 'personal' } },
+ },
+ ],
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ description: 'Chat ID',
+ modes: [
+ {
+ displayName: 'From List',
+ name: 'list',
+ type: 'list',
+ typeOptions: { searchListMethod: 'searchChats', searchable: true },
+ },
+ {
+ displayName: 'By ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 334',
+ },
+ {
+ displayName: 'By URL',
+ name: 'url',
+ type: 'string',
+ placeholder: 'https://app.pachca.com/chats/12345',
+ extractValue: { type: 'regex', regex: 'https?://[^/]+/chats/(\\d+)' },
+ validation: [{ type: 'regex', properties: { regex: 'https?://[^/]+/chats/(\\d+)', errorMessage: 'Not a valid Pachca chat URL' } }],
+ },
+ ],
+ displayOptions: { show: { resource: ['chat'], operation: ['get'] } },
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['chat'], operation: ['get'] } },
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ description: 'Chat ID',
+ modes: [
+ {
+ displayName: 'From List',
+ name: 'list',
+ type: 'list',
+ typeOptions: { searchListMethod: 'searchChats', searchable: true },
+ },
+ {
+ displayName: 'By ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 334',
+ },
+ {
+ displayName: 'By URL',
+ name: 'url',
+ type: 'string',
+ placeholder: 'https://app.pachca.com/chats/12345',
+ extractValue: { type: 'regex', regex: 'https?://[^/]+/chats/(\\d+)' },
+ validation: [{ type: 'regex', properties: { regex: 'https?://[^/]+/chats/(\\d+)', errorMessage: 'Not a valid Pachca chat URL' } }],
+ },
+ ],
+ displayOptions: { show: { resource: ['chat'], operation: ['update'] } },
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: { show: { resource: ['chat'], operation: ['update'] } },
+ options: [
+ {
+ displayName: 'Name',
+ name: 'chatName',
+ type: 'string',
+ default: "",
+ placeholder: 'Pool',
+ routing: { send: { type: 'body', property: 'name' } },
+ },
+ {
+ displayName: 'Public',
+ name: 'public',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to publicly accessible',
+ routing: { send: { type: 'body', property: 'public' } },
+ },
+ ],
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ description: 'Chat ID',
+ modes: [
+ {
+ displayName: 'From List',
+ name: 'list',
+ type: 'list',
+ typeOptions: { searchListMethod: 'searchChats', searchable: true },
+ },
+ {
+ displayName: 'By ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 334',
+ },
+ {
+ displayName: 'By URL',
+ name: 'url',
+ type: 'string',
+ placeholder: 'https://app.pachca.com/chats/12345',
+ extractValue: { type: 'regex', regex: 'https?://[^/]+/chats/(\\d+)' },
+ validation: [{ type: 'regex', properties: { regex: 'https?://[^/]+/chats/(\\d+)', errorMessage: 'Not a valid Pachca chat URL' } }],
+ },
+ ],
+ displayOptions: { show: { resource: ['chat'], operation: ['archive'] } },
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ description: 'Chat ID',
+ modes: [
+ {
+ displayName: 'From List',
+ name: 'list',
+ type: 'list',
+ typeOptions: { searchListMethod: 'searchChats', searchable: true },
+ },
+ {
+ displayName: 'By ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 334',
+ },
+ {
+ displayName: 'By URL',
+ name: 'url',
+ type: 'string',
+ placeholder: 'https://app.pachca.com/chats/12345',
+ extractValue: { type: 'regex', regex: 'https?://[^/]+/chats/(\\d+)' },
+ validation: [{ type: 'regex', properties: { regex: 'https?://[^/]+/chats/(\\d+)', errorMessage: 'Not a valid Pachca chat URL' } }],
+ },
+ ],
+ displayOptions: { show: { resource: ['chat'], operation: ['unarchive'] } },
+ },
+];
\ No newline at end of file
diff --git a/integrations/n8n/nodes/Pachca/V2/CustomPropertyDescription.ts b/integrations/n8n/nodes/Pachca/V2/CustomPropertyDescription.ts
new file mode 100644
index 00000000..345fba18
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V2/CustomPropertyDescription.ts
@@ -0,0 +1,41 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const customPropertyOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: { show: { resource: ['customProperty'] } },
+ options: [
+ {
+ name: 'Get',
+ value: 'get',
+ action: 'Get a custom property',
+ },
+ ],
+ default: 'get',
+ },
+];
+
+export const customPropertyFields: INodeProperties[] = [
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['customProperty'], operation: ['get'] } },
+ },
+ {
+ displayName: 'Entity Type',
+ name: 'entityType',
+ type: 'options',
+ required: true,
+ options: [{ name: 'Task', value: 'Task' },
+{ name: 'User', value: 'User' }],
+ default: "",
+ displayOptions: { show: { resource: ['customProperty'], operation: ['get'] } },
+ routing: { send: { type: 'query', property: 'entity_type' } },
+ },
+];
\ No newline at end of file
diff --git a/integrations/n8n/nodes/Pachca/V2/ExportDescription.ts b/integrations/n8n/nodes/Pachca/V2/ExportDescription.ts
new file mode 100644
index 00000000..9b03c96f
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V2/ExportDescription.ts
@@ -0,0 +1,110 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const exportOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: { show: { resource: ['export'] } },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ action: 'Create a chat export',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ action: 'Get a chat export',
+ },
+ ],
+ default: 'create',
+ },
+];
+
+export const exportFields: INodeProperties[] = [
+ {
+ displayName: 'Requires owner role and the "Corporation" plan',
+ name: 'exportCreateNotice',
+ type: 'notice',
+ default: '',
+ displayOptions: { show: { resource: ['export'], operation: ['create'] } },
+ },
+ {
+ displayName: 'Start At',
+ name: 'startAt',
+ type: 'string',
+ required: true,
+ default: "",
+ description: 'Export start date (ISO-8601, UTC+0) in YYYY-MM-DD format',
+ displayOptions: { show: { resource: ['export'], operation: ['create'] } },
+ placeholder: '2025-03-20',
+ routing: { send: { type: 'body', property: 'start_at' } },
+ },
+ {
+ displayName: 'End At',
+ name: 'endAt',
+ type: 'string',
+ required: true,
+ default: "",
+ description: 'Export end date (ISO-8601, UTC+0) in YYYY-MM-DD format',
+ displayOptions: { show: { resource: ['export'], operation: ['create'] } },
+ placeholder: '2025-03-20',
+ routing: { send: { type: 'body', property: 'end_at' } },
+ },
+ {
+ displayName: 'Webhook URL',
+ name: 'webhookUrl',
+ type: 'string',
+ required: true,
+ default: "",
+ description: 'URL to receive a webhook when the export is complete',
+ hint: 'Set this to a Webhook node URL in another workflow to receive the export-ready notification',
+ displayOptions: { show: { resource: ['export'], operation: ['create'] } },
+ placeholder: 'https://webhook.site/9227d3b8-6e82-4e64-bf5d-ad972ad270f2',
+ routing: { send: { type: 'body', property: 'webhook_url' } },
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: { show: { resource: ['export'], operation: ['create'] } },
+ options: [
+ {
+ displayName: 'Chat IDs',
+ name: 'chatIds',
+ type: 'string',
+ default: "",
+ description: 'Array of chat IDs. Specify to export messages from specific chats only.',
+ placeholder: '1381521',
+ },
+ {
+ displayName: 'Skip Chats File',
+ name: 'skipChatsFile',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to skip generating the chat list file (chats.JSON)',
+ routing: { send: { type: 'body', property: 'skip_chats_file' } },
+ },
+ ],
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['export'], operation: ['get'] } },
+ description: 'Export ID',
+ },
+ {
+ displayName: 'Requires owner role and the "Corporation" plan',
+ name: 'exportGetNotice',
+ type: 'notice',
+ default: '',
+ displayOptions: { show: { resource: ['export'], operation: ['get'] } },
+ },
+];
\ No newline at end of file
diff --git a/integrations/n8n/nodes/Pachca/V2/FileDescription.ts b/integrations/n8n/nodes/Pachca/V2/FileDescription.ts
new file mode 100644
index 00000000..ab01269a
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V2/FileDescription.ts
@@ -0,0 +1,76 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const fileOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: { show: { resource: ['file'] } },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ action: 'Create a file',
+ },
+ ],
+ default: 'create',
+ },
+];
+
+export const fileFields: INodeProperties[] = [
+ {
+ displayName: 'File Source',
+ name: 'fileSource',
+ type: 'options',
+ options: [
+ { name: 'Binary Data', value: 'binary' },
+ { name: 'URL', value: 'url' },
+ ],
+ default: 'binary',
+ description: 'Where to get the file to upload',
+ displayOptions: { show: { resource: ['file'], operation: ['create'] } },
+ },
+ {
+ displayName: 'File URL',
+ name: 'fileUrl',
+ type: 'string',
+ required: true,
+ default: '',
+ description: 'URL of the file to upload',
+ displayOptions: { show: { resource: ['file'], operation: ['create'], fileSource: ['url'] } },
+ },
+ {
+ displayName: 'Input Binary Field',
+ name: 'binaryProperty',
+ type: 'string',
+ required: true,
+ default: 'data',
+ hint: 'The name of the input binary field containing the file to be uploaded',
+ displayOptions: { show: { resource: ['file'], operation: ['create'], fileSource: ['binary'] } },
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: { show: { resource: ['file'], operation: ['create'] } },
+ options: [
+ {
+ displayName: 'Content Type',
+ name: 'contentType',
+ type: 'string',
+ default: '',
+ description: 'MIME type of the file (e.g. image/png). If not set, auto-detected from file extension.',
+ },
+ {
+ displayName: 'File Name',
+ name: 'fileName',
+ type: 'string',
+ default: '',
+ description: 'Name of the file. If not set, auto-detected from source.',
+ },
+ ],
+ },
+];
\ No newline at end of file
diff --git a/integrations/n8n/nodes/Pachca/V2/FormDescription.ts b/integrations/n8n/nodes/Pachca/V2/FormDescription.ts
new file mode 100644
index 00000000..dcf1f85f
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V2/FormDescription.ts
@@ -0,0 +1,310 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const formOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: { show: { resource: ['form'] } },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ action: 'Create a form',
+ },
+ ],
+ default: 'create',
+ },
+];
+
+export const formFields: INodeProperties[] = [
+ {
+ displayName: 'Title',
+ name: 'formTitle',
+ type: 'string',
+ required: true,
+ default: "",
+ description: 'View title',
+ displayOptions: { show: { resource: ['form'], operation: ['create'] } },
+ placeholder: 'Vacation notification',
+ routing: { send: { type: 'body', property: 'title' } },
+ },
+ {
+ displayName: 'Type',
+ name: 'type',
+ type: 'options',
+ required: true,
+ options: [{ name: 'Modal', value: 'modal', description: 'Modal window' }],
+ default: "modal",
+ description: 'View opening method',
+ displayOptions: { show: { resource: ['form'], operation: ['create'] } },
+ routing: { send: { type: 'body', property: 'type' } },
+ },
+ {
+ displayName: 'Trigger ID',
+ name: 'triggerId',
+ type: 'string',
+ required: true,
+ default: "",
+ description: 'Unique event identifier (received, for example, in the outgoing webhook for a button click)',
+ hint: 'Trigger ID from a button press webhook event — expires in 3 seconds',
+ displayOptions: { show: { resource: ['form'], operation: ['create'] } },
+ placeholder: '791a056b-006c-49dd-834b-c633fde52fe8',
+ routing: { send: { type: 'body', property: 'trigger_id' } },
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: { show: { resource: ['form'], operation: ['create'] } },
+ options: [
+ {
+ displayName: 'Callback ID',
+ name: 'callbackId',
+ type: 'string',
+ default: "",
+ description: 'Optional identifier for recognizing this view, which will be sent to your application when the user submits the form. Use this field, for example, to determine which form the user was supposed to fill out.',
+ placeholder: 'timeoff_reguest_form',
+ routing: { send: { type: 'body', property: 'callback_id' } },
+ },
+ {
+ displayName: 'Close Text',
+ name: 'closeText',
+ type: 'string',
+ default: "Cancel",
+ description: 'Close button text',
+ placeholder: 'Close',
+ routing: { send: { type: 'body', property: 'close_text' } },
+ },
+ {
+ displayName: 'Private Metadata',
+ name: 'privateMetadata',
+ type: 'string',
+ default: "",
+ description: 'Optional string that will be sent to your application when the user submits the form. Use this field, for example, to pass additional information in `JSON` format along with the form data.',
+ placeholder: '{"timeoff_id":4378}',
+ routing: { send: { type: 'body', property: 'private_metadata' } },
+ },
+ {
+ displayName: 'Submit Text',
+ name: 'submitText',
+ type: 'string',
+ default: "Submit",
+ description: 'Submit button text',
+ placeholder: 'Submit request',
+ routing: { send: { type: 'body', property: 'submit_text' } },
+ },
+ ],
+ },
+ {
+ displayName: 'Builder Mode',
+ name: 'formBuilderMode',
+ type: 'options',
+ options: [
+ { name: 'Visual Builder', value: 'builder' },
+ { name: 'JSON', value: 'json' },
+ ],
+ default: 'builder',
+ description: 'Build form visually or paste JSON',
+ displayOptions: { show: { resource: ['form'], operation: ['create'] } },
+ },
+ {
+ displayName: 'Form Blocks',
+ name: 'formBlocks',
+ type: 'fixedCollection',
+ typeOptions: { multipleValues: true, sortable: true },
+ default: {},
+ description: 'Add form blocks using the visual builder',
+ displayOptions: { show: { resource: ['form'], operation: ['create'], formBuilderMode: ['builder'] } },
+ options: [{
+ name: 'block',
+ displayName: 'Block',
+ values: [
+ {
+ displayName: 'Allowed File Types',
+ name: 'filetypes',
+ type: 'string',
+ default: '',
+ placeholder: 'png,jpg,pdf',
+ description: 'Comma-separated list of allowed file extensions',
+ displayOptions: { show: { type: ['file_input'] } },
+ },
+ {
+ displayName: 'Field Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ description: 'Unique field identifier (used in form submission data)',
+ displayOptions: { show: { type: ['input', 'select', 'radio', 'checkbox', 'date', 'time', 'file_input'] } },
+ },
+ {
+ displayName: 'Hint',
+ name: 'hint',
+ type: 'string',
+ default: '',
+ displayOptions: { show: { type: ['input', 'select', 'radio', 'checkbox', 'date', 'time', 'file_input'] } },
+ },
+ {
+ displayName: 'Initial Date',
+ name: 'initial_date',
+ type: 'string',
+ default: '',
+ placeholder: '2024-01-01',
+ displayOptions: { show: { type: ['date'] } },
+ },
+ {
+ displayName: 'Initial Time',
+ name: 'initial_time',
+ type: 'string',
+ default: '',
+ placeholder: '09:00',
+ displayOptions: { show: { type: ['time'] } },
+ },
+ {
+ displayName: 'Initial Value',
+ name: 'initial_value',
+ type: 'string',
+ default: '',
+ displayOptions: { show: { type: ['input'] } },
+ },
+ {
+ displayName: 'Label',
+ name: 'label',
+ type: 'string',
+ default: '',
+ displayOptions: { show: { type: ['input', 'select', 'radio', 'checkbox', 'date', 'time', 'file_input'] } },
+ },
+ {
+ displayName: 'Max Files',
+ name: 'max_files',
+ type: 'number',
+ default: 10,
+ displayOptions: { show: { type: ['file_input'] } },
+ },
+ {
+ displayName: 'Max Length',
+ name: 'max_length',
+ type: 'number',
+ default: 0,
+ displayOptions: { show: { type: ['input'] } },
+ },
+ {
+ displayName: 'Min Length',
+ name: 'min_length',
+ type: 'number',
+ default: 0,
+ displayOptions: { show: { type: ['input'] } },
+ },
+ {
+ displayName: 'Multiline',
+ name: 'multiline',
+ type: 'boolean',
+ default: false,
+ displayOptions: { show: { type: ['input'] } },
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'fixedCollection',
+ typeOptions: { multipleValues: true },
+ default: {},
+ displayOptions: { show: { type: ['select', 'radio', 'checkbox'] } },
+ options: [{
+ name: 'option',
+ displayName: 'Option',
+ values: [
+ { displayName: 'Checked by Default', name: 'checked', type: 'boolean', default: false },
+ { displayName: 'Description', name: 'description', type: 'string', default: '' },
+ { displayName: 'Selected by Default', name: 'selected', type: 'boolean', default: false },
+ { displayName: 'Text', name: 'text', type: 'string', default: '' },
+ { displayName: 'Value', name: 'value', type: 'string', default: '' },
+ ],
+ }],
+ },
+ {
+ displayName: 'Placeholder',
+ name: 'placeholder',
+ type: 'string',
+ default: '',
+ displayOptions: { show: { type: ['input'] } },
+ },
+ {
+ displayName: 'Required',
+ name: 'required',
+ type: 'boolean',
+ default: false,
+ displayOptions: { show: { type: ['input', 'select', 'radio', 'checkbox', 'date', 'time', 'file_input'] } },
+ },
+ {
+ displayName: 'Text',
+ name: 'text',
+ type: 'string',
+ default: '',
+ displayOptions: { show: { type: ['header', 'plain_text', 'markdown'] } },
+ },
+ {
+ displayName: 'Type',
+ name: 'type',
+ type: 'options',
+ options: [
+ { name: '☑️ Checkboxes', value: 'checkbox' },
+ { name: '➖ Divider', value: 'divider' },
+ { name: '📄 Plain Text', value: 'plain_text' },
+ { name: '📅 Date Picker', value: 'date' },
+ { name: '📋 Select Dropdown', value: 'select' },
+ { name: '📎 File Upload', value: 'file_input' },
+ { name: '📝 Header', value: 'header' },
+ { name: '📝 Markdown', value: 'markdown' },
+ { name: '📝 Text Input', value: 'input' },
+ { name: '🔘 Radio Buttons', value: 'radio' },
+ { name: '🕐 Time Picker', value: 'time' },
+ ],
+ default: 'input',
+ },
+ ],
+ }],
+ },
+ {
+ displayName: 'Blocks (JSON)',
+ name: 'formBlocks',
+ type: 'json',
+ required: true,
+ default: '[]',
+ description: 'Paste an array of blocks or the full form JSON from the visual form builder',
+ hint: 'Build your form visually at dev.pachca.com/guides/forms/overview, then paste the JSON here',
+ placeholder: '{"title":"My form","blocks":[{"type":"input","name":"field_1","label":"Your name"}]}',
+ displayOptions: { show: { resource: ['form'], operation: ['create'], formBuilderMode: ['json'] } },
+ },
+ {
+ displayName: 'This operation is deprecated. In v2, use the Pachca Trigger node to receive form submissions via webhook, then process the data with standard n8n nodes.',
+ name: 'processSubmissionNotice',
+ type: 'notice',
+ default: '',
+ displayOptions: { show: { resource: ['form'], operation: ['processSubmission'] } },
+ },
+ {
+ displayName: 'Form Type',
+ name: 'formType',
+ type: 'options',
+ options: [
+ { name: 'Auto-Detect', value: 'auto' },
+ { name: 'Feedback Form', value: 'feedback_form' },
+ { name: 'Task Request', value: 'task_request' },
+ { name: 'Timeoff Request', value: 'timeoff_request' },
+ ],
+ default: 'auto',
+ description: 'Form type for processing data',
+ displayOptions: { show: { resource: ['form'], operation: ['processSubmission'] } },
+ },
+ {
+ displayName: 'Validation Errors',
+ name: 'validationErrors',
+ type: 'json',
+ default: '{}',
+ description: 'Validation errors to send to user (JSON object with field names and messages)',
+ displayOptions: { show: { resource: ['form'], operation: ['processSubmission'] } },
+ },
+];
\ No newline at end of file
diff --git a/integrations/n8n/nodes/Pachca/V2/GroupTagDescription.ts b/integrations/n8n/nodes/Pachca/V2/GroupTagDescription.ts
new file mode 100644
index 00000000..e334ce58
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V2/GroupTagDescription.ts
@@ -0,0 +1,215 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const groupTagOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: { show: { resource: ['groupTag'] } },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ action: 'Create a group tag',
+ },
+ {
+ name: 'Delete',
+ value: 'delete',
+ action: 'Delete a group tag',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ action: 'Get a group tag',
+ },
+ {
+ name: 'Get Many',
+ value: 'getAll',
+ action: 'Get many group tags',
+ },
+ {
+ name: 'Get Many Users',
+ value: 'getAllUsers',
+ action: 'Get many group tag users',
+ },
+ {
+ name: 'Update',
+ value: 'update',
+ action: 'Update a group tag',
+ },
+ ],
+ default: 'getAll',
+ },
+];
+
+export const groupTagFields: INodeProperties[] = [
+ {
+ displayName: 'Requires admin permissions',
+ name: 'groupTagCreateNotice',
+ type: 'notice',
+ default: '',
+ displayOptions: { show: { resource: ['groupTag'], operation: ['create'] } },
+ },
+ {
+ displayName: 'Name',
+ name: 'groupTagName',
+ type: 'string',
+ required: true,
+ default: "",
+ description: 'Tag name',
+ displayOptions: { show: { resource: ['groupTag'], operation: ['create'] } },
+ placeholder: 'New tag name',
+ routing: { send: { type: 'body', property: 'name' } },
+ },
+ {
+ displayName: 'Requires admin permissions',
+ name: 'groupTagGetAllNotice',
+ type: 'notice',
+ default: '',
+ displayOptions: { show: { resource: ['groupTag'], operation: ['getAll'] } },
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ displayOptions: { show: { resource: ['groupTag'], operation: ['getAll'] } },
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 50,
+ description: 'Max number of results to return',
+ typeOptions: { minValue: 1, maxValue: 50 },
+ displayOptions: { show: { resource: ['groupTag'], operation: ['getAll'], returnAll: [false] } },
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['groupTag'], operation: ['getAll'] } },
+ },
+ {
+ displayName: 'Names',
+ name: 'names',
+ type: 'string',
+ default: "",
+ description: 'Array of tag names to filter by',
+ placeholder: 'Design,Product',
+ displayOptions: { show: { resource: ['groupTag'], operation: ['getAll'] } },
+ routing: { send: { type: 'query', property: 'names' } },
+ },
+ {
+ displayName: 'ID',
+ name: 'groupTagId',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['groupTag'], operation: ['get'] } },
+ description: 'Tag ID',
+ },
+ {
+ displayName: 'Requires admin permissions',
+ name: 'groupTagGetNotice',
+ type: 'notice',
+ default: '',
+ displayOptions: { show: { resource: ['groupTag'], operation: ['get'] } },
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['groupTag'], operation: ['get'] } },
+ },
+ {
+ displayName: 'ID',
+ name: 'groupTagId',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['groupTag'], operation: ['update'] } },
+ description: 'Tag ID',
+ },
+ {
+ displayName: 'Requires admin permissions',
+ name: 'groupTagUpdateNotice',
+ type: 'notice',
+ default: '',
+ displayOptions: { show: { resource: ['groupTag'], operation: ['update'] } },
+ },
+ {
+ displayName: 'Name',
+ name: 'groupTagName',
+ type: 'string',
+ required: true,
+ default: "",
+ description: 'Tag name',
+ displayOptions: { show: { resource: ['groupTag'], operation: ['update'] } },
+ placeholder: 'New tag name',
+ routing: { send: { type: 'body', property: 'name' } },
+ },
+ {
+ displayName: 'ID',
+ name: 'groupTagId',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['groupTag'], operation: ['delete'] } },
+ description: 'Tag ID',
+ },
+ {
+ displayName: 'Requires admin permissions',
+ name: 'groupTagDeleteNotice',
+ type: 'notice',
+ default: '',
+ displayOptions: { show: { resource: ['groupTag'], operation: ['delete'] } },
+ },
+ {
+ displayName: 'ID',
+ name: 'groupTagId',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['groupTag'], operation: ['getAllUsers'] } },
+ description: 'Tag ID',
+ },
+ {
+ displayName: 'Requires admin permissions',
+ name: 'groupTagGetAllUsersNotice',
+ type: 'notice',
+ default: '',
+ displayOptions: { show: { resource: ['groupTag'], operation: ['getAllUsers'] } },
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ displayOptions: { show: { resource: ['groupTag'], operation: ['getAllUsers'] } },
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 50,
+ description: 'Max number of results to return',
+ typeOptions: { minValue: 1, maxValue: 50 },
+ displayOptions: { show: { resource: ['groupTag'], operation: ['getAllUsers'], returnAll: [false] } },
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['groupTag'], operation: ['getAllUsers'] } },
+ },
+];
\ No newline at end of file
diff --git a/integrations/n8n/nodes/Pachca/V2/LinkPreviewDescription.ts b/integrations/n8n/nodes/Pachca/V2/LinkPreviewDescription.ts
new file mode 100644
index 00000000..411c5f65
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V2/LinkPreviewDescription.ts
@@ -0,0 +1,41 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const linkPreviewOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: { show: { resource: ['linkPreview'] } },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ action: 'Create a link preview',
+ },
+ ],
+ default: 'create',
+ },
+];
+
+export const linkPreviewFields: INodeProperties[] = [
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['linkPreview'], operation: ['create'] } },
+ description: 'Message ID',
+ },
+ {
+ displayName: 'Link Previews',
+ name: 'linkPreviews',
+ type: 'json',
+ required: true,
+ default: "{}",
+ description: 'JSON map of link previews, where each key is a `URL` received in the outgoing webhook about a new message',
+ displayOptions: { show: { resource: ['linkPreview'], operation: ['create'] } },
+ routing: { send: { type: 'body', property: 'link_previews' } },
+ },
+];
\ No newline at end of file
diff --git a/integrations/n8n/nodes/Pachca/V2/MemberDescription.ts b/integrations/n8n/nodes/Pachca/V2/MemberDescription.ts
new file mode 100644
index 00000000..ae784f97
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V2/MemberDescription.ts
@@ -0,0 +1,426 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const memberOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: { show: { resource: ['member'] } },
+ options: [
+ {
+ name: 'Add Group Tags',
+ value: 'addGroupTags',
+ action: 'Add group tags to chat member',
+ },
+ {
+ name: 'Create',
+ value: 'create',
+ action: 'Create a chat member',
+ },
+ {
+ name: 'Delete',
+ value: 'delete',
+ action: 'Delete a chat member',
+ },
+ {
+ name: 'Get Many',
+ value: 'getAll',
+ action: 'Get many chat members',
+ },
+ {
+ name: 'Leave',
+ value: 'leave',
+ action: 'Leave chat',
+ },
+ {
+ name: 'Remove Group Tags',
+ value: 'removeGroupTags',
+ action: 'Remove group tags from chat member',
+ },
+ {
+ name: 'Update',
+ value: 'update',
+ action: 'Update a chat member',
+ },
+ ],
+ default: 'getAll',
+ },
+];
+
+export const memberFields: INodeProperties[] = [
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ description: 'Chat ID',
+ modes: [
+ {
+ displayName: 'From List',
+ name: 'list',
+ type: 'list',
+ typeOptions: { searchListMethod: 'searchChats', searchable: true },
+ },
+ {
+ displayName: 'By ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 334',
+ },
+ {
+ displayName: 'By URL',
+ name: 'url',
+ type: 'string',
+ placeholder: 'https://app.pachca.com/chats/12345',
+ extractValue: { type: 'regex', regex: 'https?://[^/]+/chats/(\\d+)' },
+ validation: [{ type: 'regex', properties: { regex: 'https?://[^/]+/chats/(\\d+)', errorMessage: 'Not a valid Pachca chat URL' } }],
+ },
+ ],
+ displayOptions: { show: { resource: ['member'], operation: ['addGroupTags'] } },
+ },
+ {
+ displayName: 'Group Tag IDs',
+ name: 'groupTagIds',
+ type: 'string',
+ required: true,
+ default: "",
+ description: 'Array of tag IDs to be added as members',
+ displayOptions: { show: { resource: ['member'], operation: ['addGroupTags'] } },
+ placeholder: '86,18',
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ description: 'Chat ID',
+ modes: [
+ {
+ displayName: 'From List',
+ name: 'list',
+ type: 'list',
+ typeOptions: { searchListMethod: 'searchChats', searchable: true },
+ },
+ {
+ displayName: 'By ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 334',
+ },
+ {
+ displayName: 'By URL',
+ name: 'url',
+ type: 'string',
+ placeholder: 'https://app.pachca.com/chats/12345',
+ extractValue: { type: 'regex', regex: 'https?://[^/]+/chats/(\\d+)' },
+ validation: [{ type: 'regex', properties: { regex: 'https?://[^/]+/chats/(\\d+)', errorMessage: 'Not a valid Pachca chat URL' } }],
+ },
+ ],
+ displayOptions: { show: { resource: ['member'], operation: ['removeGroupTags'] } },
+ },
+ {
+ displayName: 'Tag ID',
+ name: 'tagId',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['member'], operation: ['removeGroupTags'] } },
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ description: 'Chat ID',
+ modes: [
+ {
+ displayName: 'From List',
+ name: 'list',
+ type: 'list',
+ typeOptions: { searchListMethod: 'searchChats', searchable: true },
+ },
+ {
+ displayName: 'By ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 334',
+ },
+ {
+ displayName: 'By URL',
+ name: 'url',
+ type: 'string',
+ placeholder: 'https://app.pachca.com/chats/12345',
+ extractValue: { type: 'regex', regex: 'https?://[^/]+/chats/(\\d+)' },
+ validation: [{ type: 'regex', properties: { regex: 'https?://[^/]+/chats/(\\d+)', errorMessage: 'Not a valid Pachca chat URL' } }],
+ },
+ ],
+ displayOptions: { show: { resource: ['member'], operation: ['leave'] } },
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ description: 'Chat ID',
+ modes: [
+ {
+ displayName: 'From List',
+ name: 'list',
+ type: 'list',
+ typeOptions: { searchListMethod: 'searchChats', searchable: true },
+ },
+ {
+ displayName: 'By ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 334',
+ },
+ {
+ displayName: 'By URL',
+ name: 'url',
+ type: 'string',
+ placeholder: 'https://app.pachca.com/chats/12345',
+ extractValue: { type: 'regex', regex: 'https?://[^/]+/chats/(\\d+)' },
+ validation: [{ type: 'regex', properties: { regex: 'https?://[^/]+/chats/(\\d+)', errorMessage: 'Not a valid Pachca chat URL' } }],
+ },
+ ],
+ displayOptions: { show: { resource: ['member'], operation: ['getAll'] } },
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ displayOptions: { show: { resource: ['member'], operation: ['getAll'] } },
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 50,
+ description: 'Max number of results to return',
+ typeOptions: { minValue: 1, maxValue: 50 },
+ displayOptions: { show: { resource: ['member'], operation: ['getAll'], returnAll: [false] } },
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['member'], operation: ['getAll'] } },
+ },
+ {
+ displayName: 'Role',
+ name: 'role',
+ type: 'options',
+ options: [{ name: 'Admin', value: 'admin' },
+{ name: 'All', value: 'all', description: 'Any role' },
+{ name: 'Editor', value: 'editor' },
+{ name: 'Member', value: 'member', description: 'Member/subscriber' },
+{ name: 'Owner', value: 'owner' }],
+ default: "all",
+ description: 'Role in the chat',
+ displayOptions: { show: { resource: ['member'], operation: ['getAll'] } },
+ routing: { send: { type: 'query', property: 'role' } },
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ description: 'Chat ID (conversation, channel, or thread chat)',
+ modes: [
+ {
+ displayName: 'From List',
+ name: 'list',
+ type: 'list',
+ typeOptions: { searchListMethod: 'searchChats', searchable: true },
+ },
+ {
+ displayName: 'By ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 334',
+ },
+ {
+ displayName: 'By URL',
+ name: 'url',
+ type: 'string',
+ placeholder: 'https://app.pachca.com/chats/12345',
+ extractValue: { type: 'regex', regex: 'https?://[^/]+/chats/(\\d+)' },
+ validation: [{ type: 'regex', properties: { regex: 'https?://[^/]+/chats/(\\d+)', errorMessage: 'Not a valid Pachca chat URL' } }],
+ },
+ ],
+ displayOptions: { show: { resource: ['member'], operation: ['create'] } },
+ },
+ {
+ displayName: 'Member IDs',
+ name: 'memberIds',
+ type: 'string',
+ required: true,
+ default: "",
+ description: 'Array of user IDs who will become members',
+ displayOptions: { show: { resource: ['member'], operation: ['create'] } },
+ placeholder: '186,187',
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: { show: { resource: ['member'], operation: ['create'] } },
+ options: [
+ {
+ displayName: 'Silent',
+ name: 'silent',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to do not create a system message in the chat about adding a member',
+ routing: { send: { type: 'body', property: 'silent' } },
+ },
+ ],
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ description: 'Chat ID',
+ modes: [
+ {
+ displayName: 'From List',
+ name: 'list',
+ type: 'list',
+ typeOptions: { searchListMethod: 'searchChats', searchable: true },
+ },
+ {
+ displayName: 'By ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 334',
+ },
+ {
+ displayName: 'By URL',
+ name: 'url',
+ type: 'string',
+ placeholder: 'https://app.pachca.com/chats/12345',
+ extractValue: { type: 'regex', regex: 'https?://[^/]+/chats/(\\d+)' },
+ validation: [{ type: 'regex', properties: { regex: 'https?://[^/]+/chats/(\\d+)', errorMessage: 'Not a valid Pachca chat URL' } }],
+ },
+ ],
+ displayOptions: { show: { resource: ['member'], operation: ['delete'] } },
+ },
+ {
+ displayName: 'User ID',
+ name: 'userId',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ modes: [
+ {
+ displayName: 'From List',
+ name: 'list',
+ type: 'list',
+ typeOptions: { searchListMethod: 'searchUsers', searchable: true },
+ },
+ {
+ displayName: 'By ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 186',
+ },
+ {
+ displayName: 'By URL',
+ name: 'url',
+ type: 'string',
+ placeholder: 'https://app.pachca.com/users/12345',
+ extractValue: { type: 'regex', regex: 'https?://[^/]+/users/(\\d+)' },
+ validation: [{ type: 'regex', properties: { regex: 'https?://[^/]+/users/(\\d+)', errorMessage: 'Not a valid Pachca user URL' } }],
+ },
+ ],
+ displayOptions: { show: { resource: ['member'], operation: ['delete'] } },
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ description: 'Chat ID',
+ modes: [
+ {
+ displayName: 'From List',
+ name: 'list',
+ type: 'list',
+ typeOptions: { searchListMethod: 'searchChats', searchable: true },
+ },
+ {
+ displayName: 'By ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 334',
+ },
+ {
+ displayName: 'By URL',
+ name: 'url',
+ type: 'string',
+ placeholder: 'https://app.pachca.com/chats/12345',
+ extractValue: { type: 'regex', regex: 'https?://[^/]+/chats/(\\d+)' },
+ validation: [{ type: 'regex', properties: { regex: 'https?://[^/]+/chats/(\\d+)', errorMessage: 'Not a valid Pachca chat URL' } }],
+ },
+ ],
+ displayOptions: { show: { resource: ['member'], operation: ['update'] } },
+ },
+ {
+ displayName: 'User ID',
+ name: 'userId',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ modes: [
+ {
+ displayName: 'From List',
+ name: 'list',
+ type: 'list',
+ typeOptions: { searchListMethod: 'searchUsers', searchable: true },
+ },
+ {
+ displayName: 'By ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 186',
+ },
+ {
+ displayName: 'By URL',
+ name: 'url',
+ type: 'string',
+ placeholder: 'https://app.pachca.com/users/12345',
+ extractValue: { type: 'regex', regex: 'https?://[^/]+/users/(\\d+)' },
+ validation: [{ type: 'regex', properties: { regex: 'https?://[^/]+/users/(\\d+)', errorMessage: 'Not a valid Pachca user URL' } }],
+ },
+ ],
+ displayOptions: { show: { resource: ['member'], operation: ['update'] } },
+ },
+ {
+ displayName: 'Role',
+ name: 'role',
+ type: 'options',
+ required: true,
+ options: [{ name: 'Admin', value: 'admin' },
+{ name: 'Editor', value: 'editor', description: 'Editor (available for channels only)' },
+{ name: 'Member', value: 'member', description: 'Member or subscriber' }],
+ default: "member",
+ displayOptions: { show: { resource: ['member'], operation: ['update'] } },
+ routing: { send: { type: 'body', property: 'role' } },
+ },
+];
\ No newline at end of file
diff --git a/integrations/n8n/nodes/Pachca/V2/MessageDescription.ts b/integrations/n8n/nodes/Pachca/V2/MessageDescription.ts
new file mode 100644
index 00000000..b9b1f586
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V2/MessageDescription.ts
@@ -0,0 +1,588 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const messageOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: { show: { resource: ['message'] } },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ action: 'Create a message',
+ },
+ {
+ name: 'Delete',
+ value: 'delete',
+ action: 'Delete a message',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ action: 'Get a message',
+ },
+ {
+ name: 'Get Many',
+ value: 'getAll',
+ action: 'Get many messages',
+ },
+ {
+ name: 'Pin',
+ value: 'pin',
+ action: 'Pin a message',
+ },
+ {
+ name: 'Unpin',
+ value: 'unpin',
+ action: 'Unpin a message',
+ },
+ {
+ name: 'Update',
+ value: 'update',
+ action: 'Update a message',
+ },
+ ],
+ default: 'create',
+ },
+];
+
+export const messageFields: INodeProperties[] = [
+ {
+ displayName: 'Entity Type',
+ name: 'entityType',
+ type: 'options',
+ required: true,
+ options: [{ name: 'Discussion', value: 'discussion', description: 'Conversation or channel' },
+{ name: 'Thread', value: 'thread' },
+{ name: 'User', value: 'user' }],
+ default: "discussion",
+ displayOptions: { show: { resource: ['message'], operation: ['create'] } },
+ routing: { send: { type: 'body', property: 'entity_type' } },
+ },
+ {
+ displayName: 'Entity ID',
+ name: 'entityId',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ modes: [
+ {
+ displayName: 'From List',
+ name: 'list',
+ type: 'list',
+ typeOptions: { searchListMethod: 'searchEntities', searchable: true },
+ },
+ {
+ displayName: 'By ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 334',
+ },
+ ],
+ displayOptions: { show: { resource: ['message'], operation: ['create'] } },
+ routing: { send: { type: 'body', property: 'entity_id' } },
+ },
+ {
+ displayName: 'Content',
+ name: 'content',
+ type: 'string',
+ required: true,
+ typeOptions: { rows: 4 },
+ default: "",
+ description: 'Message text',
+ displayOptions: { show: { resource: ['message'], operation: ['create'] } },
+ placeholder: 'Yesterday we sold 756 t-shirts (10% more than last Sunday)',
+ routing: { send: { type: 'body', property: 'content' } },
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: { show: { resource: ['message'], operation: ['create'] } },
+ options: [
+ {
+ displayName: 'Display Avatar URL',
+ name: 'displayAvatarUrl',
+ type: 'string',
+ default: "",
+ description: 'Custom sender avatar URL for this message. This field can only be used with a bot access_token.',
+ placeholder: 'https://example.com/avatar.png',
+ routing: { send: { type: 'body', property: 'display_avatar_url' } },
+ },
+ {
+ displayName: 'Display Name',
+ name: 'displayName',
+ type: 'string',
+ default: "",
+ description: 'Custom sender display name for this message. This field can only be used with a bot access_token.',
+ placeholder: 'Support Bot',
+ routing: { send: { type: 'body', property: 'display_name' } },
+ },
+ {
+ displayName: 'Files',
+ name: 'files',
+ type: 'fixedCollection',
+ typeOptions: { multipleValues: true },
+ options: [{
+ name: 'file',
+ displayName: 'File',
+ values: [
+ {
+ displayName: 'File Type',
+ name: 'file_type',
+ type: 'options',
+ options: [{ name: 'File', value: 'file', description: 'Regular file' },
+{ name: 'Image', value: 'image' }],
+ default: "",
+ },
+ {
+ displayName: 'Height',
+ name: 'height',
+ type: 'number',
+ default: 0,
+ description: 'Image height in px (used when file_type is set to image)',
+ },
+ {
+ displayName: 'Key',
+ name: 'key',
+ type: 'string',
+ default: "",
+ description: 'File path obtained from [file upload](POST /direct_url)',
+ },
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ default: "",
+ description: 'File name to display to the user (recommended to include the file extension)',
+ },
+ {
+ displayName: 'Size',
+ name: 'size',
+ type: 'number',
+ default: 0,
+ description: 'File size in bytes, displayed to the user',
+ },
+ {
+ displayName: 'Width',
+ name: 'width',
+ type: 'number',
+ default: 0,
+ description: 'Image width in px (used when file_type is set to image)',
+ },
+ ],
+ }],
+ default: [],
+ description: 'Files to attach',
+ hint: 'Upload a file first using File > Create, then use the returned key here',
+ routing: { send: { type: 'body', property: 'files' } },
+ },
+ {
+ displayName: 'Link Preview',
+ name: 'linkPreview',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to display a preview of the first link found in the message text',
+ routing: { send: { type: 'body', property: 'link_preview' } },
+ },
+ {
+ displayName: 'Parent Message ID',
+ name: 'parentMessageId',
+ type: 'number',
+ default: 0,
+ description: 'Message ID. Specify when sending a reply to another message.',
+ placeholder: '194270',
+ routing: { send: { type: 'body', property: 'parent_message_id' } },
+ },
+ {
+ displayName: 'Skip Invite Mentions',
+ name: 'skipInviteMentions',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to skip adding mentioned users to the thread. Only works when sending a message to a thread.',
+ routing: { send: { type: 'body', property: 'skip_invite_mentions' } },
+ },
+ ],
+ },
+ {
+ displayName: 'Button Layout',
+ name: 'buttonLayout',
+ type: 'options',
+ options: [
+ { name: 'None', value: 'none' },
+ { name: 'Single Row', value: 'single_row' },
+ { name: 'Multiple Rows', value: 'multiple_rows' },
+ { name: 'Raw JSON', value: 'raw_json' },
+ ],
+ default: 'none',
+ description: 'How to layout buttons in the message',
+ displayOptions: { show: { resource: ['message'], operation: ['create'] } },
+ },
+ {
+ displayName: 'Buttons',
+ name: 'buttons',
+ type: 'fixedCollection',
+ typeOptions: { multipleValues: true },
+ options: [{
+ name: 'button',
+ displayName: 'Button',
+ values: [
+ {
+ displayName: 'Text',
+ name: 'text',
+ type: 'string',
+ default: '',
+ description: 'Button label (max 255 characters)',
+ placeholder: 'Click me',
+ },
+ {
+ displayName: 'Type',
+ name: 'type',
+ type: 'options',
+ options: [
+ { name: 'Data (Webhook)', value: 'data' },
+ { name: 'URL (Link)', value: 'url' },
+ ],
+ default: 'data',
+ description: 'Data sends a webhook on click, URL opens a link',
+ },
+ {
+ displayName: 'Data',
+ name: 'data',
+ type: 'string',
+ default: '',
+ description: 'Data sent via webhook when button is clicked (max 255 characters)',
+ placeholder: 'action_confirm',
+ displayOptions: { show: { type: ['data'] } },
+ },
+ {
+ displayName: 'URL',
+ name: 'url',
+ type: 'string',
+ default: '',
+ description: 'URL to open when button is clicked',
+ placeholder: 'https://example.com',
+ displayOptions: { show: { type: ['url'] } },
+ },
+ ],
+ }],
+ default: {},
+ description: 'Buttons to add to the message. Max 100 buttons, up to 8 per row.',
+ displayOptions: { show: { resource: ['message'], operation: ['create'], buttonLayout: ['single_row', 'multiple_rows'] } },
+ },
+ {
+ displayName: 'Buttons (JSON)',
+ name: 'rawJsonButtons',
+ type: 'json',
+ default: '[]',
+ description: 'Buttons as JSON: array of rows, each row is an array of buttons. To remove all buttons, send [].',
+ placeholder: '[[{"text":"OK","data":"confirm"}],[{"text":"Link","url":"https://example.com"}]]',
+ displayOptions: { show: { resource: ['message'], operation: ['create'], buttonLayout: ['raw_json'] } },
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ displayOptions: { show: { resource: ['message'], operation: ['getAll'] } },
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 50,
+ description: 'Max number of results to return',
+ typeOptions: { minValue: 1, maxValue: 50 },
+ displayOptions: { show: { resource: ['message'], operation: ['getAll'], returnAll: [false] } },
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['message'], operation: ['getAll'] } },
+ },
+ {
+ displayName: 'Chat ID',
+ name: 'chatId',
+ type: 'resourceLocator',
+ required: true,
+ default: { mode: 'list', value: '' },
+ description: 'Chat ID (conversation, channel, direct message, or thread chat)',
+ modes: [
+ {
+ displayName: 'From List',
+ name: 'list',
+ type: 'list',
+ typeOptions: { searchListMethod: 'searchChats', searchable: true },
+ },
+ {
+ displayName: 'By ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 198',
+ },
+ {
+ displayName: 'By URL',
+ name: 'url',
+ type: 'string',
+ placeholder: 'https://app.pachca.com/chats/12345',
+ extractValue: { type: 'regex', regex: 'https?://[^/]+/chats/(\\d+)' },
+ validation: [{ type: 'regex', properties: { regex: 'https?://[^/]+/chats/(\\d+)', errorMessage: 'Not a valid Pachca chat URL' } }],
+ },
+ ],
+ displayOptions: { show: { resource: ['message'], operation: ['getAll'] } },
+ routing: { send: { type: 'query', property: 'chat_id' } },
+ },
+ {
+ displayName: 'Sort',
+ name: 'sort',
+ type: 'options',
+ options: [{ name: 'ID', value: 'id', description: 'By message ID' }],
+ default: "id",
+ description: 'Sort field',
+ displayOptions: { show: { resource: ['message'], operation: ['getAll'] } },
+ routing: { send: { type: 'query', property: 'sort' } },
+ },
+ {
+ displayName: 'Order',
+ name: 'order',
+ type: 'options',
+ options: [{ name: 'Asc', value: 'asc', description: 'Ascending' },
+{ name: 'Desc', value: 'desc', description: 'Descending' }],
+ default: "desc",
+ description: 'Sort direction',
+ displayOptions: { show: { resource: ['message'], operation: ['getAll'] } },
+ routing: { send: { type: 'query', property: 'order' } },
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['message'], operation: ['get'] } },
+ description: 'Message ID',
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['message'], operation: ['get'] } },
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['message'], operation: ['update'] } },
+ description: 'Message ID',
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: { show: { resource: ['message'], operation: ['update'] } },
+ options: [
+ {
+ displayName: 'Content',
+ name: 'content',
+ type: 'string',
+ typeOptions: { rows: 4 },
+ default: "",
+ description: 'Message text',
+ placeholder: 'Try to spell these correctly on the first attempt: bureaucracy, accommodate, definitely, entrepreneur, liaison, necessary, surveillance, questionnaire.',
+ routing: { send: { type: 'body', property: 'content' } },
+ },
+ {
+ displayName: 'Display Avatar URL',
+ name: 'displayAvatarUrl',
+ type: 'string',
+ default: "",
+ description: 'Custom sender avatar URL for this message. This field can only be used with a bot access_token.',
+ placeholder: 'https://example.com/avatar.png',
+ routing: { send: { type: 'body', property: 'display_avatar_url' } },
+ },
+ {
+ displayName: 'Display Name',
+ name: 'displayName',
+ type: 'string',
+ default: "",
+ description: 'Custom sender display name for this message. This field can only be used with a bot access_token.',
+ placeholder: 'Support Bot',
+ routing: { send: { type: 'body', property: 'display_name' } },
+ },
+ {
+ displayName: 'Files',
+ name: 'files',
+ type: 'fixedCollection',
+ typeOptions: { multipleValues: true },
+ options: [{
+ name: 'file',
+ displayName: 'File',
+ values: [
+ {
+ displayName: 'File Type',
+ name: 'file_type',
+ type: 'string',
+ default: "",
+ description: 'File type: file (file), image (image)',
+ },
+ {
+ displayName: 'Height',
+ name: 'height',
+ type: 'number',
+ default: 0,
+ description: 'Image height in px (used when file_type is set to image)',
+ },
+ {
+ displayName: 'Key',
+ name: 'key',
+ type: 'string',
+ default: "",
+ description: 'File path obtained from [file upload](POST /direct_url)',
+ },
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ default: "",
+ description: 'File name to display to the user (recommended to include the file extension)',
+ },
+ {
+ displayName: 'Size',
+ name: 'size',
+ type: 'number',
+ default: 0,
+ description: 'File size in bytes, displayed to the user',
+ },
+ {
+ displayName: 'Width',
+ name: 'width',
+ type: 'number',
+ default: 0,
+ description: 'Image width in px (used when file_type is set to image)',
+ },
+ ],
+ }],
+ default: [],
+ description: 'Files to attach',
+ hint: 'Upload a file first using File > Create, then use the returned key here',
+ routing: { send: { type: 'body', property: 'files' } },
+ },
+ ],
+ },
+ {
+ displayName: 'Button Layout',
+ name: 'buttonLayout',
+ type: 'options',
+ options: [
+ { name: 'None', value: 'none' },
+ { name: 'Single Row', value: 'single_row' },
+ { name: 'Multiple Rows', value: 'multiple_rows' },
+ { name: 'Raw JSON', value: 'raw_json' },
+ ],
+ default: 'none',
+ description: 'How to layout buttons in the message',
+ displayOptions: { show: { resource: ['message'], operation: ['update'] } },
+ },
+ {
+ displayName: 'Buttons',
+ name: 'buttons',
+ type: 'fixedCollection',
+ typeOptions: { multipleValues: true },
+ options: [{
+ name: 'button',
+ displayName: 'Button',
+ values: [
+ {
+ displayName: 'Text',
+ name: 'text',
+ type: 'string',
+ default: '',
+ description: 'Button label (max 255 characters)',
+ placeholder: 'Click me',
+ },
+ {
+ displayName: 'Type',
+ name: 'type',
+ type: 'options',
+ options: [
+ { name: 'Data (Webhook)', value: 'data' },
+ { name: 'URL (Link)', value: 'url' },
+ ],
+ default: 'data',
+ description: 'Data sends a webhook on click, URL opens a link',
+ },
+ {
+ displayName: 'Data',
+ name: 'data',
+ type: 'string',
+ default: '',
+ description: 'Data sent via webhook when button is clicked (max 255 characters)',
+ placeholder: 'action_confirm',
+ displayOptions: { show: { type: ['data'] } },
+ },
+ {
+ displayName: 'URL',
+ name: 'url',
+ type: 'string',
+ default: '',
+ description: 'URL to open when button is clicked',
+ placeholder: 'https://example.com',
+ displayOptions: { show: { type: ['url'] } },
+ },
+ ],
+ }],
+ default: {},
+ description: 'Buttons to add to the message. Max 100 buttons, up to 8 per row.',
+ displayOptions: { show: { resource: ['message'], operation: ['update'], buttonLayout: ['single_row', 'multiple_rows'] } },
+ },
+ {
+ displayName: 'Buttons (JSON)',
+ name: 'rawJsonButtons',
+ type: 'json',
+ default: '[]',
+ description: 'Buttons as JSON: array of rows, each row is an array of buttons. To remove all buttons, send [].',
+ placeholder: '[[{"text":"OK","data":"confirm"}],[{"text":"Link","url":"https://example.com"}]]',
+ displayOptions: { show: { resource: ['message'], operation: ['update'], buttonLayout: ['raw_json'] } },
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['message'], operation: ['delete'] } },
+ description: 'Message ID',
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['message'], operation: ['pin'] } },
+ description: 'Message ID',
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['message'], operation: ['unpin'] } },
+ description: 'Message ID',
+ },
+];
\ No newline at end of file
diff --git a/integrations/n8n/nodes/Pachca/V2/PachcaV2.node.ts b/integrations/n8n/nodes/Pachca/V2/PachcaV2.node.ts
new file mode 100644
index 00000000..f3dbb3a1
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V2/PachcaV2.node.ts
@@ -0,0 +1,119 @@
+import type {
+ INodeType,
+ INodeTypeBaseDescription,
+ INodeTypeDescription,
+ IExecuteFunctions,
+ INodeExecutionData,
+} from 'n8n-workflow';
+import { NodeConnectionTypes } from 'n8n-workflow';
+import { router } from '../SharedRouter';
+import { searchChats, searchUsers, searchEntities, getCustomProperties } from '../GenericFunctions';
+
+import { securityOperations, securityFields } from './SecurityDescription';
+import { botOperations, botFields } from './BotDescription';
+import { chatOperations, chatFields } from './ChatDescription';
+import { memberOperations, memberFields } from './MemberDescription';
+import { groupTagOperations, groupTagFields } from './GroupTagDescription';
+import { messageOperations, messageFields } from './MessageDescription';
+import { linkPreviewOperations, linkPreviewFields } from './LinkPreviewDescription';
+import { reactionOperations, reactionFields } from './ReactionDescription';
+import { readMemberOperations, readMemberFields } from './ReadMemberDescription';
+import { threadOperations, threadFields } from './ThreadDescription';
+import { profileOperations, profileFields } from './ProfileDescription';
+import { searchOperations, searchFields } from './SearchDescription';
+import { taskOperations, taskFields } from './TaskDescription';
+import { userOperations, userFields } from './UserDescription';
+import { formOperations, formFields } from './FormDescription';
+import { exportOperations, exportFields } from './ExportDescription';
+import { customPropertyOperations, customPropertyFields } from './CustomPropertyDescription';
+import { fileOperations, fileFields } from './FileDescription';
+
+export class PachcaV2 implements INodeType {
+ description: INodeTypeDescription;
+
+ constructor(baseDescription: INodeTypeBaseDescription) {
+ this.description = {
+ ...baseDescription,
+ version: 2,
+ defaults: { name: 'Pachca' },
+ usableAsTool: true,
+ inputs: [NodeConnectionTypes.Main],
+ outputs: [NodeConnectionTypes.Main],
+ credentials: [{ name: 'pachcaApi', required: true }],
+ properties: [
+ {
+ displayName: 'Resource',
+ name: 'resource',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ { name: 'Bot', value: 'bot' },
+ { name: 'Chat', value: 'chat' },
+ { name: 'Chat Export', value: 'export' },
+ { name: 'Chat Member', value: 'member' },
+ { name: 'Custom Property', value: 'customProperty' },
+ { name: 'File', value: 'file' },
+ { name: 'Form', value: 'form' },
+ { name: 'Group Tag', value: 'groupTag' },
+ { name: 'Link Preview', value: 'linkPreview' },
+ { name: 'Message', value: 'message' },
+ { name: 'Profile', value: 'profile' },
+ { name: 'Reaction', value: 'reaction' },
+ { name: 'Read Member', value: 'readMember' },
+ { name: 'Search', value: 'search' },
+ { name: 'Security', value: 'security' },
+ { name: 'Task', value: 'task' },
+ { name: 'Thread', value: 'thread' },
+ { name: 'User', value: 'user' },
+ ],
+ default: 'message',
+ },
+ ...securityOperations,
+ ...securityFields,
+ ...botOperations,
+ ...botFields,
+ ...chatOperations,
+ ...chatFields,
+ ...memberOperations,
+ ...memberFields,
+ ...groupTagOperations,
+ ...groupTagFields,
+ ...messageOperations,
+ ...messageFields,
+ ...linkPreviewOperations,
+ ...linkPreviewFields,
+ ...reactionOperations,
+ ...reactionFields,
+ ...readMemberOperations,
+ ...readMemberFields,
+ ...threadOperations,
+ ...threadFields,
+ ...profileOperations,
+ ...profileFields,
+ ...searchOperations,
+ ...searchFields,
+ ...taskOperations,
+ ...taskFields,
+ ...userOperations,
+ ...userFields,
+ ...formOperations,
+ ...formFields,
+ ...exportOperations,
+ ...exportFields,
+ ...customPropertyOperations,
+ ...customPropertyFields,
+ ...fileOperations,
+ ...fileFields,
+ ],
+ };
+ }
+
+ async execute(this: IExecuteFunctions): Promise {
+ return router.call(this);
+ }
+
+ methods = {
+ listSearch: { searchChats, searchUsers, searchEntities },
+ loadOptions: { getCustomProperties },
+ };
+}
diff --git a/integrations/n8n/nodes/Pachca/V2/ProfileDescription.ts b/integrations/n8n/nodes/Pachca/V2/ProfileDescription.ts
new file mode 100644
index 00000000..cbc38570
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V2/ProfileDescription.ts
@@ -0,0 +1,143 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const profileOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: { show: { resource: ['profile'] } },
+ options: [
+ {
+ name: 'Delete Avatar',
+ value: 'deleteAvatar',
+ action: 'Delete profile avatar',
+ },
+ {
+ name: 'Delete Status',
+ value: 'deleteStatus',
+ action: 'Delete profile status',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ action: 'Get a profile',
+ },
+ {
+ name: 'Get Info',
+ value: 'getInfo',
+ action: 'Get profile info',
+ },
+ {
+ name: 'Get Status',
+ value: 'getStatus',
+ action: 'Get profile status',
+ },
+ {
+ name: 'Update Avatar',
+ value: 'updateAvatar',
+ action: 'Update profile avatar',
+ },
+ {
+ name: 'Update Status',
+ value: 'updateStatus',
+ action: 'Update profile status',
+ },
+ ],
+ default: 'getInfo',
+ },
+];
+
+export const profileFields: INodeProperties[] = [
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['profile'], operation: ['getInfo'] } },
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['profile'], operation: ['get'] } },
+ },
+ {
+ displayName: 'Input Binary Field',
+ name: 'image',
+ type: 'string',
+ required: true,
+ default: "data",
+ description: 'Name of the binary property containing the avatar image. Use a previous node (e.g. HTTP Request, Read Binary File) to load the image.',
+ displayOptions: { show: { resource: ['profile'], operation: ['updateAvatar'] } },
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['profile'], operation: ['getStatus'] } },
+ },
+ {
+ displayName: 'Emoji',
+ name: 'statusEmoji',
+ type: 'string',
+ required: true,
+ default: "",
+ description: 'Status emoji character',
+ displayOptions: { show: { resource: ['profile'], operation: ['updateStatus'] } },
+ placeholder: '🎮',
+ routing: { send: { type: 'body', property: 'emoji' } },
+ },
+ {
+ displayName: 'Title',
+ name: 'statusTitle',
+ type: 'string',
+ required: true,
+ default: "",
+ description: 'Status text',
+ displayOptions: { show: { resource: ['profile'], operation: ['updateStatus'] } },
+ placeholder: 'Very busy',
+ routing: { send: { type: 'body', property: 'title' } },
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: { show: { resource: ['profile'], operation: ['updateStatus'] } },
+ options: [
+ {
+ displayName: 'Away Message',
+ name: 'awayMessage',
+ type: 'string',
+ default: "",
+ description: 'Away mode message text. Displayed in the profile and in direct messages/mentions.',
+ placeholder: 'Back after 3 PM',
+ routing: { send: { type: 'body', property: 'away_message' } },
+ },
+ {
+ displayName: 'Expires At',
+ name: 'statusExpiresAt',
+ type: 'dateTime',
+ default: "",
+ description: 'Status expiration date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format',
+ placeholder: '2024-04-08T10:00:00.000Z',
+ routing: { send: { type: 'body', property: 'expires_at' } },
+ },
+ {
+ displayName: 'Is Away',
+ name: 'isAway',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to "Away" mode',
+ routing: { send: { type: 'body', property: 'is_away' } },
+ },
+ ],
+ },
+];
\ No newline at end of file
diff --git a/integrations/n8n/nodes/Pachca/V2/ReactionDescription.ts b/integrations/n8n/nodes/Pachca/V2/ReactionDescription.ts
new file mode 100644
index 00000000..b6b3cae3
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V2/ReactionDescription.ts
@@ -0,0 +1,135 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const reactionOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: { show: { resource: ['reaction'] } },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ action: 'Create a reaction',
+ },
+ {
+ name: 'Delete',
+ value: 'delete',
+ action: 'Delete a reaction',
+ },
+ {
+ name: 'Get Many',
+ value: 'getAll',
+ action: 'Get many reactions',
+ },
+ ],
+ default: 'create',
+ },
+];
+
+export const reactionFields: INodeProperties[] = [
+ {
+ displayName: 'ID',
+ name: 'reactionsMessageId',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['reaction'], operation: ['create'] } },
+ description: 'Message ID',
+ },
+ {
+ displayName: 'Code',
+ name: 'reactionsReactionCode',
+ type: 'string',
+ required: true,
+ default: "",
+ description: 'Reaction emoji character',
+ displayOptions: { show: { resource: ['reaction'], operation: ['create'] } },
+ placeholder: '👍',
+ routing: { send: { type: 'body', property: 'code' } },
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: { show: { resource: ['reaction'], operation: ['create'] } },
+ options: [
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ default: "",
+ description: 'Emoji text name (used for custom emoji)',
+ placeholder: ':+1:',
+ routing: { send: { type: 'body', property: 'name' } },
+ },
+ ],
+ },
+ {
+ displayName: 'ID',
+ name: 'reactionsMessageId',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['reaction'], operation: ['delete'] } },
+ description: 'Message ID',
+ },
+ {
+ displayName: 'Code',
+ name: 'reactionsReactionCode',
+ type: 'string',
+ required: true,
+ default: "",
+ description: 'Emoji character of the reaction',
+ placeholder: '👍',
+ displayOptions: { show: { resource: ['reaction'], operation: ['delete'] } },
+ routing: { send: { type: 'query', property: 'code' } },
+ },
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ default: "",
+ description: 'Text name of the emoji (used for custom emoji)',
+ placeholder: ':+1:',
+ displayOptions: { show: { resource: ['reaction'], operation: ['delete'] } },
+ routing: { send: { type: 'query', property: 'name' } },
+ },
+ {
+ displayName: 'ID',
+ name: 'reactionsMessageId',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['reaction'], operation: ['getAll'] } },
+ description: 'Message ID',
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ displayOptions: { show: { resource: ['reaction'], operation: ['getAll'] } },
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 50,
+ description: 'Max number of results to return',
+ typeOptions: { minValue: 1, maxValue: 50 },
+ displayOptions: { show: { resource: ['reaction'], operation: ['getAll'], returnAll: [false] } },
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['reaction'], operation: ['getAll'] } },
+ },
+];
\ No newline at end of file
diff --git a/integrations/n8n/nodes/Pachca/V2/ReadMemberDescription.ts b/integrations/n8n/nodes/Pachca/V2/ReadMemberDescription.ts
new file mode 100644
index 00000000..0d66b1ca
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V2/ReadMemberDescription.ts
@@ -0,0 +1,56 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const readMemberOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: { show: { resource: ['readMember'] } },
+ options: [
+ {
+ name: 'Get Many Read Member IDs',
+ value: 'getAllReadMemberIds',
+ action: 'Get many read member ids',
+ },
+ ],
+ default: 'getAllReadMemberIds',
+ },
+];
+
+export const readMemberFields: INodeProperties[] = [
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['readMember'], operation: ['getAllReadMemberIds'] } },
+ description: 'Message ID',
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ displayOptions: { show: { resource: ['readMember'], operation: ['getAllReadMemberIds'] } },
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 50,
+ description: 'Max number of results to return',
+ typeOptions: { minValue: 1, maxValue: 300 },
+ displayOptions: { show: { resource: ['readMember'], operation: ['getAllReadMemberIds'], returnAll: [false] } },
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['readMember'], operation: ['getAllReadMemberIds'] } },
+ },
+];
\ No newline at end of file
diff --git a/integrations/n8n/nodes/Pachca/V2/SearchDescription.ts b/integrations/n8n/nodes/Pachca/V2/SearchDescription.ts
new file mode 100644
index 00000000..6cf603b6
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V2/SearchDescription.ts
@@ -0,0 +1,322 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const searchOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: { show: { resource: ['search'] } },
+ options: [
+ {
+ name: 'Get Many Chats',
+ value: 'getAllChats',
+ action: 'Search chats',
+ },
+ {
+ name: 'Get Many Messages',
+ value: 'getAllMessages',
+ action: 'Search messages',
+ },
+ {
+ name: 'Get Many Users',
+ value: 'getAllUsers',
+ action: 'Search users',
+ },
+ ],
+ default: 'getAllChats',
+ },
+];
+
+export const searchFields: INodeProperties[] = [
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ displayOptions: { show: { resource: ['search'], operation: ['getAllChats'] } },
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 50,
+ description: 'Max number of results to return',
+ typeOptions: { minValue: 1, maxValue: 100 },
+ displayOptions: { show: { resource: ['search'], operation: ['getAllChats'], returnAll: [false] } },
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['search'], operation: ['getAllChats'] } },
+ },
+ {
+ displayName: 'Query',
+ name: 'query',
+ type: 'string',
+ default: "",
+ description: 'Search query text',
+ placeholder: 'Development',
+ displayOptions: { show: { resource: ['search'], operation: ['getAllChats'] } },
+ routing: { send: { type: 'query', property: 'query' } },
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: { show: { resource: ['search'], operation: ['getAllChats'] } },
+ options: [
+ {
+ displayName: 'Active',
+ name: 'active',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to filter by chat activity',
+ routing: { send: { type: 'query', property: 'active' } },
+ },
+ {
+ displayName: 'Chat Subtype',
+ name: 'chatSubtype',
+ type: 'options',
+ options: [{ name: 'Discussion', value: 'discussion', description: 'Channel or conversation' },
+{ name: 'Thread', value: 'thread' }],
+ default: "",
+ description: 'Filter by chat type',
+ routing: { send: { type: 'query', property: 'chat_subtype' } },
+ },
+ {
+ displayName: 'Created From',
+ name: 'createdFrom',
+ type: 'dateTime',
+ default: "",
+ description: 'Filter by creation date (from)',
+ placeholder: '2025-01-01T00:00:00.000Z',
+ routing: { send: { type: 'query', property: 'created_from' } },
+ },
+ {
+ displayName: 'Created To',
+ name: 'createdTo',
+ type: 'dateTime',
+ default: "",
+ description: 'Filter by creation date (to)',
+ placeholder: '2025-02-01T00:00:00.000Z',
+ routing: { send: { type: 'query', property: 'created_to' } },
+ },
+ {
+ displayName: 'Order',
+ name: 'order',
+ type: 'options',
+ options: [{ name: 'Asc', value: 'asc', description: 'Ascending' },
+{ name: 'Desc', value: 'desc', description: 'Descending' }],
+ default: "",
+ description: 'Sort direction',
+ routing: { send: { type: 'query', property: 'order' } },
+ },
+ {
+ displayName: 'Personal',
+ name: 'personal',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to filter by direct chats',
+ routing: { send: { type: 'query', property: 'personal' } },
+ },
+ ],
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ displayOptions: { show: { resource: ['search'], operation: ['getAllMessages'] } },
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 50,
+ description: 'Max number of results to return',
+ typeOptions: { minValue: 1, maxValue: 200 },
+ displayOptions: { show: { resource: ['search'], operation: ['getAllMessages'], returnAll: [false] } },
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['search'], operation: ['getAllMessages'] } },
+ },
+ {
+ displayName: 'Query',
+ name: 'query',
+ type: 'string',
+ default: "",
+ description: 'Search query text',
+ placeholder: 't-shirts',
+ displayOptions: { show: { resource: ['search'], operation: ['getAllMessages'] } },
+ routing: { send: { type: 'query', property: 'query' } },
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: { show: { resource: ['search'], operation: ['getAllMessages'] } },
+ options: [
+ {
+ displayName: 'Active',
+ name: 'active',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to filter by chat activity',
+ routing: { send: { type: 'query', property: 'active' } },
+ },
+ {
+ displayName: 'Chat IDs',
+ name: 'chatIds',
+ type: 'string',
+ default: "",
+ description: 'Filter by chat IDs',
+ placeholder: '198,334',
+ routing: { send: { type: 'query', property: 'chat_ids' } },
+ },
+ {
+ displayName: 'Created From',
+ name: 'createdFrom',
+ type: 'dateTime',
+ default: "",
+ description: 'Filter by creation date (from)',
+ placeholder: '2025-01-01T00:00:00.000Z',
+ routing: { send: { type: 'query', property: 'created_from' } },
+ },
+ {
+ displayName: 'Created To',
+ name: 'createdTo',
+ type: 'dateTime',
+ default: "",
+ description: 'Filter by creation date (to)',
+ placeholder: '2025-02-01T00:00:00.000Z',
+ routing: { send: { type: 'query', property: 'created_to' } },
+ },
+ {
+ displayName: 'Order',
+ name: 'order',
+ type: 'options',
+ options: [{ name: 'Asc', value: 'asc', description: 'Ascending' },
+{ name: 'Desc', value: 'desc', description: 'Descending' }],
+ default: "",
+ description: 'Sort direction',
+ routing: { send: { type: 'query', property: 'order' } },
+ },
+ {
+ displayName: 'User IDs',
+ name: 'userIds',
+ type: 'string',
+ default: "",
+ description: 'Filter by message author IDs',
+ placeholder: '12,185',
+ routing: { send: { type: 'query', property: 'user_ids' } },
+ },
+ ],
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ displayOptions: { show: { resource: ['search'], operation: ['getAllUsers'] } },
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 50,
+ description: 'Max number of results to return',
+ typeOptions: { minValue: 1, maxValue: 200 },
+ displayOptions: { show: { resource: ['search'], operation: ['getAllUsers'], returnAll: [false] } },
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['search'], operation: ['getAllUsers'] } },
+ },
+ {
+ displayName: 'Query',
+ name: 'query',
+ type: 'string',
+ default: "",
+ description: 'Search query text',
+ placeholder: 'Oleg',
+ displayOptions: { show: { resource: ['search'], operation: ['getAllUsers'] } },
+ routing: { send: { type: 'query', property: 'query' } },
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: { show: { resource: ['search'], operation: ['getAllUsers'] } },
+ options: [
+ {
+ displayName: 'Company Roles',
+ name: 'companyRoles',
+ type: 'string',
+ default: "",
+ description: 'Filter by employee roles',
+ placeholder: 'admin,user',
+ routing: { send: { type: 'query', property: 'company_roles' } },
+ },
+ {
+ displayName: 'Created From',
+ name: 'createdFrom',
+ type: 'dateTime',
+ default: "",
+ description: 'Filter by creation date (from)',
+ placeholder: '2025-01-01T00:00:00.000Z',
+ routing: { send: { type: 'query', property: 'created_from' } },
+ },
+ {
+ displayName: 'Created To',
+ name: 'createdTo',
+ type: 'dateTime',
+ default: "",
+ description: 'Filter by creation date (to)',
+ placeholder: '2025-02-01T00:00:00.000Z',
+ routing: { send: { type: 'query', property: 'created_to' } },
+ },
+ {
+ displayName: 'Order',
+ name: 'order',
+ type: 'options',
+ options: [{ name: 'Asc', value: 'asc', description: 'Ascending' },
+{ name: 'Desc', value: 'desc', description: 'Descending' }],
+ default: "",
+ description: 'Sort direction',
+ routing: { send: { type: 'query', property: 'order' } },
+ },
+ {
+ displayName: 'Sort',
+ name: 'sort',
+ type: 'options',
+ options: [{ name: 'Alphabetical', value: 'alphabetical', description: 'Alphabetically' },
+{ name: 'By Score', value: 'by_score', description: 'By relevance' }],
+ default: "",
+ description: 'Sort results by',
+ routing: { send: { type: 'query', property: 'sort' } },
+ },
+ ],
+ },
+];
\ No newline at end of file
diff --git a/integrations/n8n/nodes/Pachca/V2/SecurityDescription.ts b/integrations/n8n/nodes/Pachca/V2/SecurityDescription.ts
new file mode 100644
index 00000000..19fca03e
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V2/SecurityDescription.ts
@@ -0,0 +1,159 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const securityOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: { show: { resource: ['security'] } },
+ options: [
+ {
+ name: 'Get Many',
+ value: 'getAll',
+ action: 'Get many audit events',
+ },
+ ],
+ default: 'getAll',
+ },
+];
+
+export const securityFields: INodeProperties[] = [
+ {
+ displayName: 'Requires owner role and the "Corporation" plan',
+ name: 'securityGetAllNotice',
+ type: 'notice',
+ default: '',
+ displayOptions: { show: { resource: ['security'], operation: ['getAll'] } },
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ displayOptions: { show: { resource: ['security'], operation: ['getAll'] } },
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 50,
+ description: 'Max number of results to return',
+ typeOptions: { minValue: 1, maxValue: 50 },
+ displayOptions: { show: { resource: ['security'], operation: ['getAll'], returnAll: [false] } },
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['security'], operation: ['getAll'] } },
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: { show: { resource: ['security'], operation: ['getAll'] } },
+ options: [
+ {
+ displayName: 'Actor ID',
+ name: 'actorId',
+ type: 'string',
+ default: "",
+ description: 'ID of the user who performed the action',
+ placeholder: '98765',
+ routing: { send: { type: 'query', property: 'actor_id' } },
+ },
+ {
+ displayName: 'Actor Type',
+ name: 'actorType',
+ type: 'string',
+ default: "",
+ placeholder: 'User',
+ routing: { send: { type: 'query', property: 'actor_type' } },
+ },
+ {
+ displayName: 'End Time',
+ name: 'endTime',
+ type: 'dateTime',
+ default: "",
+ description: 'End timestamp (exclusive)',
+ placeholder: '2025-05-02T09:11:00Z',
+ routing: { send: { type: 'query', property: 'end_time' } },
+ },
+ {
+ displayName: 'Entity ID',
+ name: 'entityId',
+ type: 'string',
+ default: "",
+ description: 'ID of the affected entity',
+ placeholder: '98765',
+ routing: { send: { type: 'query', property: 'entity_id' } },
+ },
+ {
+ displayName: 'Entity Type',
+ name: 'entityType',
+ type: 'string',
+ default: "",
+ placeholder: 'User',
+ routing: { send: { type: 'query', property: 'entity_type' } },
+ },
+ {
+ displayName: 'Event Key',
+ name: 'eventKey',
+ type: 'options',
+ options: [{ name: 'Access Token Created', value: 'access_token_created', description: 'New access token created' },
+{ name: 'Access Token Destroy', value: 'access_token_destroy', description: 'Access token deleted' },
+{ name: 'Access Token Updated', value: 'access_token_updated' },
+{ name: 'Audit Events Accessed', value: 'audit_events_accessed', description: 'Audit logs accessed' },
+{ name: 'Chat Created', value: 'chat_created', description: 'New chat created' },
+{ name: 'Chat Permission Changed', value: 'chat_permission_changed', description: 'Chat access permissions changed' },
+{ name: 'Chat Renamed', value: 'chat_renamed' },
+{ name: 'Dlp Violation Detected', value: 'dlp_violation_detected', description: 'DLP rule violation detected' },
+{ name: 'Kms Decrypt', value: 'kms_decrypt', description: 'Data decrypted' },
+{ name: 'Kms Encrypt', value: 'kms_encrypt', description: 'Data encrypted' },
+{ name: 'Message Created', value: 'message_created' },
+{ name: 'Message Deleted', value: 'message_deleted' },
+{ name: 'Message Updated', value: 'message_updated', description: 'Message edited' },
+{ name: 'Reaction Created', value: 'reaction_created', description: 'Reaction added' },
+{ name: 'Reaction Deleted', value: 'reaction_deleted', description: 'Reaction removed' },
+{ name: 'Search Chats Api', value: 'search_chats_api', description: 'Chat search via API' },
+{ name: 'Search Messages Api', value: 'search_messages_api', description: 'Message search via API' },
+{ name: 'Search Users Api', value: 'search_users_api', description: 'Employee search via API' },
+{ name: 'Tag Added To Chat', value: 'tag_added_to_chat' },
+{ name: 'Tag Created', value: 'tag_created', description: 'New tag created' },
+{ name: 'Tag Deleted', value: 'tag_deleted' },
+{ name: 'Tag Removed From Chat', value: 'tag_removed_from_chat' },
+{ name: 'Thread Created', value: 'thread_created' },
+{ name: 'User 2fa Fail', value: 'user_2fa_fail', description: 'Failed two-factor authentication attempt' },
+{ name: 'User 2fa Success', value: 'user_2fa_success', description: 'Successful two-factor authentication' },
+{ name: 'User Added To Tag', value: 'user_added_to_tag' },
+{ name: 'User Chat Join', value: 'user_chat_join', description: 'User joined the chat' },
+{ name: 'User Chat Leave', value: 'user_chat_leave', description: 'User left the chat' },
+{ name: 'User Created', value: 'user_created', description: 'New user account created' },
+{ name: 'User Deleted', value: 'user_deleted', description: 'User account deleted' },
+{ name: 'User Login', value: 'user_login', description: 'User logged in successfully' },
+{ name: 'User Logout', value: 'user_logout', description: 'User logged out' },
+{ name: 'User Removed From Tag', value: 'user_removed_from_tag' },
+{ name: 'User Role Changed', value: 'user_role_changed' },
+{ name: 'User Updated', value: 'user_updated', description: 'User data updated' }],
+ default: "",
+ description: 'Filter by specific event type',
+ routing: { send: { type: 'query', property: 'event_key' } },
+ },
+ {
+ displayName: 'Start Time',
+ name: 'startTime',
+ type: 'dateTime',
+ default: "",
+ description: 'Start timestamp (inclusive)',
+ placeholder: '2025-05-01T09:11:00Z',
+ routing: { send: { type: 'query', property: 'start_time' } },
+ },
+ ],
+ },
+];
\ No newline at end of file
diff --git a/integrations/n8n/nodes/Pachca/V2/TaskDescription.ts b/integrations/n8n/nodes/Pachca/V2/TaskDescription.ts
new file mode 100644
index 00000000..c27fb141
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V2/TaskDescription.ts
@@ -0,0 +1,331 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const taskOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: { show: { resource: ['task'] } },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ action: 'Create a task',
+ },
+ {
+ name: 'Delete',
+ value: 'delete',
+ action: 'Delete a task',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ action: 'Get a task',
+ },
+ {
+ name: 'Get Many',
+ value: 'getAll',
+ action: 'Get many tasks',
+ },
+ {
+ name: 'Update',
+ value: 'update',
+ action: 'Update a task',
+ },
+ ],
+ default: 'create',
+ },
+];
+
+export const taskFields: INodeProperties[] = [
+ {
+ displayName: 'Kind',
+ name: 'taskKind',
+ type: 'options',
+ required: true,
+ options: [{ name: 'Call', value: 'call', description: 'Call a contact' },
+{ name: 'Email', value: 'email', description: 'Send an email' },
+{ name: 'Event', value: 'event' },
+{ name: 'Meeting', value: 'meeting' },
+{ name: 'Reminder', value: 'reminder', description: 'Simple reminder' }],
+ default: "call",
+ displayOptions: { show: { resource: ['task'], operation: ['create'] } },
+ routing: { send: { type: 'body', property: 'kind' } },
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: { show: { resource: ['task'], operation: ['create'] } },
+ options: [
+ {
+ displayName: 'All Day',
+ name: 'allDay',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to all-day task (without specific time)',
+ routing: { send: { type: 'body', property: 'all_day' } },
+ },
+ {
+ displayName: 'Chat ID',
+ name: 'chatId',
+ type: 'number',
+ default: 0,
+ description: 'ID of the chat to link the task to',
+ placeholder: '456',
+ routing: { send: { type: 'body', property: 'chat_id' } },
+ },
+ {
+ displayName: 'Content',
+ name: 'taskContent',
+ type: 'string',
+ typeOptions: { rows: 4 },
+ default: "",
+ description: 'Description (defaults to the kind name)',
+ placeholder: 'Pick up 21 orders from the warehouse',
+ routing: { send: { type: 'body', property: 'content' } },
+ },
+ {
+ displayName: 'Custom Properties',
+ name: 'customProperties',
+ type: 'fixedCollection',
+ typeOptions: { multipleValues: true },
+ options: [{
+ name: 'property',
+ displayName: 'Custom Property',
+ values: [
+ {
+ displayName: 'Custom Property Name or ID',
+ name: 'id',
+ type: 'options',
+ typeOptions: { loadOptionsMethod: 'getCustomProperties' },
+ default: '',
+ description: 'Choose from the list, or specify an ID using an expression',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: "",
+ description: 'Value to set',
+ },
+ ],
+ }],
+ default: [],
+ description: 'Custom properties to set',
+ routing: { send: { type: 'body', property: 'custom_properties' } },
+ },
+ {
+ displayName: 'Due At',
+ name: 'taskDueAt',
+ type: 'dateTime',
+ default: "",
+ description: 'Task due date (ISO-8601) in YYYY-MM-DDThh:mm:ss.sssTZD format. If the time is set to 23:59:59.000, the task will be created as an all-day task (without specific time).',
+ placeholder: '2020-06-05T12:00:00.000+03:00',
+ routing: { send: { type: 'body', property: 'due_at' } },
+ },
+ {
+ displayName: 'Performer IDs',
+ name: 'performerIds',
+ type: 'string',
+ default: "",
+ description: 'Array of user IDs to assign as task performers (defaults to you)',
+ placeholder: '12,13',
+ },
+ {
+ displayName: 'Priority',
+ name: 'taskPriority',
+ type: 'options',
+ options: [{ name: 'None', value: 0 },
+{ name: '1 — Normal', value: 1 },
+{ name: '2 — Important', value: 2 },
+{ name: '3 — Very Important', value: 3 }],
+ default: 0,
+ description: 'Priority: 1, 2 (important), or 3 (very important)',
+ placeholder: '2',
+ routing: { send: { type: 'body', property: 'priority' } },
+ },
+ ],
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ displayOptions: { show: { resource: ['task'], operation: ['getAll'] } },
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 50,
+ description: 'Max number of results to return',
+ typeOptions: { minValue: 1, maxValue: 50 },
+ displayOptions: { show: { resource: ['task'], operation: ['getAll'], returnAll: [false] } },
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['task'], operation: ['getAll'] } },
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['task'], operation: ['get'] } },
+ description: 'Task ID',
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['task'], operation: ['get'] } },
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['task'], operation: ['update'] } },
+ description: 'Task ID',
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: { show: { resource: ['task'], operation: ['update'] } },
+ options: [
+ {
+ displayName: 'All Day',
+ name: 'allDay',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to all-day task (without specific time)',
+ routing: { send: { type: 'body', property: 'all_day' } },
+ },
+ {
+ displayName: 'Content',
+ name: 'content',
+ type: 'string',
+ typeOptions: { rows: 4 },
+ default: "",
+ description: 'Description',
+ placeholder: 'Pick up 21 orders from the warehouse',
+ routing: { send: { type: 'body', property: 'content' } },
+ },
+ {
+ displayName: 'Custom Properties',
+ name: 'customProperties',
+ type: 'fixedCollection',
+ typeOptions: { multipleValues: true },
+ options: [{
+ name: 'property',
+ displayName: 'Custom Property',
+ values: [
+ {
+ displayName: 'Custom Property Name or ID',
+ name: 'id',
+ type: 'options',
+ typeOptions: { loadOptionsMethod: 'getCustomProperties' },
+ default: '',
+ description: 'Choose from the list, or specify an ID using an expression',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: "",
+ description: 'Value to set',
+ },
+ ],
+ }],
+ default: [],
+ description: 'Custom properties to set',
+ routing: { send: { type: 'body', property: 'custom_properties' } },
+ },
+ {
+ displayName: 'Done At',
+ name: 'doneAt',
+ type: 'dateTime',
+ default: "",
+ description: 'Task completion date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format',
+ placeholder: '2020-06-05T12:00:00.000Z',
+ routing: { send: { type: 'body', property: 'done_at' } },
+ },
+ {
+ displayName: 'Due At',
+ name: 'dueAt',
+ type: 'dateTime',
+ default: "",
+ description: 'Task due date (ISO-8601) in YYYY-MM-DDThh:mm:ss.sssTZD format. If the time is set to 23:59:59.000, the task will be created as an all-day task (without specific time).',
+ placeholder: '2020-06-05T12:00:00.000+03:00',
+ routing: { send: { type: 'body', property: 'due_at' } },
+ },
+ {
+ displayName: 'Kind',
+ name: 'kind',
+ type: 'options',
+ options: [{ name: 'Call', value: 'call', description: 'Call a contact' },
+{ name: 'Email', value: 'email', description: 'Send an email' },
+{ name: 'Event', value: 'event' },
+{ name: 'Meeting', value: 'meeting' },
+{ name: 'Reminder', value: 'reminder', description: 'Simple reminder' }],
+ default: "call",
+ routing: { send: { type: 'body', property: 'kind' } },
+ },
+ {
+ displayName: 'Performer IDs',
+ name: 'performerIds',
+ type: 'string',
+ default: "",
+ description: 'Array of user IDs to assign as task performers',
+ placeholder: '12',
+ },
+ {
+ displayName: 'Priority',
+ name: 'priority',
+ type: 'options',
+ options: [{ name: 'None', value: 0 },
+{ name: '1 — Normal', value: 1 },
+{ name: '2 — Important', value: 2 },
+{ name: '3 — Very Important', value: 3 }],
+ default: 0,
+ description: 'Priority: 1, 2 (important), or 3 (very important)',
+ placeholder: '2',
+ routing: { send: { type: 'body', property: 'priority' } },
+ },
+ {
+ displayName: 'Status',
+ name: 'status',
+ type: 'options',
+ options: [{ name: 'Done', value: 'done' },
+{ name: 'Undone', value: 'undone', description: 'Active' }],
+ default: "done",
+ routing: { send: { type: 'body', property: 'status' } },
+ },
+ ],
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['task'], operation: ['delete'] } },
+ description: 'Task ID',
+ },
+];
\ No newline at end of file
diff --git a/integrations/n8n/nodes/Pachca/V2/ThreadDescription.ts b/integrations/n8n/nodes/Pachca/V2/ThreadDescription.ts
new file mode 100644
index 00000000..cdf99d27
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V2/ThreadDescription.ts
@@ -0,0 +1,53 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const threadOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: { show: { resource: ['thread'] } },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ action: 'Create a thread',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ action: 'Get a thread',
+ },
+ ],
+ default: 'create',
+ },
+];
+
+export const threadFields: INodeProperties[] = [
+ {
+ displayName: 'ID',
+ name: 'threadMessageId',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['thread'], operation: ['create'] } },
+ description: 'Message ID',
+ },
+ {
+ displayName: 'ID',
+ name: 'threadThreadId',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['thread'], operation: ['get'] } },
+ description: 'Thread ID',
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['thread'], operation: ['get'] } },
+ },
+];
\ No newline at end of file
diff --git a/integrations/n8n/nodes/Pachca/V2/UserDescription.ts b/integrations/n8n/nodes/Pachca/V2/UserDescription.ts
new file mode 100644
index 00000000..af72c1d3
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/V2/UserDescription.ts
@@ -0,0 +1,634 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const userOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ displayOptions: { show: { resource: ['user'] } },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ action: 'Create a user',
+ },
+ {
+ name: 'Delete',
+ value: 'delete',
+ action: 'Delete a user',
+ },
+ {
+ name: 'Delete Avatar',
+ value: 'deleteAvatar',
+ action: 'Delete user avatar',
+ },
+ {
+ name: 'Delete Status',
+ value: 'deleteStatus',
+ action: 'Delete user status',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ action: 'Get a user',
+ },
+ {
+ name: 'Get Many',
+ value: 'getAll',
+ action: 'Get many users',
+ },
+ {
+ name: 'Get Status',
+ value: 'getStatus',
+ action: 'Get user status',
+ },
+ {
+ name: 'Update',
+ value: 'update',
+ action: 'Update a user',
+ },
+ {
+ name: 'Update Avatar',
+ value: 'updateAvatar',
+ action: 'Update user avatar',
+ },
+ {
+ name: 'Update Status',
+ value: 'updateStatus',
+ action: 'Update user status',
+ },
+ ],
+ default: 'getAll',
+ },
+];
+
+export const userFields: INodeProperties[] = [
+ {
+ displayName: 'Requires admin permissions',
+ name: 'userCreateNotice',
+ type: 'notice',
+ default: '',
+ displayOptions: { show: { resource: ['user'], operation: ['create'] } },
+ },
+ {
+ displayName: 'Email',
+ name: 'email',
+ type: 'string',
+ required: true,
+ default: "",
+ displayOptions: { show: { resource: ['user'], operation: ['create'] } },
+ placeholder: 'olegp@example.com',
+ routing: { send: { type: 'body', property: 'email' } },
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: { show: { resource: ['user'], operation: ['create'] } },
+ options: [
+ {
+ displayName: 'Custom Properties',
+ name: 'customProperties',
+ type: 'fixedCollection',
+ typeOptions: { multipleValues: true },
+ options: [{
+ name: 'property',
+ displayName: 'Custom Property',
+ values: [
+ {
+ displayName: 'Custom Property Name or ID',
+ name: 'id',
+ type: 'options',
+ typeOptions: { loadOptionsMethod: 'getCustomProperties' },
+ default: '',
+ description: 'Choose from the list, or specify an ID using an expression',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: "",
+ description: 'Value to set',
+ },
+ ],
+ }],
+ default: [],
+ description: 'Custom properties to set',
+ routing: { send: { type: 'body', property: 'custom_properties' } },
+ },
+ {
+ displayName: 'Department',
+ name: 'department',
+ type: 'string',
+ default: "",
+ placeholder: 'Product',
+ routing: { send: { type: 'body', property: 'department' } },
+ },
+ {
+ displayName: 'First Name',
+ name: 'firstName',
+ type: 'string',
+ default: "",
+ placeholder: 'Oleg',
+ routing: { send: { type: 'body', property: 'first_name' } },
+ },
+ {
+ displayName: 'Last Name',
+ name: 'lastName',
+ type: 'string',
+ default: "",
+ placeholder: 'Petrov',
+ routing: { send: { type: 'body', property: 'last_name' } },
+ },
+ {
+ displayName: 'List Tags',
+ name: 'listTags',
+ type: 'string',
+ default: "",
+ description: 'Array of tags to assign to the employee',
+ placeholder: 'Product,Design',
+ },
+ {
+ displayName: 'Nickname',
+ name: 'nickname',
+ type: 'string',
+ default: "",
+ description: 'Username',
+ placeholder: 'olegpetrov',
+ routing: { send: { type: 'body', property: 'nickname' } },
+ },
+ {
+ displayName: 'Phone Number',
+ name: 'phoneNumber',
+ type: 'string',
+ default: "",
+ placeholder: '+79001234567',
+ routing: { send: { type: 'body', property: 'phone_number' } },
+ },
+ {
+ displayName: 'Role',
+ name: 'role',
+ type: 'options',
+ options: [{ name: 'Admin', value: 'admin', description: 'Administrator' },
+{ name: 'Multi Guest', value: 'multi_guest', description: 'Multi-guest' },
+{ name: 'User', value: 'user', description: 'Employee' }],
+ default: "user",
+ description: 'Access level',
+ routing: { send: { type: 'body', property: 'role' } },
+ },
+ {
+ displayName: 'Skip Email Notify',
+ name: 'skipEmailNotify',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to skip sending an invitation to the employee. The employee will not receive an email invitation to create an account. Useful when pre-creating accounts before SSO login.',
+ routing: { send: { type: 'body', property: 'skip_email_notify' } },
+ },
+ {
+ displayName: 'Suspended',
+ name: 'suspended',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to user deactivated',
+ routing: { send: { type: 'body', property: 'suspended' } },
+ },
+ {
+ displayName: 'Title',
+ name: 'title',
+ type: 'string',
+ default: "",
+ description: 'Job title',
+ placeholder: 'CIO',
+ routing: { send: { type: 'body', property: 'title' } },
+ },
+ ],
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ displayOptions: { show: { resource: ['user'], operation: ['getAll'] } },
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 50,
+ description: 'Max number of results to return',
+ typeOptions: { minValue: 1, maxValue: 50 },
+ displayOptions: { show: { resource: ['user'], operation: ['getAll'], returnAll: [false] } },
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['user'], operation: ['getAll'] } },
+ },
+ {
+ displayName: 'Query',
+ name: 'query',
+ type: 'string',
+ default: "",
+ description: 'Search phrase to filter results. Search works on the following fields: `first_name`, `last_name`, `email`, `phone_number`, and `nickname`.',
+ placeholder: 'Oleg',
+ displayOptions: { show: { resource: ['user'], operation: ['getAll'] } },
+ routing: { send: { type: 'query', property: 'query' } },
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ description: 'User ID',
+ modes: [
+ {
+ displayName: 'From List',
+ name: 'list',
+ type: 'list',
+ typeOptions: { searchListMethod: 'searchUsers', searchable: true },
+ },
+ {
+ displayName: 'By ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 12',
+ },
+ {
+ displayName: 'By URL',
+ name: 'url',
+ type: 'string',
+ placeholder: 'https://app.pachca.com/users/12345',
+ extractValue: { type: 'regex', regex: 'https?://[^/]+/users/(\\d+)' },
+ validation: [{ type: 'regex', properties: { regex: 'https?://[^/]+/users/(\\d+)', errorMessage: 'Not a valid Pachca user URL' } }],
+ },
+ ],
+ displayOptions: { show: { resource: ['user'], operation: ['get'] } },
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['user'], operation: ['get'] } },
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ description: 'User ID',
+ modes: [
+ {
+ displayName: 'From List',
+ name: 'list',
+ type: 'list',
+ typeOptions: { searchListMethod: 'searchUsers', searchable: true },
+ },
+ {
+ displayName: 'By ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 12',
+ },
+ {
+ displayName: 'By URL',
+ name: 'url',
+ type: 'string',
+ placeholder: 'https://app.pachca.com/users/12345',
+ extractValue: { type: 'regex', regex: 'https?://[^/]+/users/(\\d+)' },
+ validation: [{ type: 'regex', properties: { regex: 'https?://[^/]+/users/(\\d+)', errorMessage: 'Not a valid Pachca user URL' } }],
+ },
+ ],
+ displayOptions: { show: { resource: ['user'], operation: ['update'] } },
+ },
+ {
+ displayName: 'Requires admin permissions',
+ name: 'userUpdateNotice',
+ type: 'notice',
+ default: '',
+ displayOptions: { show: { resource: ['user'], operation: ['update'] } },
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: { show: { resource: ['user'], operation: ['update'] } },
+ options: [
+ {
+ displayName: 'Custom Properties',
+ name: 'customProperties',
+ type: 'fixedCollection',
+ typeOptions: { multipleValues: true },
+ options: [{
+ name: 'property',
+ displayName: 'Custom Property',
+ values: [
+ {
+ displayName: 'Custom Property Name or ID',
+ name: 'id',
+ type: 'options',
+ typeOptions: { loadOptionsMethod: 'getCustomProperties' },
+ default: '',
+ description: 'Choose from the list, or specify an ID using an expression',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: "",
+ description: 'Value to set',
+ },
+ ],
+ }],
+ default: [],
+ description: 'Custom properties to set',
+ routing: { send: { type: 'body', property: 'custom_properties' } },
+ },
+ {
+ displayName: 'Department',
+ name: 'department',
+ type: 'string',
+ default: "",
+ placeholder: 'Engineering',
+ routing: { send: { type: 'body', property: 'department' } },
+ },
+ {
+ displayName: 'Email',
+ name: 'email',
+ type: 'string',
+ default: "",
+ placeholder: 'olegpetrov@example.com',
+ routing: { send: { type: 'body', property: 'email' } },
+ },
+ {
+ displayName: 'First Name',
+ name: 'firstName',
+ type: 'string',
+ default: "",
+ placeholder: 'Oleg',
+ routing: { send: { type: 'body', property: 'first_name' } },
+ },
+ {
+ displayName: 'Last Name',
+ name: 'lastName',
+ type: 'string',
+ default: "",
+ placeholder: 'Petrov',
+ routing: { send: { type: 'body', property: 'last_name' } },
+ },
+ {
+ displayName: 'List Tags',
+ name: 'listTags',
+ type: 'string',
+ default: "",
+ description: 'Array of tags to assign to the employee',
+ placeholder: 'Product',
+ },
+ {
+ displayName: 'Nickname',
+ name: 'nickname',
+ type: 'string',
+ default: "",
+ description: 'Username',
+ placeholder: 'olegpetrov',
+ routing: { send: { type: 'body', property: 'nickname' } },
+ },
+ {
+ displayName: 'Phone Number',
+ name: 'phoneNumber',
+ type: 'string',
+ default: "",
+ placeholder: '+79001234567',
+ routing: { send: { type: 'body', property: 'phone_number' } },
+ },
+ {
+ displayName: 'Role',
+ name: 'role',
+ type: 'options',
+ options: [{ name: 'Admin', value: 'admin', description: 'Administrator' },
+{ name: 'Multi Guest', value: 'multi_guest', description: 'Multi-guest' },
+{ name: 'User', value: 'user', description: 'Employee' }],
+ default: "user",
+ description: 'Access level',
+ routing: { send: { type: 'body', property: 'role' } },
+ },
+ {
+ displayName: 'Suspended',
+ name: 'suspended',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to user deactivated',
+ routing: { send: { type: 'body', property: 'suspended' } },
+ },
+ {
+ displayName: 'Title',
+ name: 'title',
+ type: 'string',
+ default: "",
+ description: 'Job title',
+ placeholder: 'Senior Developer',
+ routing: { send: { type: 'body', property: 'title' } },
+ },
+ ],
+ },
+ {
+ displayName: 'ID',
+ name: 'id',
+ type: 'resourceLocator',
+ default: { mode: 'list', value: '' },
+ required: true,
+ description: 'User ID',
+ modes: [
+ {
+ displayName: 'From List',
+ name: 'list',
+ type: 'list',
+ typeOptions: { searchListMethod: 'searchUsers', searchable: true },
+ },
+ {
+ displayName: 'By ID',
+ name: 'id',
+ type: 'string',
+ placeholder: 'e.g. 12',
+ },
+ {
+ displayName: 'By URL',
+ name: 'url',
+ type: 'string',
+ placeholder: 'https://app.pachca.com/users/12345',
+ extractValue: { type: 'regex', regex: 'https?://[^/]+/users/(\\d+)' },
+ validation: [{ type: 'regex', properties: { regex: 'https?://[^/]+/users/(\\d+)', errorMessage: 'Not a valid Pachca user URL' } }],
+ },
+ ],
+ displayOptions: { show: { resource: ['user'], operation: ['delete'] } },
+ },
+ {
+ displayName: 'Requires admin permissions',
+ name: 'userDeleteNotice',
+ type: 'notice',
+ default: '',
+ displayOptions: { show: { resource: ['user'], operation: ['delete'] } },
+ },
+ {
+ displayName: 'User ID',
+ name: 'userId',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['user'], operation: ['updateAvatar'] } },
+ },
+ {
+ displayName: 'Requires admin permissions',
+ name: 'userUpdateAvatarNotice',
+ type: 'notice',
+ default: '',
+ displayOptions: { show: { resource: ['user'], operation: ['updateAvatar'] } },
+ },
+ {
+ displayName: 'Input Binary Field',
+ name: 'image',
+ type: 'string',
+ required: true,
+ default: "data",
+ description: 'Name of the binary property containing the avatar image. Use a previous node (e.g. HTTP Request, Read Binary File) to load the image.',
+ displayOptions: { show: { resource: ['user'], operation: ['updateAvatar'] } },
+ },
+ {
+ displayName: 'User ID',
+ name: 'userId',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['user'], operation: ['deleteAvatar'] } },
+ },
+ {
+ displayName: 'Requires admin permissions',
+ name: 'userDeleteAvatarNotice',
+ type: 'notice',
+ default: '',
+ displayOptions: { show: { resource: ['user'], operation: ['deleteAvatar'] } },
+ },
+ {
+ displayName: 'User ID',
+ name: 'userId',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['user'], operation: ['getStatus'] } },
+ },
+ {
+ displayName: 'Requires admin permissions',
+ name: 'userGetStatusNotice',
+ type: 'notice',
+ default: '',
+ displayOptions: { show: { resource: ['user'], operation: ['getStatus'] } },
+ },
+ {
+ displayName: 'Simplify',
+ name: 'simplify',
+ type: 'boolean',
+ default: true,
+ description: 'Whether to return a simplified version of the response instead of all fields',
+ displayOptions: { show: { resource: ['user'], operation: ['getStatus'] } },
+ },
+ {
+ displayName: 'User ID',
+ name: 'userId',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['user'], operation: ['updateStatus'] } },
+ },
+ {
+ displayName: 'Requires admin permissions',
+ name: 'userUpdateStatusNotice',
+ type: 'notice',
+ default: '',
+ displayOptions: { show: { resource: ['user'], operation: ['updateStatus'] } },
+ },
+ {
+ displayName: 'Emoji',
+ name: 'emoji',
+ type: 'string',
+ required: true,
+ default: "",
+ description: 'Status emoji character',
+ displayOptions: { show: { resource: ['user'], operation: ['updateStatus'] } },
+ placeholder: '🎮',
+ routing: { send: { type: 'body', property: 'emoji' } },
+ },
+ {
+ displayName: 'Title',
+ name: 'title',
+ type: 'string',
+ required: true,
+ default: "",
+ description: 'Status text',
+ displayOptions: { show: { resource: ['user'], operation: ['updateStatus'] } },
+ placeholder: 'Very busy',
+ routing: { send: { type: 'body', property: 'title' } },
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: { show: { resource: ['user'], operation: ['updateStatus'] } },
+ options: [
+ {
+ displayName: 'Away Message',
+ name: 'awayMessage',
+ type: 'string',
+ default: "",
+ description: 'Away mode message text. Displayed in the profile and in direct messages/mentions.',
+ placeholder: 'Back after 3 PM',
+ routing: { send: { type: 'body', property: 'away_message' } },
+ },
+ {
+ displayName: 'Expires At',
+ name: 'expiresAt',
+ type: 'dateTime',
+ default: "",
+ description: 'Status expiration date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format',
+ placeholder: '2024-04-08T10:00:00.000Z',
+ routing: { send: { type: 'body', property: 'expires_at' } },
+ },
+ {
+ displayName: 'Is Away',
+ name: 'isAway',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to "Away" mode',
+ routing: { send: { type: 'body', property: 'is_away' } },
+ },
+ ],
+ },
+ {
+ displayName: 'User ID',
+ name: 'userId',
+ type: 'number',
+ required: true,
+ default: 0,
+ displayOptions: { show: { resource: ['user'], operation: ['deleteStatus'] } },
+ },
+ {
+ displayName: 'Requires admin permissions',
+ name: 'userDeleteStatusNotice',
+ type: 'notice',
+ default: '',
+ displayOptions: { show: { resource: ['user'], operation: ['deleteStatus'] } },
+ },
+];
\ No newline at end of file
diff --git a/integrations/n8n/nodes/Pachca/pachca.dark.svg b/integrations/n8n/nodes/Pachca/pachca.dark.svg
new file mode 100644
index 00000000..b34b4743
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/pachca.dark.svg
@@ -0,0 +1,3 @@
+
diff --git a/integrations/n8n/nodes/Pachca/pachca.svg b/integrations/n8n/nodes/Pachca/pachca.svg
new file mode 100644
index 00000000..21b5d0d2
--- /dev/null
+++ b/integrations/n8n/nodes/Pachca/pachca.svg
@@ -0,0 +1,3 @@
+
diff --git a/integrations/n8n/package.json b/integrations/n8n/package.json
new file mode 100644
index 00000000..610e3768
--- /dev/null
+++ b/integrations/n8n/package.json
@@ -0,0 +1,79 @@
+{
+ "name": "n8n-nodes-pachca",
+ "version": "2.0.0",
+ "description": "Pachca node for n8n workflow automation",
+ "license": "MIT",
+ "main": "index.js",
+ "author": {
+ "name": "Pachca",
+ "email": "dev@pachca.com"
+ },
+ "keywords": [
+ "n8n-community-node-package",
+ "pachca",
+ "messenger",
+ "automation",
+ "workflow",
+ "chat",
+ "messages",
+ "users",
+ "files",
+ "api",
+ "integration"
+ ],
+ "homepage": "https://github.com/pachca/openapi/tree/main/integrations/n8n#readme",
+ "bugs": {
+ "url": "https://github.com/pachca/openapi/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/pachca/openapi.git",
+ "directory": "integrations/n8n"
+ },
+ "files": [
+ "dist/",
+ "index.js",
+ "icons/",
+ "docs/",
+ "examples/"
+ ],
+ "n8n": {
+ "n8nNodesApiVersion": 1,
+ "strict": true,
+ "credentials": [
+ "dist/credentials/PachcaApi.credentials.js"
+ ],
+ "nodes": [
+ "dist/nodes/Pachca/Pachca.node.js",
+ "dist/nodes/Pachca/PachcaTrigger.node.js"
+ ]
+ },
+ "scripts": {
+ "generate-n8n": "tsx scripts/generate-n8n.ts && eslint --fix nodes/Pachca/V2/ nodes/Pachca/SharedRouter.ts nodes/Pachca/Pachca.node.ts credentials/ || true",
+ "build": "n8n-node build",
+ "dev:n8n": "n8n-node dev",
+ "lint": "eslint .",
+ "lint:fix": "eslint --fix .",
+ "test": "vitest run --exclude 'e2e/**'",
+ "e2e": "playwright test -c e2e/playwright.config.ts",
+ "e2e:headed": "playwright test -c e2e/playwright.config.ts --headed",
+ "e2e:debug": "playwright test -c e2e/playwright.config.ts --debug",
+ "release": "n8n-node release",
+ "prepublishOnly": "n8n-node prerelease"
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ },
+ "peerDependencies": {
+ "n8n-workflow": "*"
+ },
+ "devDependencies": {
+ "@n8n/node-cli": "^0.23.0",
+ "eslint": "^9.0.0",
+ "@pachca/openapi-parser": "workspace:*",
+ "n8n-workflow": "*",
+ "typescript": "^5.9.0",
+ "tsx": "^4.0.0",
+ "vitest": "^3.0.0"
+ }
+}
diff --git a/integrations/n8n/scripts/freeze-v1.ts b/integrations/n8n/scripts/freeze-v1.ts
new file mode 100644
index 00000000..1211abf8
--- /dev/null
+++ b/integrations/n8n/scripts/freeze-v1.ts
@@ -0,0 +1,312 @@
+#!/usr/bin/env tsx
+/**
+ * One-time script: generates frozen V1 description files from v1 compiled node.
+ * Reads /tmp/v1-description.json (extracted from n8n-nodes-pachca@1.0.27)
+ * and outputs nodes/Pachca/V1/*Description.ts files.
+ *
+ * Run: npx tsx scripts/freeze-v1.ts
+ * Then delete this script — V1 files are frozen forever.
+ */
+
+import * as fs from 'fs';
+import * as path from 'path';
+
+const V1_DIR = path.resolve(__dirname, '../nodes/Pachca/V1');
+const description = JSON.parse(fs.readFileSync('/tmp/v1-description.json', 'utf-8'));
+const properties: any[] = description.properties;
+
+// Resource name → PascalCase for file naming
+const RESOURCE_FILE_NAMES: Record = {
+ message: 'Message',
+ thread: 'Thread',
+ reactions: 'Reactions',
+ chat: 'Chat',
+ user: 'User',
+ groupTag: 'GroupTag',
+ status: 'Status',
+ customFields: 'CustomFields',
+ task: 'Task',
+ bot: 'Bot',
+ file: 'File',
+ form: 'Form',
+};
+
+// Serialize a value to TypeScript literal
+function toTS(value: unknown, indent: number = 0): string {
+ const pad = '\t'.repeat(indent);
+ const pad1 = '\t'.repeat(indent + 1);
+
+ if (value === null || value === undefined) return 'undefined';
+ if (typeof value === 'string') {
+ // Use single quotes, escape internal single quotes
+ const escaped = value.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n');
+ return `'${escaped}'`;
+ }
+ if (typeof value === 'number' || typeof value === 'boolean') return String(value);
+
+ if (Array.isArray(value)) {
+ if (value.length === 0) return '[]';
+ // Check if it's a simple array (all primitives)
+ const allPrimitive = value.every(v => typeof v !== 'object' || v === null);
+ if (allPrimitive) {
+ return `[${value.map(v => toTS(v)).join(', ')}]`;
+ }
+ const items = value.map(v => `${pad1}${toTS(v, indent + 1)},`).join('\n');
+ return `[\n${items}\n${pad}]`;
+ }
+
+ if (typeof value === 'object') {
+ const obj = value as Record;
+ const keys = Object.keys(obj);
+ if (keys.length === 0) return '{}';
+
+ const entries = keys.map(k => {
+ const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k) ? k : `'${k}'`;
+ return `${pad1}${key}: ${toTS(obj[k], indent + 1)},`;
+ }).join('\n');
+ return `{\n${entries}\n${pad}}`;
+ }
+
+ return String(value);
+}
+
+// Clean up property for output — remove undefined fields, normalize
+function cleanProp(prop: any): any {
+ const clean: any = {};
+ // Ordered keys for n8n convention
+ const keyOrder = [
+ 'displayName', 'name', 'type', 'required', 'noDataExpression',
+ 'typeOptions', 'options', 'default', 'description', 'placeholder',
+ 'displayOptions', 'routing', 'modes',
+ ];
+ for (const key of keyOrder) {
+ if (prop[key] !== undefined) {
+ clean[key] = prop[key];
+ }
+ }
+ // Any remaining keys
+ for (const key of Object.keys(prop)) {
+ if (!(key in clean) && prop[key] !== undefined) {
+ clean[key] = prop[key];
+ }
+ }
+ return clean;
+}
+
+// Group properties by resource
+const resourceProp = properties.find((p: any) => p.name === 'resource');
+const resources: string[] = resourceProp.options.map((o: any) => o.value);
+
+for (const resource of resources) {
+ const pascalName = RESOURCE_FILE_NAMES[resource];
+ if (!pascalName) {
+ console.error(`Unknown resource: ${resource}, skipping`);
+ continue;
+ }
+
+ // Find operation definition for this resource
+ const operationProps = properties.filter(
+ (p: any) => p.name === 'operation' && p.displayOptions?.show?.resource?.includes(resource),
+ );
+
+ // Find field definitions for this resource
+ const fieldProps = properties.filter(
+ (p: any) =>
+ p.name !== 'resource' &&
+ p.name !== 'operation' &&
+ p.displayOptions?.show?.resource?.includes(resource),
+ );
+
+ // Build the operations export
+ const opsVarName = `${resource}Operations`;
+ const fieldsVarName = `${resource}Fields`;
+
+ let output = `// ============================================================================\n`;
+ output += `// ${pascalName}Description.ts — FROZEN V1 description (from n8n-nodes-pachca@1.0.27)\n`;
+ output += `// DO NOT EDIT — this file is frozen for backward compatibility\n`;
+ output += `// ============================================================================\n\n`;
+ output += `import type { INodeProperties } from 'n8n-workflow';\n\n`;
+
+ // Operations
+ output += `export const ${opsVarName}: INodeProperties[] = [\n`;
+ for (const op of operationProps) {
+ output += `\t${toTS(cleanProp(op), 1)},\n`;
+ }
+ output += `];\n\n`;
+
+ // Fields
+ output += `export const ${fieldsVarName}: INodeProperties[] = [\n`;
+ for (const field of fieldProps) {
+ output += `\t${toTS(cleanProp(field), 1)},\n`;
+ }
+ output += `];\n`;
+
+ const filePath = path.join(V1_DIR, `${pascalName}Description.ts`);
+ fs.writeFileSync(filePath, output);
+ console.log(` ${pascalName}Description.ts — ${operationProps.length} ops, ${fieldProps.length} fields`);
+}
+
+// Now generate PachcaV1.node.ts
+const v1NodeContent = `// ============================================================================
+// PachcaV1.node.ts — FROZEN V1 node class (from n8n-nodes-pachca@1.0.27)
+// DO NOT EDIT — this file is frozen for backward compatibility
+// ============================================================================
+
+import type {
+\tINodeType,
+\tINodeTypeBaseDescription,
+\tINodeTypeDescription,
+\tIExecuteFunctions,
+\tINodeExecutionData,
+\tILoadOptionsFunctions,
+\tINodeListSearchResult,
+\tINodePropertyOptions,
+} from 'n8n-workflow';
+import { NodeConnectionTypes } from 'n8n-workflow';
+import { router } from '../SharedRouter';
+
+${resources.map(r => {
+ const pascal = RESOURCE_FILE_NAMES[r];
+ return `import { ${r}Operations, ${r}Fields } from './${pascal}Description';`;
+}).join('\n')}
+
+function formatUserName(u: { first_name: string; last_name: string; nickname: string }): string {
+\tconst fullName = [u.first_name, u.last_name]
+\t\t.filter((v) => v != null && v !== '' && v !== 'null')
+\t\t.join(' ');
+\tconst display = fullName || u.nickname || 'User';
+\treturn u.nickname ? \`\${display} (@\${u.nickname})\` : display;
+}
+
+export class PachcaV1 implements INodeType {
+\tdescription: INodeTypeDescription;
+
+\tconstructor(baseDescription: INodeTypeBaseDescription) {
+\t\tthis.description = {
+\t\t\t...baseDescription,
+\t\t\tversion: 1,
+\t\t\tdefaults: { name: 'Pachca' },
+\t\t\tinputs: [NodeConnectionTypes.Main],
+\t\t\toutputs: [NodeConnectionTypes.Main],
+\t\t\tcredentials: [{ name: 'pachcaApi', required: true }],
+\t\t\tproperties: [
+\t\t\t\t{
+\t\t\t\t\tdisplayName: 'Resource',
+\t\t\t\t\tname: 'resource',
+\t\t\t\t\ttype: 'options',
+\t\t\t\t\tnoDataExpression: true,
+\t\t\t\t\toptions: [
+${resourceProp.options.map((o: any) => `\t\t\t\t\t\t{ name: ${toTS(o.name)}, value: ${toTS(o.value)} },`).join('\n')}
+\t\t\t\t\t],
+\t\t\t\t\tdefault: 'message',
+\t\t\t\t},
+${resources.map(r => `\t\t\t\t...${r}Operations,\n\t\t\t\t...${r}Fields,`).join('\n')}
+\t\t\t],
+\t\t};
+\t}
+
+\tasync execute(this: IExecuteFunctions): Promise {
+\t\treturn router.call(this);
+\t}
+
+\tmethods = {
+\t\tlistSearch: {
+\t\t\tasync searchChats(this: ILoadOptionsFunctions, filter?: string): Promise {
+\t\t\t\tconst credentials = await this.getCredentials('pachcaApi');
+\t\t\t\tconst url = filter
+\t\t\t\t\t? \`\${credentials.baseUrl}/search/chats?query=\${encodeURIComponent(filter)}\`
+\t\t\t\t\t: \`\${credentials.baseUrl}/chats?per=50\`;
+\t\t\t\tconst response = await this.helpers.httpRequestWithAuthentication.call(this, 'pachcaApi', {
+\t\t\t\t\tmethod: 'GET',
+\t\t\t\t\turl,
+\t\t\t\t});
+\t\t\t\tconst items = response.data ?? [];
+\t\t\t\treturn {
+\t\t\t\t\tresults: items.map((c: { id: number; name: string }) => ({
+\t\t\t\t\t\tname: c.name,
+\t\t\t\t\t\tvalue: c.id,
+\t\t\t\t\t})),
+\t\t\t\t};
+\t\t\t},
+\t\t\tasync searchUsers(this: ILoadOptionsFunctions, filter?: string): Promise {
+\t\t\t\tconst credentials = await this.getCredentials('pachcaApi');
+\t\t\t\tif (!filter) return { results: [] };
+\t\t\t\tconst url = \`\${credentials.baseUrl}/search/users?query=\${encodeURIComponent(filter)}\`;
+\t\t\t\tconst response = await this.helpers.httpRequestWithAuthentication.call(this, 'pachcaApi', {
+\t\t\t\t\tmethod: 'GET',
+\t\t\t\t\turl,
+\t\t\t\t});
+\t\t\t\tconst items = response.data ?? [];
+\t\t\t\treturn {
+\t\t\t\t\tresults: items.map((u: { id: number; first_name: string; last_name: string; nickname: string }) => ({
+\t\t\t\t\t\tname: formatUserName(u),
+\t\t\t\t\t\tvalue: u.id,
+\t\t\t\t\t})),
+\t\t\t\t};
+\t\t\t},
+\t\t\tasync searchEntities(this: ILoadOptionsFunctions, filter?: string): Promise {
+\t\t\t\tlet entityType = 'discussion';
+\t\t\t\ttry {
+\t\t\t\t\tentityType = (this.getNodeParameter('entityType') as string) || 'discussion';
+\t\t\t\t} catch {
+\t\t\t\t\ttry {
+\t\t\t\t\t\tentityType = (this.getCurrentNodeParameter('entityType') as string) || 'discussion';
+\t\t\t\t\t} catch { /* parameter may not exist yet */ }
+\t\t\t\t}
+\t\t\t\tconst credentials = await this.getCredentials('pachcaApi');
+\t\t\t\tif (entityType === 'user') {
+\t\t\t\t\tif (!filter) return { results: [] };
+\t\t\t\t\tconst url = \`\${credentials.baseUrl}/search/users?query=\${encodeURIComponent(filter)}\`;
+\t\t\t\t\tconst response = await this.helpers.httpRequestWithAuthentication.call(this, 'pachcaApi', { method: 'GET', url });
+\t\t\t\t\tconst items = response.data ?? [];
+\t\t\t\t\treturn {
+\t\t\t\t\t\tresults: items.map((u: { id: number; first_name: string; last_name: string; nickname: string }) => ({
+\t\t\t\t\t\t\tname: formatUserName(u),
+\t\t\t\t\t\t\tvalue: u.id,
+\t\t\t\t\t\t})),
+\t\t\t\t\t};
+\t\t\t\t}
+\t\t\t\tif (entityType === 'thread') {
+\t\t\t\t\treturn { results: [] };
+\t\t\t\t}
+\t\t\t\tconst url = filter
+\t\t\t\t\t? \`\${credentials.baseUrl}/search/chats?query=\${encodeURIComponent(filter)}\`
+\t\t\t\t\t: \`\${credentials.baseUrl}/chats?per=50\`;
+\t\t\t\tconst response = await this.helpers.httpRequestWithAuthentication.call(this, 'pachcaApi', { method: 'GET', url });
+\t\t\t\tconst items = response.data ?? [];
+\t\t\t\treturn {
+\t\t\t\t\tresults: items.map((c: { id: number; name: string }) => ({
+\t\t\t\t\t\tname: c.name,
+\t\t\t\t\t\tvalue: c.id,
+\t\t\t\t\t})),
+\t\t\t\t};
+\t\t\t},
+\t\t},
+\t\tloadOptions: {
+\t\t\tasync getCustomProperties(this: ILoadOptionsFunctions): Promise {
+\t\t\t\tconst credentials = await this.getCredentials('pachcaApi');
+\t\t\t\tconst resource = this.getNodeParameter('resource') as string;
+\t\t\t\tconst entityType = resource === 'task' ? 'Task' : 'User';
+\t\t\t\ttry {
+\t\t\t\t\tconst response = await this.helpers.httpRequestWithAuthentication.call(this, 'pachcaApi', {
+\t\t\t\t\t\tmethod: 'GET',
+\t\t\t\t\t\turl: \`\${credentials.baseUrl}/custom_properties?entity_type=\${entityType}\`,
+\t\t\t\t\t});
+\t\t\t\t\tconst items = response.data ?? [];
+\t\t\t\t\treturn items.map((p: { id: number; name: string }) => ({
+\t\t\t\t\t\tname: p.name,
+\t\t\t\t\t\tvalue: p.id,
+\t\t\t\t\t}));
+\t\t\t\t} catch {
+\t\t\t\t\treturn [];
+\t\t\t\t}
+\t\t\t},
+\t\t},
+\t};
+}
+`;
+
+fs.writeFileSync(path.join(V1_DIR, 'PachcaV1.node.ts'), v1NodeContent);
+console.log(' PachcaV1.node.ts — wrapper with execute + loadOptions');
+
+console.log('\nDone! V1 files frozen in', V1_DIR);
diff --git a/integrations/n8n/scripts/generate-n8n.ts b/integrations/n8n/scripts/generate-n8n.ts
new file mode 100644
index 00000000..98ced762
--- /dev/null
+++ b/integrations/n8n/scripts/generate-n8n.ts
@@ -0,0 +1,3319 @@
+#!/usr/bin/env tsx
+/**
+ * n8n Node Generator — produces declarative n8n node files from OpenAPI spec.
+ *
+ * Sources:
+ * - @pachca/openapi-parser → ParsedAPI (endpoints, schemas, tags)
+ * - packages/spec/workflows.ts → English descriptions
+ * - packages/generator/src/naming.ts → case conversion utilities
+ * - apps/docs/lib/openapi/mapper.ts → groupEndpointsByTag
+ *
+ * Output:
+ * - nodes/Pachca/*Description.ts (16 resource files)
+ * - nodes/Pachca/Pachca.node.ts (main node)
+ * - credentials/PachcaApi.credentials.ts
+ */
+
+import * as fs from 'fs';
+import * as path from 'path';
+import { parseOpenAPI, resolveAllOf, getSchemaType } from '@pachca/openapi-parser';
+import type { Endpoint, Schema, Parameter } from '@pachca/openapi-parser';
+import { snakeToCamel, snakeToPascal } from '../../../packages/generator/src/naming.js';
+import { extractBodyFields, getWrapperKey, type BodyField } from './utils.js';
+import { generateUrlFromOperation } from '../../../apps/docs/lib/openapi/mapper.js';
+import { generateExample } from '../../../apps/docs/lib/openapi/example-generator.js';
+
+// ============================================================================
+// PATHS
+// ============================================================================
+const ROOT = path.resolve(__dirname, '../../..');
+const EN_SPEC_PATH = path.join(ROOT, 'packages/spec/openapi.en.yaml');
+const OUTPUT_DIR = path.resolve(__dirname, '../nodes/Pachca/V2');
+const CREDS_DIR = path.resolve(__dirname, '../credentials');
+
+// ============================================================================
+// V1 COMPATIBILITY TABLES
+// ============================================================================
+
+/** v2 resource value → v1 resource value (for resources that were renamed) */
+const V1_COMPAT_RESOURCES: Record = {
+ customProperty: 'customFields',
+ profile: 'status',
+ reaction: 'reactions',
+};
+
+/** v2 operation value → v1 operation value (per resource) */
+const V1_COMPAT_OPS: Record> = {
+ message: { create: 'send', get: 'getById' },
+ user: { get: 'getById' },
+ chat: { get: 'getById' },
+ groupTag: { get: 'getById', getAllUsers: 'getUsers' },
+ profile: { get: 'getProfile' },
+ customProperty: { get: 'getCustomProperties' },
+ reaction: { create: 'addReaction', delete: 'deleteReaction', getAll: 'getReactions' },
+ thread: { create: 'createThread', get: 'getThread' },
+ form: { create: 'createView' },
+ file: { create: 'upload' },
+};
+
+/** Alias operations in old resources — shown only for @version:[1] */
+const V1_ALIAS_OPS: Record = {
+ message: ['getReadMembers', 'unfurl'],
+ chat: ['getMembers', 'addUsers', 'removeUser', 'updateRole', 'leaveChat'],
+ groupTag: ['addTags', 'removeTag'],
+};
+
+/** v1 parameter name overrides (resource-prefixed names) */
+const V1_COMPAT_PARAMS: Record>> = {
+ reactions: {
+ '*': { id: 'reactionsMessageId', code: 'reactionsReactionCode' },
+ },
+ thread: {
+ createThread: { id: 'threadMessageId' },
+ getThread: { id: 'threadThreadId' },
+ },
+ groupTag: {
+ '*': { id: 'groupTagId' },
+ create: { name: 'groupTagName' },
+ update: { name: 'groupTagName' },
+ addTags: { chatId: 'groupTagChatId', groupTagIds: 'groupTagIds' },
+ removeTag: { chatId: 'groupTagChatId', tagId: 'tagId' },
+ getUsers: { id: 'groupTagId' },
+ },
+ chat: {
+ create: { name: 'chatName' },
+ update: { name: 'chatName' },
+ },
+ task: {
+ create: { kind: 'taskKind', content: 'taskContent', dueAt: 'taskDueAt', priority: 'taskPriority' },
+ },
+ status: {
+ updateStatus: { emoji: 'statusEmoji', title: 'statusTitle', expiresAt: 'statusExpiresAt' },
+ },
+ bot: {
+ update: { id: 'botId', outgoingUrl: 'webhookUrl' },
+ },
+ form: {
+ createView: { title: 'formTitle', blocks: 'formBlocks', builderMode: 'formBuilderMode' },
+ },
+};
+
+/** v1 collection name overrides */
+const V1_COMPAT_COLLECTIONS: Record>> = {
+ user: {
+ getAll: { additionalFields: 'additionalOptions' },
+ },
+ chat: {
+ getMembers: { additionalFields: 'chatMembersOptions' },
+ },
+ message: {
+ getReadMembers: { additionalFields: 'readMembersOptions' },
+ },
+ reactions: {
+ getReactions: { additionalFields: 'reactionsOptions' },
+ },
+};
+
+/** v1 sub-collection names for fixedCollections */
+const V1_COMPAT_SUBCOLLECTIONS: Record = {
+ buttons: 'button',
+ files: 'file',
+ customProperties: 'property',
+ formBlocks: 'block',
+ linkPreviews: 'preview',
+ options: 'option',
+};
+
+/** v1 alias operations: optional query params (not from OpenAPI) */
+const V1_ALIAS_QUERY_PARAMS: Record> = {
+ chat: {
+ getMembers: [['role', 'role'], ['limit', 'limit'], ['cursor', 'cursor']],
+ },
+ message: {
+ getReadMembers: [['per', 'readMembersPer'], ['page', 'readMembersPage']],
+ },
+ reactions: {
+ getReactions: [['limit', 'reactionsPer'], ['cursor', 'reactionsCursor']],
+ },
+};
+
+/** v1 compat: pagination fields from V1 collections → API query params (for primary ops, not aliases) */
+const V1_COMPAT_PAGINATION: Record> = {
+ chat: {
+ getMembers: [['limit', 'limit'], ['cursor', 'cursor']],
+ },
+ message: {
+ getReadMembers: [['per', 'readMembersPer'], ['page', 'readMembersPage']],
+ },
+ reaction: {
+ getAll: [['limit', 'reactionsPer'], ['cursor', 'reactionsCursor']],
+ },
+};
+
+/** v1 alias operations: special handlers */
+const V1_ALIAS_SPECIALS: Record> = {
+ message: {
+ unfurl: 'unfurlLinkPreviews',
+ },
+};
+
+/** Extra body fields for v2 operations not in OpenAPI (v1 compat) */
+const V1_EXTRA_BODY_FIELDS: Record> = {
+};
+
+/** Resources only visible in v2 (new, not in v1) */
+const V2_ONLY_RESOURCES = new Set(['member', 'readMember', 'linkPreview', 'search', 'security', 'export']);
+
+/** Resources whose endpoints are sub-paths of another resource but should use standard CRUD names */
+const STANDARD_CRUD_SUBRESOURCES = new Set(['reaction', 'member', 'readMember', 'linkPreview', 'thread', 'export']);
+
+/** Preferred default operation per resource (for better UX — list-first resources default to getAll) */
+const PREFERRED_DEFAULT_OPS: Record = {
+ chat: 'getAll',
+ groupTag: 'getAll',
+ user: 'getAll',
+ member: 'getAll',
+};
+
+/** v1 ID parameter fallback — shared ops where v1 used prefixed names (chatId, messageId, userId) */
+const V1_ID_FALLBACKS: Record = {
+ chat: { v1Name: 'chatId', sharedOps: ['get', 'getById', 'update', 'archive', 'unarchive'] },
+ message: { v1Name: 'messageId', sharedOps: ['get', 'getById', 'update', 'delete', 'pin', 'unpin'] },
+ user: { v1Name: 'userId', sharedOps: ['get', 'getById', 'update', 'delete'] },
+};
+
+/** Routing for v1 alias operations (cross-resource, @version:[1] only) */
+const V1_ALIAS_ROUTING: Record> = {
+ chat: {
+ getMembers: { method: 'GET', url: '=/chats/{{$parameter["chatId"]}}/members', pagination: true },
+ addUsers: { method: 'POST', url: '=/chats/{{$parameter["chatId"]}}/members', splitComma: [['memberIds', 'member_ids', 'int']] },
+ removeUser: { method: 'DELETE', url: '=/chats/{{$parameter["chatId"]}}/members/{{$parameter["userId"]}}' },
+ updateRole: { method: 'PUT', url: '=/chats/{{$parameter["chatId"]}}/members/{{$parameter["userId"]}}' },
+ leaveChat: { method: 'DELETE', url: '=/chats/{{$parameter["chatId"]}}/leave' },
+ },
+ message: {
+ getReadMembers: { method: 'GET', url: '=/messages/{{$parameter["messageId"]}}/read_member_ids', pagination: true },
+ unfurl: { method: 'POST', url: '=/messages/{{$parameter["messageId"]}}/link_previews' },
+ },
+ groupTag: {
+ addTags: { method: 'POST', url: '=/chats/{{$parameter["groupTagChatId"]}}/group_tags', splitComma: [['groupTagIds', 'group_tag_ids', 'int']] },
+ removeTag: { method: 'DELETE', url: '=/chats/{{$parameter["groupTagChatId"]}}/group_tags/{{$parameter["tagId"]}}' },
+ },
+};
+
+/** Field definitions for V1_ALIAS_OPS — these operations get no fields from the OpenAPI loop */
+type AliasFieldDef = {
+ name: string;
+ displayName: string;
+ type: 'number' | 'string' | 'boolean' | 'options';
+ required?: boolean;
+ default?: unknown;
+ description?: string;
+ placeholder?: string;
+ options?: { name: string; value: string }[];
+ routing?: { send: { type: 'body' | 'query'; property: string } };
+};
+
+const V1_ALIAS_FIELDS: Record> = {
+ chat: {
+ getMembers: {
+ pagination: true,
+ fields: [
+ { name: 'chatId', displayName: 'Chat ID', type: 'number', required: true, default: 0, description: 'ID of the chat' },
+ ],
+ },
+ addUsers: {
+ fields: [
+ { name: 'chatId', displayName: 'Chat ID', type: 'number', required: true, default: 0, description: 'ID of the chat' },
+ { name: 'memberIds', displayName: 'Member IDs', type: 'string', required: true, default: '', description: 'Comma-separated list of user IDs to add', placeholder: '186,187' },
+ { name: 'silent', displayName: 'Silent', type: 'boolean', default: false, description: 'Whether to skip creating a system message about adding members', routing: { send: { type: 'body', property: 'silent' } } },
+ ],
+ },
+ removeUser: {
+ fields: [
+ { name: 'chatId', displayName: 'Chat ID', type: 'number', required: true, default: 0, description: 'ID of the chat' },
+ { name: 'userId', displayName: 'User ID', type: 'number', required: true, default: 0, description: 'ID of the user to remove' },
+ ],
+ },
+ updateRole: {
+ fields: [
+ { name: 'chatId', displayName: 'Chat ID', type: 'number', required: true, default: 0, description: 'ID of the chat' },
+ { name: 'userId', displayName: 'User ID', type: 'number', required: true, default: 0, description: 'ID of the user' },
+ { name: 'newRole', displayName: 'Role', type: 'options', required: true, default: 'member',
+ options: [{ name: 'Admin', value: 'admin' }, { name: 'Editor', value: 'editor' }, { name: 'Member', value: 'member' }],
+ routing: { send: { type: 'body', property: 'role' } } },
+ ],
+ },
+ leaveChat: {
+ fields: [
+ { name: 'chatId', displayName: 'Chat ID', type: 'number', required: true, default: 0, description: 'ID of the chat' },
+ ],
+ },
+ },
+ message: {
+ getReadMembers: {
+ pagination: true,
+ fields: [
+ { name: 'messageId', displayName: 'Message ID', type: 'number', required: true, default: 0, description: 'ID of the message' },
+ ],
+ },
+ unfurl: {
+ fields: [
+ { name: 'messageId', displayName: 'Message ID', type: 'number', required: true, default: 0, description: 'ID of the message' },
+ { name: 'linkPreviews', displayName: 'Link Previews', type: 'json', required: true, default: '',
+ description: 'JSON map of link previews where each key is a URL',
+ placeholder: '{"https://example.com":{"title":"Example","description":"Desc"}}',
+ routing: { send: { type: 'body', property: 'link_previews' } } },
+ ],
+ },
+ },
+ groupTag: {
+ addTags: {
+ fields: [
+ { name: 'groupTagChatId', displayName: 'Chat ID', type: 'number', required: true, default: 0, description: 'ID of the chat to add tags to' },
+ { name: 'groupTagIds', displayName: 'Tag IDs', type: 'string', required: true, default: '', description: 'Comma-separated list of group tag IDs', placeholder: '1,2,3' },
+ ],
+ },
+ removeTag: {
+ fields: [
+ { name: 'groupTagChatId', displayName: 'Chat ID', type: 'number', required: true, default: 0, description: 'ID of the chat' },
+ { name: 'tagId', displayName: 'Tag ID', type: 'number', required: true, default: 0, description: 'ID of the tag to remove' },
+ ],
+ },
+ },
+};
+
+/** Optional fields promoted to top-level (not inside additionalFields) for ALL versions */
+const PROMOTED_TOP_LEVEL_FIELDS: Record>> = {
+ message: { create: new Set(['entity_type']), send: new Set(['entity_type']) },
+};
+
+// ============================================================================
+// WEBHOOK EVENT MAPPING (for PachcaTrigger generation)
+// ============================================================================
+
+/** Maps (type:event) from webhook payload schemas to user-friendly n8n event options */
+const WEBHOOK_EVENT_MAP: Record = {
+ 'message:new': { name: 'New Message', value: 'new_message' },
+ 'message:update': { name: 'Message Updated', value: 'message_updated' },
+ 'message:delete': { name: 'Message Deleted', value: 'message_deleted' },
+ 'message:link_shared': { name: 'Link Shared', value: 'link_shared' },
+ 'reaction:new': { name: 'New Reaction', value: 'new_reaction' },
+ 'reaction:delete': { name: 'Reaction Deleted', value: 'reaction_deleted' },
+ 'button:click': { name: 'Button Pressed', value: 'button_pressed' },
+ 'view:submit': { name: 'Form Submitted', value: 'form_submitted' },
+ 'chat_member:add': { name: 'Chat Member Added', value: 'chat_member_added' },
+ 'chat_member:remove': { name: 'Chat Member Removed', value: 'chat_member_removed' },
+ 'company_member:invite': { name: 'User Invited', value: 'company_member_invite' },
+ 'company_member:confirm': { name: 'User Confirmed', value: 'company_member_confirm' },
+ 'company_member:update': { name: 'User Updated', value: 'company_member_update' },
+ 'company_member:suspend': { name: 'User Suspended', value: 'company_member_suspend' },
+ 'company_member:activate': { name: 'User Activated', value: 'company_member_activate' },
+ 'company_member:delete': { name: 'User Deleted', value: 'company_member_delete' },
+};
+
+/** Fallback placeholders for fields without OpenAPI examples */
+const FIELD_PLACEHOLDERS: Record = {
+ blocks: '[{"type":"input","name":"field_1","label":"Your name"}]',
+};
+
+/** fixedCollection sub-fields that should use loadOptions instead of manual input */
+const LOAD_OPTIONS_SUBFIELDS: Record> = {
+ custom_properties: {
+ id: { method: 'getCustomProperties', displayName: 'Custom Property Name or ID', description: 'Choose from the list, or specify an ID using an expression' },
+ },
+};
+
+/** Notices shown above operation fields — informational tips for specific operations */
+// Scope-to-roles mapping — loaded from x-scope-roles in TokenScope schema at runtime
+let scopeRolesMap = new Map();
+const ALL_ROLES = ['owner', 'admin', 'user', 'bot'];
+
+/** Build a notice from endpoint requirements (scope → roles, plan) */
+function buildOperationNotice(ep: Endpoint): string | undefined {
+ const scope = ep.requirements?.scope;
+ const plan = ep.requirements?.plan;
+ if (!scope && !plan) return undefined;
+
+ let rolePart = '';
+ if (scope) {
+ const scopeKey = scope.replace(/:/g, '_');
+ const roles = scopeRolesMap.get(scopeKey);
+ if (roles && roles.length < ALL_ROLES.length) {
+ if (roles.length === 1 && roles[0] === 'owner') {
+ rolePart = 'owner role';
+ } else {
+ const missing = ALL_ROLES.filter(r => !roles.includes(r));
+ if (missing.includes('user') && missing.includes('bot')) {
+ rolePart = 'admin permissions';
+ } else {
+ rolePart = `${roles.join(', ')} roles`;
+ }
+ }
+ }
+ }
+
+ let planPart = '';
+ if (plan) {
+ const planName = plan.charAt(0).toUpperCase() + plan.slice(1);
+ planPart = `"${planName}" plan`;
+ }
+
+ if (!rolePart && !planPart) return undefined;
+
+ if (rolePart && planPart) return `Requires ${rolePart} and the ${planPart}`;
+ if (rolePart) return `Requires ${rolePart}`;
+ return `Requires the ${planPart}`;
+}
+
+/** URL extraction patterns for resourceLocator url mode (search method → regex + placeholder) */
+const URL_PATTERNS: Record = {
+ searchChats: {
+ regex: 'https?://[^/]+/chats/(\\d+)',
+ placeholder: 'https://app.pachca.com/chats/12345',
+ error: 'Not a valid Pachca chat URL',
+ },
+ searchUsers: {
+ regex: 'https?://[^/]+/users/(\\d+)',
+ placeholder: 'https://app.pachca.com/users/12345',
+ error: 'Not a valid Pachca user URL',
+ },
+};
+
+/** Body fields that should use resourceLocator with searchable dropdown (resource.field → searchMethod) */
+const BODY_FIELD_SEARCH: Record> = {
+ message: { entity_id: 'searchEntities' }, // dynamic: searches chats or users based on entityType
+};
+
+/** Path parameters that use resourceLocator with searchable dropdown */
+const PATH_PARAM_SEARCH: Record> = {
+ chat: { id: 'searchChats' },
+ user: { id: 'searchUsers' },
+ member: { id: 'searchChats', user_id: 'searchUsers' }, // member's `id` is chat_id, `user_id` is user
+};
+
+/** Query parameters that use resourceLocator in v2 */
+const SEARCHABLE_QUERY_PARAMS: Record = {
+ chat_id: { method: 'searchChats' },
+ user_id: { method: 'searchUsers' },
+};
+
+/** Hints for fields — contextual tips for no-code users (resource.field → hint) */
+const FIELD_HINTS: Record> = {
+ message: {
+ files: 'Upload a file first using File > Create, then use the returned key here',
+ },
+ form: {
+ trigger_id: 'Trigger ID from a button press webhook event — expires in 3 seconds',
+ },
+ export: {
+ webhook_url: 'Set this to a Webhook node URL in another workflow to receive the export-ready notification',
+ },
+};
+
+// EN description lookup maps (populated from openapi.en.yaml in main())
+let enParamDescs = new Map(); // key: `${endpointId}:${paramName}`
+let enBodyDescs = new Map(); // key: `${endpointId}:${fieldName}`
+let enSubFieldDescs = new Map(); // key: `${endpointId}:${fieldName}:${subName}`
+let enEnumDescs = new Map>(); // key: `${endpointId}:${fieldName}`
+let enEndpoints = new Map(); // key: endpointId
+
+/** Get English enum descriptions for a field from EN spec */
+function getEnumDescriptions(fieldName: string, endpointId: string): Record | undefined {
+ return enEnumDescs.get(`${endpointId}:${fieldName}`);
+}
+
+/** Get English field description from EN spec, with fallback to original (Russian) description */
+function getFieldDescription(fieldName: string, fallback: string | undefined, endpointId: string, parentField?: string): string | undefined {
+ if (parentField) {
+ const desc = enSubFieldDescs.get(`${endpointId}:${parentField}:${fieldName}`);
+ if (desc) return desc;
+ }
+ return enParamDescs.get(`${endpointId}:${fieldName}`)
+ ?? enBodyDescs.get(`${endpointId}:${fieldName}`)
+ ?? fallback;
+}
+
+// ============================================================================
+// TAG → RESOURCE MAPPING
+// ============================================================================
+
+/** OpenAPI tag → n8n resource value (singular, camelCase) */
+function tagToResource(tag: string): string {
+ const MAP: Record = {
+ 'Users': 'user',
+ 'Messages': 'message',
+ 'Chats': 'chat',
+ 'Members': 'member',
+ 'Threads': 'thread',
+ 'Reactions': 'reaction',
+ 'Group tags': 'groupTag',
+ 'Profile': 'profile',
+ 'Common': 'common',
+ 'Tasks': 'task',
+ 'Bots': 'bot',
+ 'Views': 'form',
+ 'Read members': 'readMember',
+ 'Link Previews': 'linkPreview',
+ 'Search': 'search',
+ 'Security': 'security',
+ };
+ return MAP[tag] || tag.toLowerCase().replace(/s$/, '');
+}
+
+/** Resource value → display name (Title Case, singular) */
+function resourceDisplayName(resource: string): string {
+ const MAP: Record = {
+ user: 'User', message: 'Message', chat: 'Chat', member: 'Chat Member',
+ thread: 'Thread', reaction: 'Reaction', groupTag: 'Group Tag',
+ profile: 'Profile', customProperty: 'Custom Property', task: 'Task',
+ bot: 'Bot', file: 'File', form: 'Form', readMember: 'Read Member',
+ linkPreview: 'Link Preview', search: 'Search', security: 'Security',
+ export: 'Chat Export',
+ };
+ return MAP[resource] || snakeToPascal(resource);
+}
+
+// ============================================================================
+// OPERATION MAPPING
+// ============================================================================
+
+/** Map HTTP method + path pattern → n8n operation value */
+function endpointToOperation(ep: Endpoint, resource: string): string {
+ const method = ep.method;
+ const segments = ep.path.split('/').filter(Boolean);
+ const staticSegments = segments.filter(s => !s.startsWith('{'));
+ const lastStatic = staticSegments[staticSegments.length - 1];
+ const hasTrailingParam = segments[segments.length - 1]?.startsWith('{') && staticSegments.length > 1;
+
+ // Special action paths
+ if (lastStatic === 'pin') return method === 'POST' ? 'pin' : 'unpin';
+ if (lastStatic === 'archive') return 'archive';
+ if (lastStatic === 'unarchive') return 'unarchive';
+ if (lastStatic === 'leave') return 'leave';
+ // /views/open is the primary create operation for forms
+ if (ep.path === '/views/open' && method === 'POST') return 'create';
+
+ // Sub-resource action paths (e.g., /users/{id}/status → getStatus, updateStatus)
+ // When last static segment differs from the resource root and is NOT a CRUD collection
+ if (staticSegments.length > 1) {
+ const resourceRoot = staticSegments[0];
+ if (lastStatic !== resourceRoot) {
+ // Check if this is a "standard CRUD sub-resource" — endpoints live under another
+ // resource's path but should use simple CRUD names (e.g., /messages/{id}/reactions → create/delete/getAll)
+ const lastStaticCamel = snakeToCamel(lastStatic);
+ const resourcePlural = resource.endsWith('y') ? resource.slice(0, -1) + 'ies' : resource + 's';
+ if (STANDARD_CRUD_SUBRESOURCES.has(resource) && (lastStaticCamel === resourcePlural || lastStaticCamel === resource)) {
+ if (hasTrailingParam) {
+ if (method === 'DELETE') return 'delete';
+ if (method === 'PUT' || method === 'PATCH') return 'update';
+ return 'get';
+ }
+ if (method === 'GET') return 'getAll';
+ if (method === 'POST') return 'create';
+ if (method === 'PUT') return 'update';
+ if (method === 'DELETE') return 'delete';
+ }
+
+ const subName = snakeToPascal(lastStatic);
+
+ // Single item sub-resource with trailing param (e.g., /chats/{id}/members/{user_id})
+ if (hasTrailingParam) {
+ if (method === 'DELETE') return `remove${subName}`;
+ if (method === 'PUT' || method === 'PATCH') return `update${subName}`;
+ return `get${subName}`;
+ }
+
+ // Collection sub-resource (e.g., /chats/{id}/members, /users/{id}/status)
+ if (method === 'GET') {
+ const hasCursor = ep.parameters.some(p => p.in === 'query' && p.name === 'cursor');
+ return hasCursor ? `getAll${subName}` : `get${subName}`;
+ }
+ if (method === 'POST') return `add${subName}`;
+ if (method === 'PUT') return `update${subName}`;
+ if (method === 'DELETE') return `delete${subName}`;
+ }
+ }
+
+ // Standard CRUD
+ if (method === 'GET' && !segments.some(s => s.startsWith('{'))) {
+ const hasCursor = ep.parameters.some(p => p.in === 'query' && p.name === 'cursor');
+ return hasCursor ? 'getAll' : 'get';
+ }
+ if (method === 'GET') return 'get';
+ if (method === 'POST') return 'create';
+ if (method === 'PUT' || method === 'PATCH') return 'update';
+ if (method === 'DELETE') return 'delete';
+
+ return 'execute';
+}
+
+/** Get the v1-compatible operation value */
+function getV1OpValue(resource: string, v2Op: string): string {
+ return V1_COMPAT_OPS[resource]?.[v2Op] ?? v2Op;
+}
+
+/** Generate n8n operation display name */
+function operationDisplayName(op: string): string {
+ const MAP: Record = {
+ getAll: 'Get Many', get: 'Get', create: 'Create', update: 'Update', delete: 'Delete',
+ add: 'Add', remove: 'Remove', pin: 'Pin', unpin: 'Unpin',
+ archive: 'Archive', unarchive: 'Unarchive', leave: 'Leave', updateRole: 'Update Role',
+ };
+ if (MAP[op]) return MAP[op];
+ // Sub-resource operations: "getAllStatus" → "Get Many Status", "updateMembers" → "Update Members"
+ return op.replace(/([A-Z])/g, ' $1').replace(/^\s/, '').replace(/^(get All|get|add|update|delete|remove)/i, (m) => {
+ const lower = m.toLowerCase();
+ if (lower === 'get all') return 'Get Many';
+ return m.charAt(0).toUpperCase() + m.slice(1);
+ }).replace(/\bIds?\b/g, m => m === 'Id' ? 'ID' : 'IDs');
+}
+
+/** Generate n8n action label (eslint format: "Get many users") */
+function actionLabel(op: string, resourceName: string, resource?: string): string {
+ const singular = resourceName.toLowerCase();
+ const plural = singular.endsWith('y') ? singular.slice(0, -1) + 'ies' : singular + 's';
+
+ // Resource-specific overrides for better semantics
+ const RESOURCE_PLURAL_OVERRIDES: Record = {
+ security: 'audit events',
+ };
+ const overriddenPlural = RESOURCE_PLURAL_OVERRIDES[singular] ?? plural;
+
+ // Standard CRUD
+ if (op === 'getAll') return `Get many ${overriddenPlural}`;
+ if (op === 'get') return `Get a ${singular}`;
+ if (op === 'create') return `Create a ${singular}`;
+ if (op === 'update') return `Update a ${singular}`;
+ if (op === 'delete') return `Delete a ${singular}`;
+ if (op === 'add') return `Add a ${singular}`;
+ if (op === 'remove') return `Remove a ${singular}`;
+ if (op === 'pin') return `Pin a ${singular}`;
+ if (op === 'unpin') return `Unpin a ${singular}`;
+ if (op === 'archive') return `Archive a ${singular}`;
+ if (op === 'unarchive') return `Unarchive a ${singular}`;
+ if (op === 'leave') {
+ if (resource === 'member') return 'Leave chat';
+ return `Leave ${singular}`;
+ }
+
+ // Alias operations with readable labels
+ const ALIAS_LABELS: Record = {
+ getMembers: 'Get members',
+ addUsers: 'Add users',
+ removeUser: 'Remove user',
+ updateRole: 'Update member role',
+ leaveChat: 'Leave chat',
+ getReadMembers: 'Get read members',
+ unfurl: 'Unfurl link preview',
+ addTags: 'Add tags to chat',
+ removeTag: 'Remove tag from chat',
+ };
+ if (ALIAS_LABELS[op]) return ALIAS_LABELS[op];
+
+ // Sub-resource operations: extract action + sub-resource name
+ const subMatch = op.match(/^(getAll|get|add|update|delete|remove)(.+)$/);
+ if (subMatch) {
+ const [, action, subPascal] = subMatch;
+ let subWords = subPascal.replace(/([A-Z])/g, ' $1').trim().toLowerCase();
+
+ // Remove resource name prefix from sub-resource to avoid stutter
+ // e.g., readMember.getAllReadMemberIds → "read member ids" not "read member read member ids"
+ if (subWords.startsWith(singular + ' ')) {
+ subWords = subWords.slice(singular.length + 1);
+ }
+
+ if (action === 'getAll') {
+ if (resource === 'search') return `Search ${subWords}`;
+ return `Get many ${singular} ${subWords}`;
+ }
+ if (action === 'get') return `Get ${singular} ${subWords}`;
+ if (action === 'update') return `Update ${singular} ${subWords}`;
+ if (action === 'delete') return `Delete ${singular} ${subWords}`;
+ if (action === 'add') return `Add ${subWords} to ${singular}`;
+ if (action === 'remove') return `Remove ${subWords} from ${singular}`;
+ }
+
+ // Fallback
+ return `${operationDisplayName(op)} a ${singular}`;
+}
+
+// ============================================================================
+// PARAMETER NAME RESOLUTION
+// ============================================================================
+
+/** Get the v1-compatible parameter name */
+function getParamName(resource: string, op: string, fieldName: string): string {
+ // Use v1 resource name for lookup (e.g., "reactions" not "reaction")
+ const v1Resource = V1_COMPAT_RESOURCES[resource] ?? resource;
+ const opMap = V1_COMPAT_PARAMS[v1Resource];
+ if (opMap) {
+ const wildcard = opMap['*']?.[snakeToCamel(fieldName)];
+ if (wildcard) return wildcard;
+ const specific = opMap[op]?.[snakeToCamel(fieldName)];
+ if (specific) return specific;
+ }
+ return snakeToCamel(fieldName);
+}
+
+// ============================================================================
+// n8n TYPE MAPPING
+// ============================================================================
+
+/** Resolve array items schema (handles allOf wrappers) */
+function resolveArrayItems(field: BodyField): Schema | undefined {
+ if (!field.items) return undefined;
+ return field.items.allOf ? resolveAllOf(field.items) : field.items;
+}
+
+/** Map OpenAPI type → n8n field type */
+function toN8nType(field: BodyField): string {
+ if (field.enum && field.enum.length > 0) return 'options';
+ if (field.format === 'date-time') return 'dateTime';
+ if (field.type === 'boolean') return 'boolean';
+ if (field.type === 'integer' || field.type === 'number') return 'number';
+ if (field.type === 'array' && resolveArrayItems(field)?.properties) return 'fixedCollection';
+ // Array of primitives → comma-separated string (transformed via splitCommaToArray)
+ if (field.type === 'array' && !resolveArrayItems(field)?.properties) return 'string';
+ if (field.type === 'object') return 'json';
+ return 'string';
+}
+
+/** Resolve allOf wrapper on query parameter schemas so enums are visible */
+function resolveQuerySchema(schema: Schema): Schema {
+ if (schema.allOf) return resolveAllOf(schema);
+ return schema;
+}
+
+/** Map OpenAPI query parameter schema → n8n field type */
+function queryParamN8nType(schema: Schema): string {
+ const resolved = resolveQuerySchema(schema);
+ if (resolved.enum) return 'options';
+ const type = getSchemaType(resolved);
+ if (type === 'boolean') return 'boolean';
+ if (resolved.format === 'date-time') return 'dateTime';
+ if (type === 'integer' || type === 'number') return 'number';
+ return 'string';
+}
+
+/** Check if a field is an array of primitives (needs splitCommaToArray preSend) */
+function isPrimitiveArray(field: BodyField): boolean {
+ return field.type === 'array' && !resolveArrayItems(field)?.properties;
+}
+
+/** Get the item type for primitive arrays */
+function getArrayItemType(field: BodyField): 'int' | 'string' {
+ const resolved = resolveArrayItems(field);
+ const itemType = resolved ? getSchemaType(resolved) : 'string';
+ return (itemType === 'integer' || itemType === 'number') ? 'int' : 'string';
+}
+
+/** Check if a query parameter is an array of primitives */
+function isQueryParamArray(param: Parameter): boolean {
+ const schema = resolveQuerySchema(param.schema);
+ return getSchemaType(schema) === 'array' && !!schema.items && !schema.items.properties;
+}
+
+/** Get the item type for a query parameter array */
+function getQueryArrayItemType(param: Parameter): 'int' | 'string' {
+ const schema = resolveQuerySchema(param.schema);
+ const items = schema.items;
+ if (!items) return 'string';
+ const resolved = items.allOf ? resolveAllOf(items) : items;
+ const itemType = getSchemaType(resolved);
+ return (itemType === 'integer' || itemType === 'number') ? 'int' : 'string';
+}
+
+/** Ensure boolean descriptions start with "Whether" (eslint requirement) */
+function booleanDescription(desc?: string): string {
+ if (!desc) return 'Whether to enable this option';
+ if (desc.startsWith('Whether')) return desc;
+ return `Whether to ${desc.charAt(0).toLowerCase()}${desc.slice(1)}`;
+}
+
+// ============================================================================
+// CODE GENERATION HELPERS
+// ============================================================================
+
+
+
+function quote(s: string): string {
+ return `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
+}
+
+function generateEnumOptions(values: unknown[], enumDescriptions?: Record): string {
+ return values
+ .map(v => {
+ const val = String(v);
+ const name = formatDisplayName(val);
+ const desc = enumDescriptions?.[val];
+ if (desc && desc.toLowerCase() !== name.toLowerCase()) {
+ return { name, val, str: `{ name: ${quote(name)}, value: ${quote(val)}, description: ${quote(desc)} }` };
+ }
+ return { name, val, str: `{ name: ${quote(name)}, value: ${quote(val)} }` };
+ })
+ .sort((a, b) => a.name.localeCompare(b.name))
+ .map(o => o.str)
+ .join(',\n');
+}
+
+// ============================================================================
+// GENERATE RESOURCE DESCRIPTION FILE
+// ============================================================================
+
+interface OperationInfo {
+ v2Op: string;
+ v1Op: string;
+ endpoint: Endpoint;
+ fields: BodyField[];
+ queryParams: Parameter[];
+ pathParams: Parameter[];
+ hasPagination: boolean;
+ wrapperKey: string | null;
+ description: string;
+}
+
+function generateResourceDescription(
+ resource: string,
+ operations: OperationInfo[],
+): string {
+ const displayName = resourceDisplayName(resource);
+
+ const allResourceValues = [resource];
+
+ const lines: string[] = [];
+ // Description files are now UI-only — no routing imports needed (execute() handles it)
+ lines.push(`import type { INodeProperties } from 'n8n-workflow';`);
+ lines.push('');
+ lines.push(`export const ${snakeToCamel(resource)}Operations: INodeProperties[] = [`);
+
+ // --- Operation dropdown ---
+ lines.push('\t{');
+ lines.push(`\t\tdisplayName: 'Operation',`);
+ lines.push(`\t\tname: 'operation',`);
+ lines.push(`\t\ttype: 'options',`);
+ lines.push(`\t\tnoDataExpression: true,`);
+ lines.push(`\t\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}] } },`);
+ lines.push(`\t\toptions: [`);
+
+ // Collect all operation option objects, then sort alphabetically by name
+ const opItems: { name: string; lines: string[] }[] = [];
+
+ for (const op of operations) {
+ const displayOpName = operationDisplayName(op.v2Op);
+ const action = actionLabel(op.v2Op, displayName, resource);
+
+ opItems.push({ name: displayOpName, lines: [
+ `\t\t\t{`,
+ `\t\t\t\tname: ${quote(displayOpName)},`,
+ `\t\t\t\tvalue: ${quote(op.v2Op)},`,
+ `\t\t\t\taction: ${quote(action)},`,
+ `\t\t\t},`,
+ ]});
+ }
+
+ // Sort alphabetically by display name (n8n lint rule: node-param-options-type-unsorted-items)
+ opItems.sort((a, b) => a.name.localeCompare(b.name));
+ for (const item of opItems) {
+ lines.push(...item.lines);
+ }
+
+ lines.push(`\t\t],`);
+ const preferredDefault = PREFERRED_DEFAULT_OPS[resource];
+ const defaultOp = preferredDefault && operations.some(o => o.v2Op === preferredDefault)
+ ? preferredDefault
+ : (operations[0]?.v2Op ?? operations[0]?.v1Op ?? 'getAll');
+ lines.push(`\t\tdefault: ${quote(defaultOp)},`);
+ lines.push(`\t},`);
+ lines.push(`];`);
+
+ // --- Fields ---
+ lines.push('');
+ lines.push(`export const ${snakeToCamel(resource)}Fields: INodeProperties[] = [`);
+
+ for (const op of operations) {
+ const allOpValues = [op.v2Op];
+
+ // Path parameters — with resourceLocator for searchable resources
+ // When V1_ID_FALLBACKS exists for this resource and op is a shared op,
+ // the v2 field should only show for v2 (legacy ID field handles v1).
+ const pathOpValues = allOpValues;
+ const pathVersionConstraint = '';
+
+ for (const param of op.pathParams) {
+ const paramName = getParamName(resource, op.v1Op, param.name);
+ const paramDesc = getFieldDescription(param.name, param.description, op.endpoint.id);
+ const searchMethod = PATH_PARAM_SEARCH[resource]?.[param.name];
+
+ if (searchMethod) {
+ // v2: resourceLocator with searchable dropdown
+ lines.push(`\t{`);
+ lines.push(`\t\tdisplayName: ${quote(formatDisplayName(param.name))},`);
+ lines.push(`\t\tname: ${quote(paramName)},`);
+ lines.push(`\t\ttype: 'resourceLocator',`);
+ lines.push(`\t\tdefault: { mode: 'list', value: '' },`);
+ lines.push(`\t\trequired: true,`);
+ if (paramDesc && paramDesc.toLowerCase() !== formatDisplayName(param.name).toLowerCase()) lines.push(`\t\tdescription: ${quote(sanitizeDescription(paramDesc))},`);
+ lines.push(`\t\tmodes: [`);
+ lines.push(`\t\t\t{`);
+ lines.push(`\t\t\t\tdisplayName: 'From List',`);
+ lines.push(`\t\t\t\tname: 'list',`);
+ lines.push(`\t\t\t\ttype: 'list',`);
+ lines.push(`\t\t\t\ttypeOptions: { searchListMethod: ${quote(searchMethod)}, searchable: true },`);
+ lines.push(`\t\t\t},`);
+ lines.push(`\t\t\t{`);
+ lines.push(`\t\t\t\tdisplayName: 'By ID',`);
+ lines.push(`\t\t\t\tname: 'id',`);
+ lines.push(`\t\t\t\ttype: 'string',`);
+ const pathEx = getPlaceholder(param.example ?? param.schema?.example);
+ lines.push(`\t\t\t\tplaceholder: ${quote(pathEx ? `e.g. ${pathEx}` : 'e.g. 12345')},`);
+ lines.push(`\t\t\t},`);
+ const urlPattern = URL_PATTERNS[searchMethod];
+ if (urlPattern) {
+ lines.push(`\t\t\t{`);
+ lines.push(`\t\t\t\tdisplayName: 'By URL',`);
+ lines.push(`\t\t\t\tname: 'url',`);
+ lines.push(`\t\t\t\ttype: 'string',`);
+ lines.push(`\t\t\t\tplaceholder: ${quote(urlPattern.placeholder)},`);
+ lines.push(`\t\t\t\textractValue: { type: 'regex', regex: ${quote(urlPattern.regex)} },`);
+ lines.push(`\t\t\t\tvalidation: [{ type: 'regex', properties: { regex: ${quote(urlPattern.regex)}, errorMessage: ${quote(urlPattern.error)} } }],`);
+ lines.push(`\t\t\t},`);
+ }
+ lines.push(`\t\t],`);
+ lines.push(`\t\tdisplayOptions: { show: { ${pathVersionConstraint}resource: [${allResourceValues.map(quote).join(', ')}], operation: [${pathOpValues.map(quote).join(', ')}] } },`);
+ lines.push(`\t},`);
+ } else {
+ lines.push(`\t{`);
+ lines.push(`\t\tdisplayName: ${quote(formatDisplayName(param.name))},`);
+ lines.push(`\t\tname: ${quote(paramName)},`);
+ lines.push(`\t\ttype: 'number',`);
+ lines.push(`\t\trequired: true,`);
+ lines.push(`\t\tdefault: 0,`);
+ lines.push(`\t\tdisplayOptions: { show: { ${pathVersionConstraint}resource: [${allResourceValues.map(quote).join(', ')}], operation: [${pathOpValues.map(quote).join(', ')}] } },`);
+ if (paramDesc && paramDesc.toLowerCase() !== formatDisplayName(param.name).toLowerCase()) {
+ lines.push(`\t\tdescription: ${quote(sanitizeDescription(paramDesc))},`);
+ }
+ lines.push(`\t},`);
+ }
+ }
+
+ // Operation notice — auto-generated from scope roles + plan requirements
+ const noticeText = buildOperationNotice(op.endpoint);
+ if (noticeText) {
+ lines.push(`\t{`);
+ lines.push(`\t\tdisplayName: ${quote(noticeText)},`);
+ lines.push(`\t\tname: ${quote(`${resource}${snakeToPascal(op.v2Op)}Notice`)},`);
+ lines.push(`\t\ttype: 'notice',`);
+ lines.push(`\t\tdefault: '',`);
+ lines.push(`\t\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: [${allOpValues.map(quote).join(', ')}] } },`);
+ lines.push(`\t},`);
+ }
+
+ // Pagination: returnAll + limit
+ if (op.hasPagination) {
+ lines.push(`\t{`);
+ lines.push(`\t\tdisplayName: 'Return All',`);
+ lines.push(`\t\tname: 'returnAll',`);
+ lines.push(`\t\ttype: 'boolean',`);
+ lines.push(`\t\tdefault: false,`);
+ lines.push(`\t\tdescription: 'Whether to return all results or only up to a given limit',`);
+ lines.push(`\t\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: [${allOpValues.map(quote).join(', ')}] } },`);
+ lines.push(`\t},`);
+
+ lines.push(`\t{`);
+ const limitParam = op.queryParams.find(p => p.name === 'limit');
+ const maxLimit = limitParam?.schema?.maximum ?? 50;
+ lines.push(`\t\tdisplayName: 'Limit',`);
+ lines.push(`\t\tname: 'limit',`);
+ lines.push(`\t\ttype: 'number',`);
+ lines.push(`\t\tdefault: 50,`);
+ lines.push(`\t\tdescription: 'Max number of results to return',`);
+ lines.push(`\t\ttypeOptions: { minValue: 1, maxValue: ${maxLimit} },`);
+ lines.push(`\t\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: [${allOpValues.map(quote).join(', ')}], returnAll: [false] } },`);
+ lines.push(`\t},`);
+
+ }
+
+ // Simplify toggle for GET operations (v2 only, skip for noDataWrapper resources like export)
+ if (op.endpoint.method === 'GET' && resource !== 'export') {
+ lines.push(`\t{`);
+ lines.push(`\t\tdisplayName: 'Simplify',`);
+ lines.push(`\t\tname: 'simplify',`);
+ lines.push(`\t\ttype: 'boolean',`);
+ lines.push(`\t\tdefault: true,`);
+ lines.push(`\t\tdescription: 'Whether to return a simplified version of the response instead of all fields',`);
+ lines.push(`\t\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: [${allOpValues.map(quote).join(', ')}] } },`);
+ lines.push(`\t},`);
+ }
+
+ // Required body fields + promoted optional fields (for v1 compat)
+ const promotedSet = PROMOTED_TOP_LEVEL_FIELDS[resource]?.[op.v2Op] ?? PROMOTED_TOP_LEVEL_FIELDS[resource]?.[op.v1Op];
+ let requiredFields = op.fields.filter(f => (f.required || promotedSet?.has(f.name)) && !f.readOnly);
+ const optionalFields = op.fields.filter(f => !f.required && !promotedSet?.has(f.name) && !f.readOnly);
+
+ // Bot update: replace webhook json field with webhookUrl string field
+ if (resource === 'bot' && (op.v2Op === 'update' || op.v1Op === 'update')) {
+ requiredFields = requiredFields.filter(f => f.name !== 'webhook');
+ lines.push(`\t{`);
+ lines.push(`\t\tdisplayName: 'Webhook URL',`);
+ lines.push(`\t\tname: 'webhookUrl',`);
+ lines.push(`\t\ttype: 'string',`);
+ lines.push(`\t\trequired: true,`);
+ lines.push(`\t\tdefault: '',`);
+ lines.push(`\t\tplaceholder: 'https://example.com/webhook',`);
+ lines.push(`\t\tdescription: 'URL for the outgoing webhook',`);
+ lines.push(`\t\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: [${allOpValues.map(quote).join(', ')}] } },`);
+ lines.push(`\t},`);
+ }
+
+ // Form create: skip `blocks` from required fields — handled by form builder below
+ if (resource === 'form') {
+ requiredFields = requiredFields.filter(f => f.name !== 'blocks');
+ }
+
+ // Avatar upload: replace `image` body field with binary property reference
+ if (getSpecialHandler(resource, op.v2Op) === 'avatarUpload') {
+ requiredFields = requiredFields.filter(f => f.name !== 'image');
+ lines.push(`\t{`);
+ lines.push(`\t\tdisplayName: 'Input Binary Field',`);
+ lines.push(`\t\tname: 'image',`);
+ lines.push(`\t\ttype: 'string',`);
+ lines.push(`\t\trequired: true,`);
+ lines.push(`\t\tdefault: "data",`);
+ lines.push(`\t\tdescription: 'Name of the binary property containing the avatar image. Use a previous node (e.g. HTTP Request, Read Binary File) to load the image.',`);
+ lines.push(`\t\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: [${allOpValues.map(quote).join(', ')}] } },`);
+ lines.push(`\t},`);
+ }
+
+ for (const field of requiredFields) {
+ lines.push(generateFieldProperty(field, resource, op, allResourceValues, allOpValues, true));
+ }
+
+ // Optional fields in "Additional Fields" collection
+ // Skip `buttons` for message ops — handled by visual button constructor above
+ const filteredOptionalFields = resource === 'message'
+ ? optionalFields.filter(f => f.name !== 'buttons')
+ : optionalFields;
+ if (filteredOptionalFields.length > 0) {
+ const collectionName = getCollectionName(resource, op.v1Op, 'additionalFields');
+ lines.push(`\t{`);
+ lines.push(`\t\tdisplayName: 'Additional Fields',`);
+ lines.push(`\t\tname: ${quote(collectionName)},`);
+ lines.push(`\t\ttype: 'collection',`);
+ lines.push(`\t\tplaceholder: 'Add Field',`);
+ lines.push(`\t\tdefault: {},`);
+ lines.push(`\t\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: [${allOpValues.map(quote).join(', ')}] } },`);
+ lines.push(`\t\toptions: [`);
+ const sortedOptionalFields = [...filteredOptionalFields].sort((a, b) => formatDisplayName(a.name).localeCompare(formatDisplayName(b.name)));
+ for (const field of sortedOptionalFields) {
+ lines.push(generateFieldProperty(field, resource, op, allResourceValues, allOpValues, false, true));
+ }
+ lines.push(`\t\t],`);
+ lines.push(`\t},`);
+ }
+
+ // Query parameters (non-pagination, skip parameterized params with {})
+ // Primary query params are required or named 'query' (shown top-level for all resources)
+ const PRIMARY_QUERY_PARAMS = new Set(['query']);
+ // Resources where optional query params should be wrapped in Additional Fields
+ const QUERY_FILTER_RESOURCES = new Set(['search', 'security']);
+
+ const nonPaginationParams = op.queryParams.filter(
+ p => !['limit', 'cursor', 'per', 'page'].includes(p.name) && !p.name.includes('{')
+ );
+
+ // Split into primary (top-level) and filter (collection) params.
+ // Optional boolean query params always go into filter (Additional Fields) because
+ // boolean default false is indistinguishable from "not set" at the UI level,
+ // but the API may treat absence differently from false (e.g. chat.getAll personal).
+ const shouldWrapFilters = QUERY_FILTER_RESOURCES.has(resource);
+ const isOptionalBool = (p: Parameter) => !p.required && queryParamN8nType(p.schema) === 'boolean';
+ const primaryParams = shouldWrapFilters
+ ? nonPaginationParams.filter(p => p.required || PRIMARY_QUERY_PARAMS.has(p.name))
+ : nonPaginationParams.filter(p => !isOptionalBool(p));
+ const filterParams = shouldWrapFilters
+ ? nonPaginationParams.filter(p => !p.required && !PRIMARY_QUERY_PARAMS.has(p.name))
+ : nonPaginationParams.filter(p => isOptionalBool(p));
+
+ for (const param of primaryParams) {
+ const paramName = getParamName(resource, op.v1Op, param.name);
+ const searchable = SEARCHABLE_QUERY_PARAMS[param.name];
+ const queryDesc = getFieldDescription(param.name, param.description, op.endpoint.id);
+
+ if (searchable) {
+ // v2: resourceLocator with searchable dropdown
+ lines.push(`\t{`);
+ lines.push(`\t\tdisplayName: ${quote(formatDisplayName(param.name))},`);
+ lines.push(`\t\tname: ${quote(paramName)},`);
+ lines.push(`\t\ttype: 'resourceLocator',`);
+ if (param.required) lines.push(`\t\trequired: true,`);
+ lines.push(`\t\tdefault: { mode: 'list', value: '' },`);
+ if (queryDesc && queryDesc.toLowerCase() !== formatDisplayName(param.name).toLowerCase()) lines.push(`\t\tdescription: ${quote(sanitizeDescription(queryDesc))},`);
+ lines.push(`\t\tmodes: [`);
+ lines.push(`\t\t\t{`);
+ lines.push(`\t\t\t\tdisplayName: 'From List',`);
+ lines.push(`\t\t\t\tname: 'list',`);
+ lines.push(`\t\t\t\ttype: 'list',`);
+ lines.push(`\t\t\t\ttypeOptions: { searchListMethod: ${quote(searchable.method)}, searchable: true },`);
+ lines.push(`\t\t\t},`);
+ lines.push(`\t\t\t{`);
+ lines.push(`\t\t\t\tdisplayName: 'By ID',`);
+ lines.push(`\t\t\t\tname: 'id',`);
+ lines.push(`\t\t\t\ttype: 'string',`);
+ const qPathEx = getPlaceholder(param.example ?? param.schema?.example);
+ lines.push(`\t\t\t\tplaceholder: ${quote(qPathEx ? `e.g. ${qPathEx}` : 'e.g. 12345')},`);
+ lines.push(`\t\t\t},`);
+ const qUrlPattern = URL_PATTERNS[searchable.method];
+ if (qUrlPattern) {
+ lines.push(`\t\t\t{`);
+ lines.push(`\t\t\t\tdisplayName: 'By URL',`);
+ lines.push(`\t\t\t\tname: 'url',`);
+ lines.push(`\t\t\t\ttype: 'string',`);
+ lines.push(`\t\t\t\tplaceholder: ${quote(qUrlPattern.placeholder)},`);
+ lines.push(`\t\t\t\textractValue: { type: 'regex', regex: ${quote(qUrlPattern.regex)} },`);
+ lines.push(`\t\t\t\tvalidation: [{ type: 'regex', properties: { regex: ${quote(qUrlPattern.regex)}, errorMessage: ${quote(qUrlPattern.error)} } }],`);
+ lines.push(`\t\t\t},`);
+ }
+ lines.push(`\t\t],`);
+ lines.push(`\t\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: [${allOpValues.map(quote).join(', ')}] } },`);
+ lines.push(`\t\trouting: { send: { type: 'query', property: ${quote(param.name)} } },`);
+ lines.push(`\t},`);
+
+ continue;
+ }
+
+ lines.push(generateQueryParamField(param, paramName, queryDesc, op.endpoint.id, allResourceValues, allOpValues, '\t'));
+ }
+
+ // Optional query params wrapped in Additional Fields (for search/security resources)
+ if (filterParams.length > 0) {
+ lines.push(`\t{`);
+ lines.push(`\t\tdisplayName: 'Additional Fields',`);
+ lines.push(`\t\tname: 'additionalFields',`);
+ lines.push(`\t\ttype: 'collection',`);
+ lines.push(`\t\tplaceholder: 'Add Field',`);
+ lines.push(`\t\tdefault: {},`);
+ lines.push(`\t\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: [${allOpValues.map(quote).join(', ')}] } },`);
+ lines.push(`\t\toptions: [`);
+ const sortedFilterParams = [...filterParams].sort((a, b) => formatDisplayName(a.name).localeCompare(formatDisplayName(b.name)));
+ for (const param of sortedFilterParams) {
+ const paramName = getParamName(resource, op.v1Op, param.name);
+ const queryDesc = getFieldDescription(param.name, param.description, op.endpoint.id);
+ lines.push(generateQueryParamField(param, paramName, queryDesc, op.endpoint.id, allResourceValues, allOpValues, '\t\t\t', true));
+ }
+ lines.push(`\t\t],`);
+ lines.push(`\t},`);
+ }
+
+ // Visual button constructor for message create/send/update
+ if (resource === 'message' && (op.v2Op === 'create' || op.v1Op === 'send' || op.v2Op === 'update')) {
+ lines.push(`\t{`);
+ lines.push(`\t\tdisplayName: 'Button Layout',`);
+ lines.push(`\t\tname: 'buttonLayout',`);
+ lines.push(`\t\ttype: 'options',`);
+ lines.push(`\t\toptions: [`);
+ lines.push(`\t\t\t{ name: 'None', value: 'none' },`);
+ lines.push(`\t\t\t{ name: 'Single Row', value: 'single_row' },`);
+ lines.push(`\t\t\t{ name: 'Multiple Rows', value: 'multiple_rows' },`);
+ lines.push(`\t\t\t{ name: 'Raw JSON', value: 'raw_json' },`);
+ lines.push(`\t\t],`);
+ lines.push(`\t\tdefault: 'none',`);
+ lines.push(`\t\tdescription: 'How to layout buttons in the message',`);
+ lines.push(`\t\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: [${allOpValues.map(quote).join(', ')}] } },`);
+ lines.push(`\t},`);
+ // Visual button fixedCollection (shown for single_row / multiple_rows)
+ lines.push(`\t{`);
+ lines.push(`\t\tdisplayName: 'Buttons',`);
+ lines.push(`\t\tname: 'buttons',`);
+ lines.push(`\t\ttype: 'fixedCollection',`);
+ lines.push(`\t\ttypeOptions: { multipleValues: true },`);
+ lines.push(`\t\toptions: [{`);
+ lines.push(`\t\t\tname: 'button',`);
+ lines.push(`\t\t\tdisplayName: 'Button',`);
+ lines.push(`\t\t\tvalues: [`);
+ lines.push(`\t\t\t\t{`);
+ lines.push(`\t\t\t\t\tdisplayName: 'Text',`);
+ lines.push(`\t\t\t\t\tname: 'text',`);
+ lines.push(`\t\t\t\t\ttype: 'string',`);
+ lines.push(`\t\t\t\t\tdefault: '',`);
+ lines.push(`\t\t\t\t\tdescription: 'Button label (max 255 characters)',`);
+ lines.push(`\t\t\t\t\tplaceholder: 'Click me',`);
+ lines.push(`\t\t\t\t},`);
+ lines.push(`\t\t\t\t{`);
+ lines.push(`\t\t\t\t\tdisplayName: 'Type',`);
+ lines.push(`\t\t\t\t\tname: 'type',`);
+ lines.push(`\t\t\t\t\ttype: 'options',`);
+ lines.push(`\t\t\t\t\toptions: [`);
+ lines.push(`\t\t\t\t\t\t{ name: 'Data (Webhook)', value: 'data' },`);
+ lines.push(`\t\t\t\t\t\t{ name: 'URL (Link)', value: 'url' },`);
+ lines.push(`\t\t\t\t\t],`);
+ lines.push(`\t\t\t\t\tdefault: 'data',`);
+ lines.push(`\t\t\t\t\tdescription: 'Data sends a webhook on click, URL opens a link',`);
+ lines.push(`\t\t\t\t},`);
+ lines.push(`\t\t\t\t{`);
+ lines.push(`\t\t\t\t\tdisplayName: 'Data',`);
+ lines.push(`\t\t\t\t\tname: 'data',`);
+ lines.push(`\t\t\t\t\ttype: 'string',`);
+ lines.push(`\t\t\t\t\tdefault: '',`);
+ lines.push(`\t\t\t\t\tdescription: 'Data sent via webhook when button is clicked (max 255 characters)',`);
+ lines.push(`\t\t\t\t\tplaceholder: 'action_confirm',`);
+ lines.push(`\t\t\t\t\tdisplayOptions: { show: { type: ['data'] } },`);
+ lines.push(`\t\t\t\t},`);
+ lines.push(`\t\t\t\t{`);
+ lines.push(`\t\t\t\t\tdisplayName: 'URL',`);
+ lines.push(`\t\t\t\t\tname: 'url',`);
+ lines.push(`\t\t\t\t\ttype: 'string',`);
+ lines.push(`\t\t\t\t\tdefault: '',`);
+ lines.push(`\t\t\t\t\tdescription: 'URL to open when button is clicked',`);
+ lines.push(`\t\t\t\t\tplaceholder: 'https://example.com',`);
+ lines.push(`\t\t\t\t\tdisplayOptions: { show: { type: ['url'] } },`);
+ lines.push(`\t\t\t\t},`);
+ lines.push(`\t\t\t],`);
+ lines.push(`\t\t}],`);
+ lines.push(`\t\tdefault: {},`);
+ lines.push(`\t\tdescription: 'Buttons to add to the message. Max 100 buttons, up to 8 per row.',`);
+ lines.push(`\t\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: [${allOpValues.map(quote).join(', ')}], buttonLayout: ['single_row', 'multiple_rows'] } },`);
+ lines.push(`\t},`);
+ // Raw JSON field (shown for raw_json mode)
+ lines.push(`\t{`);
+ lines.push(`\t\tdisplayName: 'Buttons (JSON)',`);
+ lines.push(`\t\tname: 'rawJsonButtons',`);
+ lines.push(`\t\ttype: 'json',`);
+ lines.push(`\t\tdefault: '[]',`);
+ lines.push(`\t\tdescription: 'Buttons as JSON: array of rows, each row is an array of buttons. To remove all buttons, send [].',`);
+ lines.push(`\t\tplaceholder: '[[{"text":"OK","data":"confirm"}],[{"text":"Link","url":"https://example.com"}]]',`);
+ lines.push(`\t\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: [${allOpValues.map(quote).join(', ')}], buttonLayout: ['raw_json'] } },`);
+ lines.push(`\t},`);
+ }
+
+ // Form builder for form create/createView (Phase 13)
+ if (resource === 'form' && (op.v2Op === 'create' || op.v1Op === 'createView')) {
+ // Builder mode selector: Visual Builder / JSON
+ lines.push(`\t{`);
+ lines.push(`\t\tdisplayName: 'Builder Mode',`);
+ lines.push(`\t\tname: 'formBuilderMode',`);
+ lines.push(`\t\ttype: 'options',`);
+ lines.push(`\t\toptions: [`);
+ lines.push(`\t\t\t{ name: 'Visual Builder', value: 'builder' },`);
+ lines.push(`\t\t\t{ name: 'JSON', value: 'json' },`);
+ lines.push(`\t\t],`);
+ lines.push(`\t\tdefault: 'builder',`);
+ lines.push(`\t\tdescription: 'Build form visually or paste JSON',`);
+ lines.push(`\t\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: [${allOpValues.map(quote).join(', ')}] } },`);
+ lines.push(`\t},`);
+ // Visual builder — fixedCollection with all block types (shown in builder mode)
+ lines.push(`\t{`);
+ lines.push(`\t\tdisplayName: 'Form Blocks',`);
+ lines.push(`\t\tname: 'formBlocks',`);
+ lines.push(`\t\ttype: 'fixedCollection',`);
+ lines.push(`\t\ttypeOptions: { multipleValues: true, sortable: true },`);
+ lines.push(`\t\tdefault: {},`);
+ lines.push(`\t\tdescription: 'Add form blocks using the visual builder',`);
+ lines.push(`\t\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: [${allOpValues.map(quote).join(', ')}], formBuilderMode: ['builder'] } },`);
+ lines.push(`\t\toptions: [{`);
+ lines.push(`\t\t\tname: 'block',`);
+ lines.push(`\t\t\tdisplayName: 'Block',`);
+ lines.push(`\t\t\tvalues: [`);
+ // Fields in alphabetical order by displayName (required by n8n eslint rule)
+ // Allowed File Types (file_input only)
+ lines.push(`\t\t\t\t{`);
+ lines.push(`\t\t\t\t\tdisplayName: 'Allowed File Types',`);
+ lines.push(`\t\t\t\t\tname: 'filetypes',`);
+ lines.push(`\t\t\t\t\ttype: 'string',`);
+ lines.push(`\t\t\t\t\tdefault: '',`);
+ lines.push(`\t\t\t\t\tplaceholder: 'png,jpg,pdf',`);
+ lines.push(`\t\t\t\t\tdescription: 'Comma-separated list of allowed file extensions',`);
+ lines.push(`\t\t\t\t\tdisplayOptions: { show: { type: ['file_input'] } },`);
+ lines.push(`\t\t\t\t},`);
+ // Field Name (for input fields)
+ lines.push(`\t\t\t\t{`);
+ lines.push(`\t\t\t\t\tdisplayName: 'Field Name',`);
+ lines.push(`\t\t\t\t\tname: 'name',`);
+ lines.push(`\t\t\t\t\ttype: 'string',`);
+ lines.push(`\t\t\t\t\tdefault: '',`);
+ lines.push(`\t\t\t\t\tdescription: 'Unique field identifier (used in form submission data)',`);
+ lines.push(`\t\t\t\t\tdisplayOptions: { show: { type: ['input', 'select', 'radio', 'checkbox', 'date', 'time', 'file_input'] } },`);
+ lines.push(`\t\t\t\t},`);
+ // Hint
+ lines.push(`\t\t\t\t{`);
+ lines.push(`\t\t\t\t\tdisplayName: 'Hint',`);
+ lines.push(`\t\t\t\t\tname: 'hint',`);
+ lines.push(`\t\t\t\t\ttype: 'string',`);
+ lines.push(`\t\t\t\t\tdefault: '',`);
+ lines.push(`\t\t\t\t\tdisplayOptions: { show: { type: ['input', 'select', 'radio', 'checkbox', 'date', 'time', 'file_input'] } },`);
+ lines.push(`\t\t\t\t},`);
+ // Initial Date (date only)
+ lines.push(`\t\t\t\t{`);
+ lines.push(`\t\t\t\t\tdisplayName: 'Initial Date',`);
+ lines.push(`\t\t\t\t\tname: 'initial_date',`);
+ lines.push(`\t\t\t\t\ttype: 'string',`);
+ lines.push(`\t\t\t\t\tdefault: '',`);
+ lines.push(`\t\t\t\t\tplaceholder: '2024-01-01',`);
+ lines.push(`\t\t\t\t\tdisplayOptions: { show: { type: ['date'] } },`);
+ lines.push(`\t\t\t\t},`);
+ // Initial Time (time only)
+ lines.push(`\t\t\t\t{`);
+ lines.push(`\t\t\t\t\tdisplayName: 'Initial Time',`);
+ lines.push(`\t\t\t\t\tname: 'initial_time',`);
+ lines.push(`\t\t\t\t\ttype: 'string',`);
+ lines.push(`\t\t\t\t\tdefault: '',`);
+ lines.push(`\t\t\t\t\tplaceholder: '09:00',`);
+ lines.push(`\t\t\t\t\tdisplayOptions: { show: { type: ['time'] } },`);
+ lines.push(`\t\t\t\t},`);
+ // Initial Value (input only)
+ lines.push(`\t\t\t\t{`);
+ lines.push(`\t\t\t\t\tdisplayName: 'Initial Value',`);
+ lines.push(`\t\t\t\t\tname: 'initial_value',`);
+ lines.push(`\t\t\t\t\ttype: 'string',`);
+ lines.push(`\t\t\t\t\tdefault: '',`);
+ lines.push(`\t\t\t\t\tdisplayOptions: { show: { type: ['input'] } },`);
+ lines.push(`\t\t\t\t},`);
+ // Label
+ lines.push(`\t\t\t\t{`);
+ lines.push(`\t\t\t\t\tdisplayName: 'Label',`);
+ lines.push(`\t\t\t\t\tname: 'label',`);
+ lines.push(`\t\t\t\t\ttype: 'string',`);
+ lines.push(`\t\t\t\t\tdefault: '',`);
+ lines.push(`\t\t\t\t\tdisplayOptions: { show: { type: ['input', 'select', 'radio', 'checkbox', 'date', 'time', 'file_input'] } },`);
+ lines.push(`\t\t\t\t},`);
+ // Max Files (file_input only)
+ lines.push(`\t\t\t\t{`);
+ lines.push(`\t\t\t\t\tdisplayName: 'Max Files',`);
+ lines.push(`\t\t\t\t\tname: 'max_files',`);
+ lines.push(`\t\t\t\t\ttype: 'number',`);
+ lines.push(`\t\t\t\t\tdefault: 10,`);
+ lines.push(`\t\t\t\t\tdisplayOptions: { show: { type: ['file_input'] } },`);
+ lines.push(`\t\t\t\t},`);
+ // Max Length (input only)
+ lines.push(`\t\t\t\t{`);
+ lines.push(`\t\t\t\t\tdisplayName: 'Max Length',`);
+ lines.push(`\t\t\t\t\tname: 'max_length',`);
+ lines.push(`\t\t\t\t\ttype: 'number',`);
+ lines.push(`\t\t\t\t\tdefault: 0,`);
+ lines.push(`\t\t\t\t\tdisplayOptions: { show: { type: ['input'] } },`);
+ lines.push(`\t\t\t\t},`);
+ // Min Length (input only)
+ lines.push(`\t\t\t\t{`);
+ lines.push(`\t\t\t\t\tdisplayName: 'Min Length',`);
+ lines.push(`\t\t\t\t\tname: 'min_length',`);
+ lines.push(`\t\t\t\t\ttype: 'number',`);
+ lines.push(`\t\t\t\t\tdefault: 0,`);
+ lines.push(`\t\t\t\t\tdisplayOptions: { show: { type: ['input'] } },`);
+ lines.push(`\t\t\t\t},`);
+ // Multiline (input only)
+ lines.push(`\t\t\t\t{`);
+ lines.push(`\t\t\t\t\tdisplayName: 'Multiline',`);
+ lines.push(`\t\t\t\t\tname: 'multiline',`);
+ lines.push(`\t\t\t\t\ttype: 'boolean',`);
+ lines.push(`\t\t\t\t\tdefault: false,`);
+ lines.push(`\t\t\t\t\tdisplayOptions: { show: { type: ['input'] } },`);
+ lines.push(`\t\t\t\t},`);
+ // Options (for select, radio, checkbox) — nested fixedCollection
+ lines.push(`\t\t\t\t{`);
+ lines.push(`\t\t\t\t\tdisplayName: 'Options',`);
+ lines.push(`\t\t\t\t\tname: 'options',`);
+ lines.push(`\t\t\t\t\ttype: 'fixedCollection',`);
+ lines.push(`\t\t\t\t\ttypeOptions: { multipleValues: true },`);
+ lines.push(`\t\t\t\t\tdefault: {},`);
+ lines.push(`\t\t\t\t\tdisplayOptions: { show: { type: ['select', 'radio', 'checkbox'] } },`);
+ lines.push(`\t\t\t\t\toptions: [{`);
+ lines.push(`\t\t\t\t\t\tname: 'option',`);
+ lines.push(`\t\t\t\t\t\tdisplayName: 'Option',`);
+ lines.push(`\t\t\t\t\t\tvalues: [`);
+ lines.push(`\t\t\t\t\t\t\t{ displayName: 'Checked by Default', name: 'checked', type: 'boolean', default: false },`);
+ lines.push(`\t\t\t\t\t\t\t{ displayName: 'Description', name: 'description', type: 'string', default: '' },`);
+ lines.push(`\t\t\t\t\t\t\t{ displayName: 'Selected by Default', name: 'selected', type: 'boolean', default: false },`);
+ lines.push(`\t\t\t\t\t\t\t{ displayName: 'Text', name: 'text', type: 'string', default: '' },`);
+ lines.push(`\t\t\t\t\t\t\t{ displayName: 'Value', name: 'value', type: 'string', default: '' },`);
+ lines.push(`\t\t\t\t\t\t],`);
+ lines.push(`\t\t\t\t\t}],`);
+ lines.push(`\t\t\t\t},`);
+ // Placeholder (input only)
+ lines.push(`\t\t\t\t{`);
+ lines.push(`\t\t\t\t\tdisplayName: 'Placeholder',`);
+ lines.push(`\t\t\t\t\tname: 'placeholder',`);
+ lines.push(`\t\t\t\t\ttype: 'string',`);
+ lines.push(`\t\t\t\t\tdefault: '',`);
+ lines.push(`\t\t\t\t\tdisplayOptions: { show: { type: ['input'] } },`);
+ lines.push(`\t\t\t\t},`);
+ // Required
+ lines.push(`\t\t\t\t{`);
+ lines.push(`\t\t\t\t\tdisplayName: 'Required',`);
+ lines.push(`\t\t\t\t\tname: 'required',`);
+ lines.push(`\t\t\t\t\ttype: 'boolean',`);
+ lines.push(`\t\t\t\t\tdefault: false,`);
+ lines.push(`\t\t\t\t\tdisplayOptions: { show: { type: ['input', 'select', 'radio', 'checkbox', 'date', 'time', 'file_input'] } },`);
+ lines.push(`\t\t\t\t},`);
+ // Text (for header, plain_text, markdown)
+ lines.push(`\t\t\t\t{`);
+ lines.push(`\t\t\t\t\tdisplayName: 'Text',`);
+ lines.push(`\t\t\t\t\tname: 'text',`);
+ lines.push(`\t\t\t\t\ttype: 'string',`);
+ lines.push(`\t\t\t\t\tdefault: '',`);
+ lines.push(`\t\t\t\t\tdisplayOptions: { show: { type: ['header', 'plain_text', 'markdown'] } },`);
+ lines.push(`\t\t\t\t},`);
+ // Block type selector
+ lines.push(`\t\t\t\t{`);
+ lines.push(`\t\t\t\t\tdisplayName: 'Type',`);
+ lines.push(`\t\t\t\t\tname: 'type',`);
+ lines.push(`\t\t\t\t\ttype: 'options',`);
+ lines.push(`\t\t\t\t\toptions: [`);
+ lines.push(`\t\t\t\t\t\t{ name: '☑️ Checkboxes', value: 'checkbox' },`);
+ lines.push(`\t\t\t\t\t\t{ name: '➖ Divider', value: 'divider' },`);
+ lines.push(`\t\t\t\t\t\t{ name: '📄 Plain Text', value: 'plain_text' },`);
+ lines.push(`\t\t\t\t\t\t{ name: '📅 Date Picker', value: 'date' },`);
+ lines.push(`\t\t\t\t\t\t{ name: '📋 Select Dropdown', value: 'select' },`);
+ lines.push(`\t\t\t\t\t\t{ name: '📎 File Upload', value: 'file_input' },`);
+ lines.push(`\t\t\t\t\t\t{ name: '📝 Header', value: 'header' },`);
+ lines.push(`\t\t\t\t\t\t{ name: '📝 Markdown', value: 'markdown' },`);
+ lines.push(`\t\t\t\t\t\t{ name: '📝 Text Input', value: 'input' },`);
+ lines.push(`\t\t\t\t\t\t{ name: '🔘 Radio Buttons', value: 'radio' },`);
+ lines.push(`\t\t\t\t\t\t{ name: '🕐 Time Picker', value: 'time' },`);
+ lines.push(`\t\t\t\t\t],`);
+ lines.push(`\t\t\t\t\tdefault: 'input',`);
+ lines.push(`\t\t\t\t},`);
+ lines.push(`\t\t\t],`);
+ lines.push(`\t\t}],`);
+ lines.push(`\t},`);
+ // Raw JSON blocks (shown in json mode)
+ lines.push(`\t{`);
+ lines.push(`\t\tdisplayName: 'Blocks (JSON)',`);
+ lines.push(`\t\tname: 'formBlocks',`);
+ lines.push(`\t\ttype: 'json',`);
+ lines.push(`\t\trequired: true,`);
+ lines.push(`\t\tdefault: '[]',`);
+ lines.push(`\t\tdescription: 'Paste an array of blocks or the full form JSON from the visual form builder',`);
+ lines.push(`\t\thint: 'Build your form visually at dev.pachca.com/guides/forms/overview, then paste the JSON here',`);
+ lines.push(`\t\tplaceholder: '{"title":"My form","blocks":[{"type":"input","name":"field_1","label":"Your name"}]}',`);
+ lines.push(`\t\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: [${allOpValues.map(quote).join(', ')}], formBuilderMode: ['json'] } },`);
+ lines.push(`\t},`);
+
+ // v1-only processSubmission parameters
+ lines.push(`\t{`);
+ lines.push(`\t\tdisplayName: 'This operation is deprecated. In v2, use the Pachca Trigger node to receive form submissions via webhook, then process the data with standard n8n nodes.',`);
+ lines.push(`\t\tname: 'processSubmissionNotice',`);
+ lines.push(`\t\ttype: 'notice',`);
+ lines.push(`\t\tdefault: '',`);
+ lines.push(`\t\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: ['processSubmission'] } },`);
+ lines.push(`\t},`);
+ lines.push(`\t{`);
+ lines.push(`\t\tdisplayName: 'Form Type',`);
+ lines.push(`\t\tname: 'formType',`);
+ lines.push(`\t\ttype: 'options',`);
+ lines.push(`\t\toptions: [`);
+ lines.push(`\t\t\t{ name: 'Auto-Detect', value: 'auto' },`);
+ lines.push(`\t\t\t{ name: 'Feedback Form', value: 'feedback_form' },`);
+ lines.push(`\t\t\t{ name: 'Task Request', value: 'task_request' },`);
+ lines.push(`\t\t\t{ name: 'Timeoff Request', value: 'timeoff_request' },`);
+ lines.push(`\t\t],`);
+ lines.push(`\t\tdefault: 'auto',`);
+ lines.push(`\t\tdescription: 'Form type for processing data',`);
+ lines.push(`\t\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: ['processSubmission'] } },`);
+ lines.push(`\t},`);
+ lines.push(`\t{`);
+ lines.push(`\t\tdisplayName: 'Validation Errors',`);
+ lines.push(`\t\tname: 'validationErrors',`);
+ lines.push(`\t\ttype: 'json',`);
+ lines.push(`\t\tdefault: '{}',`);
+ lines.push(`\t\tdescription: 'Validation errors to send to user (JSON object with field names and messages)',`);
+ lines.push(`\t\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: ['processSubmission'] } },`);
+ lines.push(`\t},`);
+
+ }
+
+ }
+
+ lines.push(`];`);
+
+ return lines.join('\n');
+}
+
+// ============================================================================
+// FIELD GENERATION
+// ============================================================================
+
+function generateFieldProperty(
+ field: BodyField,
+ resource: string,
+ op: OperationInfo,
+ allResourceValues: string[],
+ allOpValues: string[],
+ isRequired: boolean,
+ isInsideCollection = false,
+): string {
+ const paramName = getParamName(resource, op.v1Op, field.name);
+ const n8nType = toN8nType(field);
+ const displayName = formatDisplayName(field.name);
+
+ const lines: string[] = [];
+ const tab = isInsideCollection ? '\t\t\t' : '\t';
+
+ // Check if this body field should be a resourceLocator (searchable dropdown)
+ const bodySearchMethod = !isInsideCollection ? BODY_FIELD_SEARCH[resource]?.[field.name] : undefined;
+ if (bodySearchMethod) {
+ lines.push(`${tab}{`);
+ lines.push(`${tab}\tdisplayName: ${quote(displayName)},`);
+ lines.push(`${tab}\tname: ${quote(paramName)},`);
+ lines.push(`${tab}\ttype: 'resourceLocator',`);
+ lines.push(`${tab}\tdefault: { mode: 'list', value: '' },`);
+ if (isRequired) lines.push(`${tab}\trequired: true,`);
+ const rawDesc = getFieldDescription(field.name, field.description, op.endpoint.id);
+ if (rawDesc && rawDesc.toLowerCase() !== displayName.toLowerCase()) lines.push(`${tab}\tdescription: ${quote(sanitizeDescription(rawDesc))},`);
+ const fieldHint = FIELD_HINTS[resource]?.[field.name];
+ if (fieldHint) lines.push(`${tab}\thint: ${quote(fieldHint)},`);
+ lines.push(`${tab}\tmodes: [`);
+ lines.push(`${tab}\t\t{`);
+ lines.push(`${tab}\t\t\tdisplayName: 'From List',`);
+ lines.push(`${tab}\t\t\tname: 'list',`);
+ lines.push(`${tab}\t\t\ttype: 'list',`);
+ lines.push(`${tab}\t\t\ttypeOptions: { searchListMethod: ${quote(bodySearchMethod)}, searchable: true },`);
+ lines.push(`${tab}\t\t},`);
+ lines.push(`${tab}\t\t{`);
+ lines.push(`${tab}\t\t\tdisplayName: 'By ID',`);
+ lines.push(`${tab}\t\t\tname: 'id',`);
+ lines.push(`${tab}\t\t\ttype: 'string',`);
+ const bodyEx = getPlaceholder(field.schema?.example);
+ lines.push(`${tab}\t\t\tplaceholder: ${quote(bodyEx ? `e.g. ${bodyEx}` : 'e.g. 12345')},`);
+ lines.push(`${tab}\t\t},`);
+ const bUrlPattern = URL_PATTERNS[bodySearchMethod];
+ if (bUrlPattern) {
+ lines.push(`${tab}\t\t{`);
+ lines.push(`${tab}\t\t\tdisplayName: 'By URL',`);
+ lines.push(`${tab}\t\t\tname: 'url',`);
+ lines.push(`${tab}\t\t\ttype: 'string',`);
+ lines.push(`${tab}\t\t\tplaceholder: ${quote(bUrlPattern.placeholder)},`);
+ lines.push(`${tab}\t\t\textractValue: { type: 'regex', regex: ${quote(bUrlPattern.regex)} },`);
+ lines.push(`${tab}\t\t\tvalidation: [{ type: 'regex', properties: { regex: ${quote(bUrlPattern.regex)}, errorMessage: ${quote(bUrlPattern.error)} } }],`);
+ lines.push(`${tab}\t\t},`);
+ }
+ lines.push(`${tab}\t],`);
+ lines.push(`${tab}\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: [${allOpValues.map(quote).join(', ')}] } },`);
+ lines.push(`${tab}\trouting: { send: { type: 'body', property: ${quote(field.name)} } },`);
+ lines.push(`${tab}},`);
+ return lines.join('\n');
+ }
+
+ // Check for field type overrides (e.g. priority → options instead of number)
+ const fieldOverride = FIELD_OPTIONS_OVERRIDES[field.name];
+
+ lines.push(`${tab}{`);
+ lines.push(`${tab}\tdisplayName: ${quote(displayName)},`);
+ lines.push(`${tab}\tname: ${quote(paramName)},`);
+ lines.push(`${tab}\ttype: ${quote(fieldOverride ? 'options' : n8nType)},`);
+
+ if (isRequired) {
+ lines.push(`${tab}\trequired: true,`);
+ }
+
+ // Type-specific options
+ if (fieldOverride) {
+ const optStr = fieldOverride.options.map(o => `{ name: ${quote(o.name)}, value: ${JSON.stringify(o.value)} }`).join(',\n');
+ lines.push(`${tab}\toptions: [${optStr}],`);
+ } else if (n8nType === 'options' && field.enum) {
+ const enumDescs = getEnumDescriptions(field.name, op.endpoint.id);
+ lines.push(`${tab}\toptions: [${generateEnumOptions(field.enum, enumDescs)}],`);
+ }
+
+ if (n8nType === 'fixedCollection' && field.items?.properties) {
+ const subName = V1_COMPAT_SUBCOLLECTIONS[paramName] ?? `${paramName}Values`;
+ lines.push(`${tab}\ttypeOptions: { multipleValues: true },`);
+ lines.push(`${tab}\toptions: [{`);
+ lines.push(`${tab}\t\tname: ${quote(subName)},`);
+ lines.push(`${tab}\t\tdisplayName: ${quote(formatDisplayName(singularize(field.name)))},`);
+ lines.push(`${tab}\t\tvalues: [`);
+ const subSchema = resolveAllOf(field.items);
+ if (subSchema.properties) {
+ const sortedSubEntries = Object.entries(subSchema.properties).sort((a, b) => formatDisplayName(a[0]).localeCompare(formatDisplayName(b[0])));
+ for (const [subName2, rawSubProp] of sortedSubEntries) {
+ const subProp = rawSubProp.allOf ? resolveAllOf(rawSubProp) : rawSubProp;
+ const subSchemaType = getSchemaType(subProp);
+
+ // Check if this sub-field should use loadOptions
+ const loadOpt = LOAD_OPTIONS_SUBFIELDS[field.name]?.[subName2];
+ if (loadOpt) {
+ lines.push(`${tab}\t\t\t{`);
+ lines.push(`${tab}\t\t\t\tdisplayName: ${quote(loadOpt.displayName)},`);
+ lines.push(`${tab}\t\t\t\tname: ${quote(subName2)},`);
+ lines.push(`${tab}\t\t\t\ttype: 'options',`);
+ lines.push(`${tab}\t\t\t\ttypeOptions: { loadOptionsMethod: ${quote(loadOpt.method)} },`);
+ lines.push(`${tab}\t\t\t\tdefault: '',`);
+ lines.push(`${tab}\t\t\t\tdescription: ${quote(loadOpt.description)},`);
+ lines.push(`${tab}\t\t\t},`);
+ continue;
+ }
+
+ const subType = subProp.enum ? 'options' : (subSchemaType === 'boolean' ? 'boolean' : ((subSchemaType === 'integer' || subSchemaType === 'number') ? 'number' : 'string'));
+ lines.push(`${tab}\t\t\t{`);
+ lines.push(`${tab}\t\t\t\tdisplayName: ${quote(formatDisplayName(subName2))},`);
+ lines.push(`${tab}\t\t\t\tname: ${quote(subName2)},`);
+ lines.push(`${tab}\t\t\t\ttype: ${quote(subType)},`);
+ if (subType === 'options' && subProp.enum) {
+ const subEnumDescs = subProp['x-enum-descriptions'] as Record | undefined;
+ lines.push(`${tab}\t\t\t\toptions: [${generateEnumOptions(subProp.enum, subEnumDescs)}],`);
+ }
+ const subDefaultFallback = subType === 'number' ? 0 : (subType === 'boolean' ? false : '');
+ const subDefault = (typeof subProp.default === 'string' && /[а-яА-ЯёЁ]/.test(subProp.default)) ? subDefaultFallback : (subProp.default ?? subDefaultFallback);
+ lines.push(`${tab}\t\t\t\tdefault: ${JSON.stringify(subDefault)},`);
+ if (subProp.description) {
+ const subDisplayName = formatDisplayName(subName2);
+ const subDesc = getFieldDescription(subName2, subProp.description, op.endpoint.id, field.name);
+ if (subDesc && subDesc.toLowerCase() !== subDisplayName.toLowerCase()) {
+ lines.push(`${tab}\t\t\t\tdescription: ${quote(sanitizeDescription(subDesc))},`);
+ }
+ }
+ lines.push(`${tab}\t\t\t},`);
+ }
+ }
+ lines.push(`${tab}\t\t],`);
+ lines.push(`${tab}\t}],`);
+ }
+
+ if (field.type === 'string' && field.name === 'content') {
+ lines.push(`${tab}\ttypeOptions: { rows: 4 },`);
+ }
+
+ // Default value
+ const defaultVal = getDefaultValue(field, resource, op.v1Op, paramName);
+ lines.push(`${tab}\tdefault: ${JSON.stringify(defaultVal)},`);
+
+ // Description — EN spec first, then RU fallback
+ // Skip if identical to displayName (n8n lint: node-param-description-identical-to-display-name)
+ const rawDesc = getFieldDescription(field.name, field.description, op.endpoint.id);
+ if (rawDesc && rawDesc.toLowerCase() !== displayName.toLowerCase()) {
+ const desc = sanitizeDescription(n8nType === 'boolean' ? booleanDescription(rawDesc) : rawDesc);
+ lines.push(`${tab}\tdescription: ${quote(desc)},`);
+ }
+
+ // Hint — contextual tips for no-code users
+ const fieldHint = FIELD_HINTS[resource]?.[field.name];
+ if (fieldHint) {
+ lines.push(`${tab}\thint: ${quote(fieldHint)},`);
+ }
+
+ // displayOptions (only for top-level fields, not inside collections)
+ if (!isInsideCollection) {
+ lines.push(`${tab}\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: [${allOpValues.map(quote).join(', ')}] } },`);
+ }
+
+ // Placeholder from OpenAPI example
+ if (n8nType !== 'fixedCollection' && n8nType !== 'options' && n8nType !== 'boolean') {
+ const example = field.schema?.example;
+ const placeholder = getPlaceholder(example);
+ if (placeholder && !isPrimitiveArray(field)) {
+ lines.push(`${tab}\tplaceholder: ${quote(placeholder)},`);
+ }
+ }
+
+ // routing.send
+ if (n8nType === 'fixedCollection') {
+ // fixedCollection fields need routing on the collection itself
+ lines.push(`${tab}\trouting: { send: { type: 'body', property: ${quote(field.name)} } },`);
+ } else if (isPrimitiveArray(field)) {
+ // Primitive arrays: routing handled by splitCommaToArray preSend, not inline routing
+ const example = field.schema?.example;
+ const placeholder = getPlaceholder(example) ?? FIELD_PLACEHOLDERS[field.name];
+ if (placeholder) {
+ lines.push(`${tab}\tplaceholder: ${quote(placeholder)},`);
+ } else {
+ const arrayItemType = getArrayItemType(field);
+ lines.push(`${tab}\tplaceholder: ${quote(arrayItemType === 'int' ? '1,2,3' : 'tag1,tag2')},`);
+ }
+ // No routing here — splitCommaToArray preSend handles it
+ } else {
+ lines.push(`${tab}\trouting: { send: { type: 'body', property: ${quote(field.name)} } },`);
+ }
+
+ lines.push(`${tab}},`);
+ return lines.join('\n');
+}
+
+/** Generate a single query parameter field (reused for top-level and inside collection) */
+function generateQueryParamField(
+ param: Parameter,
+ paramName: string,
+ queryDesc: string | undefined,
+ endpointId: string,
+ allResourceValues: string[],
+ allOpValues: string[],
+ tab: string,
+ isInsideCollection = false,
+): string {
+ const lines: string[] = [];
+ const resolvedSchema = resolveQuerySchema(param.schema);
+ const n8nType = queryParamN8nType(param.schema);
+ lines.push(`${tab}{`);
+ lines.push(`${tab}\tdisplayName: ${quote(formatDisplayName(param.name))},`);
+ lines.push(`${tab}\tname: ${quote(paramName)},`);
+ lines.push(`${tab}\ttype: ${quote(n8nType)},`);
+ if (param.required && !isInsideCollection) {
+ lines.push(`${tab}\trequired: true,`);
+ }
+ if (n8nType === 'options' && resolvedSchema.enum) {
+ const qEnumDescs = getEnumDescriptions(param.name, endpointId);
+ lines.push(`${tab}\toptions: [${generateEnumOptions(resolvedSchema.enum, qEnumDescs)}],`);
+ }
+ // Use resolved default, falling back to original schema default (resolveAllOf may drop sibling defaults)
+ const rawDefault = resolvedSchema.default ?? param.schema.default;
+ const qDefault = (typeof rawDefault === 'string' && /[а-яА-ЯёЁ]/.test(rawDefault)) ? '' : (rawDefault ?? (n8nType === 'boolean' ? false : (n8nType === 'number' ? 0 : '')));
+ lines.push(`${tab}\tdefault: ${JSON.stringify(qDefault)},`);
+ if (queryDesc && queryDesc.toLowerCase() !== formatDisplayName(param.name).toLowerCase()) {
+ const desc = sanitizeDescription(n8nType === 'boolean' ? booleanDescription(queryDesc) : queryDesc);
+ lines.push(`${tab}\tdescription: ${quote(desc)},`);
+ }
+ const qPlaceholder = getPlaceholder(param.example ?? param.schema.example);
+ if (qPlaceholder && n8nType !== 'options' && n8nType !== 'boolean') {
+ lines.push(`${tab}\tplaceholder: ${quote(qPlaceholder)},`);
+ }
+ if (!isInsideCollection) {
+ lines.push(`${tab}\tdisplayOptions: { show: { resource: [${allResourceValues.map(quote).join(', ')}], operation: [${allOpValues.map(quote).join(', ')}] } },`);
+ }
+ lines.push(`${tab}\trouting: { send: { type: 'query', property: ${quote(param.name)} } },`);
+ lines.push(`${tab}},`);
+ return lines.join('\n');
+}
+
+/** Safe defaults for enum fields where alphabetical first value is dangerous.
+ * Multiple values: first match in the enum wins. E.g., 'role' tries 'member' then 'user'. */
+const SAFE_ENUM_DEFAULTS: Record = {
+ role: ['member', 'user'],
+};
+
+/** Fields that should be rendered as 'options' type regardless of OpenAPI schema.
+ * Used when OpenAPI uses plain integer but valid values are a small fixed set. */
+const FIELD_OPTIONS_OVERRIDES: Record; default: number | string }> = {
+ priority: {
+ options: [
+ { name: 'None', value: 0 },
+ { name: '1 — Normal', value: 1 },
+ { name: '2 — Important', value: 2 },
+ { name: '3 — Very Important', value: 3 },
+ ],
+ default: 0,
+ },
+};
+
+function getDefaultValue(field: BodyField, resource: string, op: string, paramName: string): unknown {
+ // Field type overrides have priority
+ const fieldOverride = FIELD_OPTIONS_OVERRIDES[field.name];
+ if (fieldOverride) return fieldOverride.default;
+ // Use OpenAPI default if set and not Russian text
+ if (field.default !== undefined && !(typeof field.default === 'string' && /[а-яА-ЯёЁ]/.test(field.default))) return field.default;
+ if (field.enum && field.enum.length > 0) {
+ // Use safe default if the field has one (e.g., role → 'member'/'user' instead of 'admin')
+ const safeCandidates = SAFE_ENUM_DEFAULTS[field.name];
+ if (safeCandidates) {
+ const safe = safeCandidates.find(c => field.enum!.includes(c));
+ if (safe) return safe;
+ }
+ return field.enum[0];
+ }
+
+ // Safe type-based defaults (examples go into placeholder, not default)
+ // Use n8n type to determine default — OpenAPI type may differ from n8n type
+ // (e.g., object without properties → string in n8n → default should be '' not {})
+ const n8nFieldType = toN8nType(field);
+ if (field.type === 'boolean') return false;
+ if (field.type === 'integer' || field.type === 'number') return 0;
+ if (field.type === 'array' && !field.items?.properties) return '';
+ if (field.type === 'array') return [];
+ if (field.type === 'object' && n8nFieldType === 'string') return '';
+ if (field.type === 'object') return '{}'; // n8n json type expects string default
+ return '';
+}
+
+/** Extract placeholder string from OpenAPI example (body field or query param) */
+function getPlaceholder(example: unknown): string | undefined {
+ if (example === undefined || example === null) return undefined;
+ if (typeof example === 'string') {
+ if (/[а-яА-ЯёЁ]/.test(example)) return undefined; // skip Russian
+ return example;
+ }
+ if (typeof example === 'number' || typeof example === 'boolean') return String(example);
+ if (Array.isArray(example)) {
+ // [1, 2, 3] → "1,2,3", [{...}] → JSON
+ if (example.length === 0) return undefined;
+ if (typeof example[0] === 'object') return JSON.stringify(example);
+ return example.join(',');
+ }
+ if (typeof example === 'object') return JSON.stringify(example);
+ return undefined;
+}
+
+function getCollectionName(resource: string, op: string, defaultName: string): string {
+ const v1Resource = V1_COMPAT_RESOURCES[resource] ?? resource;
+ return V1_COMPAT_COLLECTIONS[v1Resource]?.[op]?.[defaultName] ?? defaultName;
+}
+
+function singularize(name: string): string {
+ if (name.endsWith('ies')) return name.slice(0, -3) + 'y';
+ if (name.endsWith('s')) return name.slice(0, -1);
+ return name;
+}
+
+function formatDisplayName(name: string): string {
+ return name
+ .replace(/_/g, ' ')
+ .replace(/\b\w/g, c => c.toUpperCase())
+ .replace(/\bIds?\b/g, m => m === 'Id' ? 'ID' : 'IDs')
+ .replace(/\bUrl\b/g, 'URL');
+}
+
+/** Sanitize description for n8n lint compliance:
+ * - Single-sentence: strip trailing period (node-param-description-excess-final-period)
+ * - Multi-sentence: ensure trailing period (node-param-description-missing-final-period)
+ * - Uppercase "json" to "JSON" (node-param-description-miscased-json)
+ *
+ * The n8n lint rules count sentences by splitting on ". " (after removing "e.g.").
+ */
+function sanitizeDescription(desc: string): string {
+ let result = desc;
+ // Uppercase all occurrences of "json" (any case) to "JSON" — n8n lint requires uppercase
+ result = result.replace(/\bjson\b/gi, 'JSON');
+ // Strip leading quoted/backticked word so description starts with a letter (n8n lint)
+ result = result.replace(/^`([^`]+)`\s*/, (_m, w: string) => w + ' ');
+ result = result.replace(/^"([^"]+)"\s*/, (_m, w: string) => w + ' ');
+ // Ensure first character is uppercase
+ result = result.replace(/^([a-z])/, (_m, c: string) => c.toUpperCase());
+ // Count sentences: split on ". " after removing "e.g."
+ const egLess = result.replace(/e\.g\./g, '');
+ const sentenceCount = egLess.split('. ').length;
+ if (sentenceCount === 1) {
+ // Single sentence — must NOT end with period
+ result = result.replace(/\.\s*$/, '');
+ } else if (sentenceCount >= 2) {
+ // Multi-sentence — MUST end with period
+ if (!result.endsWith('.')) result += '.';
+ }
+ return result;
+}
+
+// ============================================================================
+// GENERATE MAIN NODE FILE
+// ============================================================================
+
+function generateV2Node(resources: string[]): string {
+ const imports = resources.map(r => {
+ const camel = snakeToCamel(r);
+ return `import { ${camel}Operations, ${camel}Fields } from './${snakeToPascal(r)}Description';`;
+ });
+
+ const sortedResources = [...resources].sort((a, b) => resourceDisplayName(a).localeCompare(resourceDisplayName(b)));
+ const resourceOptions = sortedResources.map(r => {
+ const display = resourceDisplayName(r);
+ return `\t\t\t\t\t{ name: ${quote(display)}, value: ${quote(r)} },`;
+ });
+
+ const properties = resources.map(r => `\t\t\t\t...${snakeToCamel(r)}Operations,\n\t\t\t\t...${snakeToCamel(r)}Fields,`);
+
+ return `import type {
+\tINodeType,
+\tINodeTypeBaseDescription,
+\tINodeTypeDescription,
+\tIExecuteFunctions,
+\tINodeExecutionData,
+} from 'n8n-workflow';
+import { NodeConnectionTypes } from 'n8n-workflow';
+import { router } from '../SharedRouter';
+import { searchChats, searchUsers, searchEntities, getCustomProperties } from '../GenericFunctions';
+
+${imports.join('\n')}
+
+export class PachcaV2 implements INodeType {
+\tdescription: INodeTypeDescription;
+
+\tconstructor(baseDescription: INodeTypeBaseDescription) {
+\t\tthis.description = {
+\t\t\t...baseDescription,
+\t\t\tversion: 2,
+\t\t\tdefaults: { name: 'Pachca' },
+\t\t\tusableAsTool: true,
+\t\t\tinputs: [NodeConnectionTypes.Main],
+\t\t\toutputs: [NodeConnectionTypes.Main],
+\t\t\tcredentials: [{ name: 'pachcaApi', required: true }],
+\t\t\tproperties: [
+\t\t\t\t{
+\t\t\t\t\tdisplayName: 'Resource',
+\t\t\t\t\tname: 'resource',
+\t\t\t\t\ttype: 'options',
+\t\t\t\t\tnoDataExpression: true,
+\t\t\t\t\toptions: [
+${resourceOptions.join('\n')}
+\t\t\t\t\t],
+\t\t\t\t\tdefault: 'message',
+\t\t\t\t},
+${properties.join('\n')}
+\t\t\t],
+\t\t};
+\t}
+
+\tasync execute(this: IExecuteFunctions): Promise {
+\t\treturn router.call(this);
+\t}
+
+\tmethods = {
+\t\tlistSearch: { searchChats, searchUsers, searchEntities },
+\t\tloadOptions: { getCustomProperties },
+\t};
+}
+`;
+}
+
+function generateVersionedWrapper(): string {
+ return `import { VersionedNodeType } from 'n8n-workflow';
+import type { INodeTypeBaseDescription } from 'n8n-workflow';
+import { PachcaV1 } from './V1/PachcaV1.node';
+import { PachcaV2 } from './V2/PachcaV2.node';
+
+export class Pachca extends VersionedNodeType {
+\tconstructor() {
+\t\tconst baseDescription: INodeTypeBaseDescription = {
+\t\t\tdisplayName: 'Pachca',
+\t\t\tname: 'pachca',
+\t\t\ticon: { light: 'file:pachca.svg', dark: 'file:pachca.dark.svg' },
+\t\t\tgroup: ['transform'],
+\t\t\tsubtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
+\t\t\tdescription: 'Interact with Pachca API',
+\t\t\tdefaultVersion: 2,
+\t\t};
+
+\t\tconst nodeVersions = {
+\t\t\t1: new PachcaV1(baseDescription),
+\t\t\t2: new PachcaV2(baseDescription),
+\t\t};
+
+\t\tsuper(nodeVersions, baseDescription);
+\t}
+}
+`;
+}
+
+// ============================================================================
+// GENERATE CREDENTIALS
+// ============================================================================
+
+function generateCredentials(): string {
+ return `import type {
+\tIAuthenticateGeneric,
+\tICredentialTestRequest,
+\tICredentialType,
+\tINodeProperties,
+} from 'n8n-workflow';
+
+export class PachcaApi implements ICredentialType {
+\tname = 'pachcaApi';
+\tdisplayName = 'Pachca API';
+\ticon = { light: 'file:pachca.svg', dark: 'file:pachca.dark.svg' } as const;
+\tdocumentationUrl = 'https://dev.pachca.com/api/authorization';
+
+\tproperties: INodeProperties[] = [
+\t\t{
+\t\t\tdisplayName: 'Base URL',
+\t\t\tname: 'baseUrl',
+\t\t\ttype: 'string',
+\t\t\tdefault: 'https://api.pachca.com/api/shared/v1',
+\t\t\tdescription: 'Base URL of the Pachca API. Change only for on-premise installations or API proxies.',
+\t\t},
+\t\t{
+\t\t\tdisplayName: 'Access Token',
+\t\t\tname: 'accessToken',
+\t\t\ttype: 'string',
+\t\t\ttypeOptions: { password: true },
+\t\t\tdefault: '',
+\t\t},
+\t\t{
+\t\t\tdisplayName: 'Bot ID',
+\t\t\tname: 'botId',
+\t\t\ttype: 'number',
+\t\t\tdefault: 0,
+\t\t\tdescription: 'Bot ID for automatic webhook registration (found in bot settings). Leave empty to auto-detect from token. Only needed for Pachca Trigger node.',
+\t\t\thint: 'Only required when using a bot token with the Pachca Trigger node',
+\t\t},
+\t\t{
+\t\t\tdisplayName: 'Webhook Signing Secret',
+\t\t\tname: 'signingSecret',
+\t\t\ttype: 'string',
+\t\t\ttypeOptions: { password: true },
+\t\t\tdefault: '',
+\t\t\tdescription: 'Used to verify incoming webhook requests from Pachca. Found in bot settings under the Webhook section.',
+\t\t\thint: 'Only required when using the Pachca Trigger node',
+\t\t},
+\t\t{
+\t\t\tdisplayName: 'Webhook Allowed IPs',
+\t\t\tname: 'webhookAllowedIps',
+\t\t\ttype: 'string',
+\t\t\tdefault: '',
+\t\t\tdescription: 'Comma-separated list of IP addresses allowed to send webhooks. Pachca sends from 37.200.70.177. Leave empty to allow all.',
+\t\t\tplaceholder: '37.200.70.177',
+\t\t\thint: 'Only used with the Pachca Trigger node',
+\t\t},
+\t];
+
+\tauthenticate: IAuthenticateGeneric = {
+\t\ttype: 'generic',
+\t\tproperties: {
+\t\t\theaders: {
+\t\t\t\tAuthorization: '=Bearer {{$credentials.accessToken}}',
+\t\t\t},
+\t\t},
+\t};
+
+\ttest: ICredentialTestRequest = {
+\t\trequest: {
+\t\t\tbaseURL: '={{$credentials.baseUrl}}',
+\t\t\turl: '/oauth/token/info',
+\t\t\tmethod: 'GET',
+\t\t},
+\t};
+}
+`;
+}
+
+// ============================================================================
+// WORKFLOW DESCRIPTIONS (English, from packages/spec/workflows.ts)
+// ============================================================================
+
+interface WorkflowStep {
+ descriptionEn?: string;
+ apiMethod?: string;
+ apiPath?: string;
+ notesEn?: string;
+}
+
+interface Workflow {
+ titleEn?: string;
+ steps: WorkflowStep[];
+}
+
+interface SkillConfig {
+ name: string;
+ tags: string[];
+}
+
+let WORKFLOWS: Record = {};
+let SKILL_TAG_MAP: SkillConfig[] = [];
+
+async function loadWorkflowsAndSkills(): Promise {
+ try {
+ const workflowsPath = path.join(ROOT, 'packages', 'spec', 'workflows.ts');
+ if (fs.existsSync(workflowsPath)) {
+ const mod = await import(workflowsPath);
+ WORKFLOWS = mod.WORKFLOWS ?? {};
+ }
+ } catch { /* workflows optional */ }
+
+ try {
+ const configPath = path.join(ROOT, 'apps', 'docs', 'scripts', 'skills', 'config.ts');
+ if (fs.existsSync(configPath)) {
+ const mod = await import(configPath);
+ SKILL_TAG_MAP = mod.SKILL_TAG_MAP ?? [];
+ }
+ } catch { /* skill config optional */ }
+}
+
+/**
+ * Get English description from workflows.ts for a given tag + API path.
+ * Falls back to undefined if no matching workflow found.
+ */
+function getWorkflowDescription(tag: string, operationPath: string): string | undefined {
+ const skill = SKILL_TAG_MAP.find(s => s.tags.includes(tag));
+ if (!skill) return undefined;
+
+ const workflows = WORKFLOWS[skill.name] ?? [];
+ for (const wf of workflows) {
+ for (const step of wf.steps) {
+ if (step.apiPath === operationPath && step.descriptionEn) {
+ return step.descriptionEn;
+ }
+ }
+ }
+ return undefined;
+}
+
+/**
+ * Get a documentation URL for an endpoint (links to dev.pachca.com).
+ */
+function getDocUrl(endpoint: Endpoint): string {
+ try {
+ return `https://dev.pachca.com${generateUrlFromOperation(endpoint)}`;
+ } catch {
+ return '';
+ }
+}
+
+/**
+ * Get a schema-aware default value using example-generator.
+ */
+function getSchemaDefault(schema: Schema | undefined): unknown {
+ if (!schema) return undefined;
+ try {
+ return generateExample(schema as any, 0, { requiredOnly: true, minimal: true });
+ } catch {
+ return undefined;
+ }
+}
+
+// ============================================================================
+// MAIN
+// ============================================================================
+
+function groupEndpointsByTag(endpoints: Endpoint[]): Map {
+ const groups = new Map();
+ for (const endpoint of endpoints) {
+ const tag = endpoint.tags[0] || 'Common';
+ if (!groups.has(tag)) groups.set(tag, []);
+ groups.get(tag)!.push(endpoint);
+ }
+ return groups;
+}
+
+/** Filter out "Common" tag endpoints that should map to specific resources */
+function resolveCommonEndpoints(byTag: Map): Map {
+ const common = byTag.get('Common') ?? [];
+ const result = new Map(byTag);
+ result.delete('Common');
+
+ for (const ep of common) {
+ // /custom_properties → customProperty
+ if (ep.path.startsWith('/custom_properties')) {
+ const tag = 'CustomProperty';
+ if (!result.has(tag)) result.set(tag, []);
+ result.get(tag)!.push(ep);
+ }
+ // /uploads → file
+ else if (ep.path.startsWith('/uploads')) {
+ const tag = 'File';
+ if (!result.has(tag)) result.set(tag, []);
+ result.get(tag)!.push(ep);
+ }
+ // /chats/exports → export
+ else if (ep.path.startsWith('/chats/exports')) {
+ const tag = 'Export';
+ if (!result.has(tag)) result.set(tag, []);
+ result.get(tag)!.push(ep);
+ }
+ // Other common endpoints
+ else {
+ if (!result.has('Common')) result.set('Common', []);
+ result.get('Common')!.push(ep);
+ }
+ }
+
+ return result;
+}
+
+// ============================================================================
+// POST-PROCESS: INJECT FILE UPLOAD FIELDS
+// ============================================================================
+
+/**
+ * The /uploads endpoint has no request body in OpenAPI, so the generator produces
+ * empty fileFields. This function injects the actual file upload UI fields.
+ * The actual upload logic is handled by uploadFileToS3() in Router.ts via execute().
+ */
+function injectFileUploadFields(outputDir: string): void {
+ const filePath = path.join(outputDir, 'FileDescription.ts');
+ if (!fs.existsSync(filePath)) {
+ console.log(' Skipped file upload injection (FileDescription.ts not found)');
+ return;
+ }
+
+ let code = fs.readFileSync(filePath, 'utf-8');
+
+ // Replace empty fileFields with actual upload fields
+ const fieldsCode = `export const fileFields: INodeProperties[] = [
+\t{
+\t\tdisplayName: 'File Source',
+\t\tname: 'fileSource',
+\t\ttype: 'options',
+\t\toptions: [
+\t\t\t{ name: 'Binary Data', value: 'binary' },
+\t\t\t{ name: 'URL', value: 'url' },
+\t\t],
+\t\tdefault: 'binary',
+\t\tdescription: 'Where to get the file to upload',
+\t\tdisplayOptions: { show: { resource: ['file'], operation: ['create'] } },
+\t},
+\t{
+\t\tdisplayName: 'File URL',
+\t\tname: 'fileUrl',
+\t\ttype: 'string',
+\t\trequired: true,
+\t\tdefault: '',
+\t\tdescription: 'URL of the file to upload',
+\t\tdisplayOptions: { show: { resource: ['file'], operation: ['create'], fileSource: ['url'] } },
+\t},
+\t{
+\t\tdisplayName: 'Input Binary Field',
+\t\tname: 'binaryProperty',
+\t\ttype: 'string',
+\t\trequired: true,
+\t\tdefault: 'data',
+\t\thint: 'The name of the input binary field containing the file to be uploaded',
+\t\tdisplayOptions: { show: { resource: ['file'], operation: ['create'], fileSource: ['binary'] } },
+\t},
+\t{
+\t\tdisplayName: 'Additional Fields',
+\t\tname: 'additionalFields',
+\t\ttype: 'collection',
+\t\tplaceholder: 'Add Field',
+\t\tdefault: {},
+\t\tdisplayOptions: { show: { resource: ['file'], operation: ['create'] } },
+\t\toptions: [
+\t\t\t{
+\t\t\t\tdisplayName: 'Content Type',
+\t\t\t\tname: 'contentType',
+\t\t\t\ttype: 'string',
+\t\t\t\tdefault: '',
+\t\t\t\tdescription: 'MIME type of the file (e.g. image/png). If not set, auto-detected from file extension.',
+\t\t\t},
+\t\t\t{
+\t\t\t\tdisplayName: 'File Name',
+\t\t\t\tname: 'fileName',
+\t\t\t\ttype: 'string',
+\t\t\t\tdefault: '',
+\t\t\t\tdescription: 'Name of the file. If not set, auto-detected from source.',
+\t\t\t},
+\t\t],
+\t},
+];`;
+
+ code = code.replace(
+ /export const fileFields: INodeProperties\[\] = \[\n\];/,
+ fieldsCode,
+ );
+
+ fs.writeFileSync(filePath, code);
+ console.log(' Injected file upload fields into FileDescription.ts');
+}
+
+// injectExportHandling() removed — export response format handled by Router.ts executeRoute()
+
+// ============================================================================
+// PACHCA TRIGGER NODE GENERATION
+// ============================================================================
+
+interface WebhookEventOption {
+ name: string;
+ value: string;
+ type: string;
+ event: string;
+}
+
+/**
+ * Extract webhook event types from the WebhookPayloadUnion schema.
+ * Each member has a `type` enum (e.g. ["button"]) and `event` enum (e.g. ["click"]).
+ */
+function extractWebhookEvents(schemas: Record): WebhookEventOption[] {
+ const union = schemas['WebhookPayloadUnion'];
+ if (!union?.anyOf) {
+ console.warn(' Warning: WebhookPayloadUnion not found or has no anyOf');
+ return [];
+ }
+
+ const events: WebhookEventOption[] = [];
+
+ for (const member of union.anyOf) {
+ const resolved = resolveAllOf(member);
+ const props = resolved.properties;
+ if (!props) continue;
+
+ // Extract type values (usually single-value enum like ["button"])
+ const typeProp = props['type'];
+ const typeResolved = typeProp?.allOf ? resolveAllOf(typeProp) : typeProp;
+ const typeValues = typeResolved?.enum as string[] | undefined;
+ if (!typeValues?.length) continue;
+
+ // Extract event values (may be multi-value enum like ["new","update","delete"])
+ const eventProp = props['event'];
+ const eventResolved = eventProp?.allOf ? resolveAllOf(eventProp) : eventProp;
+ const eventValues = eventResolved?.enum as string[] | undefined;
+ if (!eventValues?.length) continue;
+
+ for (const typeVal of typeValues) {
+ for (const eventVal of eventValues) {
+ const key = `${typeVal}:${eventVal}`;
+ const mapped = WEBHOOK_EVENT_MAP[key];
+ if (mapped) {
+ events.push({ ...mapped, type: typeVal, event: eventVal });
+ } else {
+ // Fallback: generate name/value from type+event
+ const name = `${snakeToPascal(typeVal)} ${snakeToPascal(eventVal)}`;
+ const value = `${typeVal}_${eventVal}`;
+ events.push({ name, value, type: typeVal, event: eventVal });
+ }
+ }
+ }
+ }
+
+ // Sort by name for consistent output
+ events.sort((a, b) => a.name.localeCompare(b.name));
+ return events;
+}
+
+/**
+ * Generate the PachcaTrigger.node.ts file content from extracted webhook events.
+ */
+function generateTriggerNode(events: WebhookEventOption[]): string {
+ // Build event options for the n8n property
+ const optionEntries = events.map(e =>
+ `\t\t\t\t\t{ name: '${e.name}', value: '${e.value}' },`
+ ).join('\n');
+
+ // Build event filter map: value → { type, event }
+ const filterEntries = events.map(e =>
+ `\t'${e.value}': { type: '${e.type}', event: '${e.event}' },`
+ ).join('\n');
+
+ return `import type {
+\tIDataObject,
+\tIHookFunctions,
+\tINodeType,
+\tINodeTypeDescription,
+\tIWebhookFunctions,
+\tIWebhookResponseData,
+} from 'n8n-workflow';
+import { NodeConnectionTypes } from 'n8n-workflow';
+import { verifyWebhookSignature, resolveBotId } from './GenericFunctions';
+
+/** Maps n8n event value to webhook payload { type, event } for filtering */
+const EVENT_FILTER: Record = {
+${filterEntries}
+};
+
+export class PachcaTrigger implements INodeType {
+\tdescription: INodeTypeDescription = {
+\t\tdisplayName: 'Pachca Trigger',
+\t\tname: 'pachcaTrigger',
+\t\ticon: { light: 'file:pachca.svg', dark: 'file:pachca.dark.svg' },
+\t\tgroup: ['trigger'],
+\t\tversion: 1,
+\t\tsubtitle: '={{$parameter["event"]}}',
+\t\tdescription: 'Starts workflow when Pachca events occur',
+\t\tdefaults: { name: 'Pachca Trigger' },
+\t\tinputs: [],
+\t\toutputs: [NodeConnectionTypes.Main],
+\t\tcredentials: [{ name: 'pachcaApi', required: true }],
+\t\twebhooks: [
+\t\t\t{
+\t\t\t\tname: 'default',
+\t\t\t\thttpMethod: 'POST',
+\t\t\t\tresponseMode: 'onReceived',
+\t\t\t\tpath: 'webhook',
+\t\t\t\trawBody: true,
+\t\t\t},
+\t\t],
+\t\tproperties: [
+\t\t\t{
+\t\t\t\tdisplayName: 'Event',
+\t\t\t\tname: 'event',
+\t\t\t\ttype: 'options',
+\t\t\t\tnoDataExpression: true,
+\t\t\t\toptions: [
+\t\t\t\t\t{ name: 'All Events', value: '*' },
+${optionEntries}
+\t\t\t\t],
+\t\t\t\tdefault: 'new_message',
+\t\t\t\tdescription: 'The event to listen for',
+\t\t\t},
+\t\t],
+\t\tusableAsTool: true,
+\t};
+
+\twebhookMethods = {
+\t\tdefault: {
+\t\t\tasync checkExists(this: IHookFunctions): Promise {
+\t\t\t\tconst credentials = await this.getCredentials('pachcaApi');
+\t\t\t\tlet botId: number;
+\t\t\t\ttry {
+\t\t\t\t\tbotId = await resolveBotId(this, credentials);
+\t\t\t\t} catch {
+\t\t\t\t\treturn false; // Network error → treat as not exists, will trigger create
+\t\t\t\t}
+\t\t\t\tif (!botId) return false;
+\t\t\t\tconst webhookUrl = this.getNodeWebhookUrl('default');
+\t\t\t\ttry {
+\t\t\t\t\tconst response = (await this.helpers.httpRequestWithAuthentication.call(
+\t\t\t\t\t\tthis,
+\t\t\t\t\t\t'pachcaApi',
+\t\t\t\t\t\t{
+\t\t\t\t\t\t\tmethod: 'GET',
+\t\t\t\t\t\t\turl: \`\${credentials.baseUrl}/bots/\${botId}\`,
+\t\t\t\t\t\t},
+\t\t\t\t\t)) as IDataObject;
+\t\t\t\t\tconst data = response.data as IDataObject | undefined;
+\t\t\t\t\tconst webhook = data?.webhook as IDataObject | undefined;
+\t\t\t\t\treturn webhook?.outgoing_url === webhookUrl;
+\t\t\t\t} catch {
+\t\t\t\t\treturn false;
+\t\t\t\t}
+\t\t\t},
+
+\t\t\tasync create(this: IHookFunctions): Promise {
+\t\t\t\tconst credentials = await this.getCredentials('pachcaApi');
+\t\t\t\tconst botId = await resolveBotId(this, credentials);
+\t\t\t\tif (!botId) {
+\t\t\t\t\tthis.logger.warn('Pachca Trigger: token is not a bot token. Webhook was NOT registered automatically. Configure webhook URL manually in Pachca bot settings.');
+\t\t\t\t\treturn true;
+\t\t\t\t}
+\t\t\t\tconst webhookUrl = this.getNodeWebhookUrl('default');
+\t\t\t\tawait this.helpers.httpRequestWithAuthentication.call(this, 'pachcaApi', {
+\t\t\t\t\tmethod: 'PUT',
+\t\t\t\t\turl: \`\${credentials.baseUrl}/bots/\${botId}\`,
+\t\t\t\t\tbody: { bot: { webhook: { outgoing_url: webhookUrl } } },
+\t\t\t\t});
+\t\t\t\treturn true;
+\t\t\t},
+
+\t\t\tasync delete(this: IHookFunctions): Promise {
+\t\t\t\tconst credentials = await this.getCredentials('pachcaApi');
+\t\t\t\tlet botId: number;
+\t\t\t\ttry {
+\t\t\t\t\tbotId = await resolveBotId(this, credentials);
+\t\t\t\t} catch {
+\t\t\t\t\treturn true; // Can't resolve bot → nothing to clean up
+\t\t\t\t}
+\t\t\t\tif (!botId) return true;
+\t\t\t\ttry {
+\t\t\t\t\tawait this.helpers.httpRequestWithAuthentication.call(this, 'pachcaApi', {
+\t\t\t\t\t\tmethod: 'PUT',
+\t\t\t\t\t\turl: \`\${credentials.baseUrl}/bots/\${botId}\`,
+\t\t\t\t\t\tbody: { bot: { webhook: { outgoing_url: '' } } },
+\t\t\t\t\t});
+\t\t\t\t} catch {
+\t\t\t\t\t// Ignore errors on cleanup
+\t\t\t\t}
+\t\t\t\treturn true;
+\t\t\t},
+\t\t},
+\t};
+
+\tasync webhook(this: IWebhookFunctions): Promise {
+\t\tconst body = this.getBodyData() as IDataObject;
+\t\tconst headerData = this.getHeaderData() as IDataObject;
+\t\tconst credentials = await this.getCredentials('pachcaApi');
+\t\tconst event = this.getNodeParameter('event') as string;
+
+\t\t// IP allowlist check
+\t\tconst allowedIps = ((credentials.webhookAllowedIps as string) || '').split(',').map(s => s.trim()).filter(Boolean);
+\t\tif (allowedIps.length > 0) {
+\t\t\tconst request = this.getRequestObject();
+\t\t\tconst clientIp = (request.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() || request.socket?.remoteAddress || '';
+\t\t\tconst normalizedIp = clientIp.replace(/^::ffff:/, '');
+\t\t\tif (!allowedIps.includes(normalizedIp)) {
+\t\t\t\treturn { webhookResponse: 'Forbidden' };
+\t\t\t}
+\t\t}
+
+\t\t// Signing secret verification (use raw body bytes for accurate HMAC)
+\t\tconst signingSecret = ((credentials.signingSecret as string) || '').trim();
+\t\tif (signingSecret) {
+\t\t\tconst signature = headerData['pachca-signature'] as string;
+\t\t\tif (!signature) {
+\t\t\t\treturn { webhookResponse: 'Rejected' };
+\t\t\t}
+\t\t\tconst request = this.getRequestObject();
+\t\t\tconst rawBody = request.rawBody
+\t\t\t\t? request.rawBody.toString()
+\t\t\t\t: JSON.stringify(body);
+\t\t\tif (
+\t\t\t\t!verifyWebhookSignature(
+\t\t\t\t\trawBody,
+\t\t\t\t\tsignature,
+\t\t\t\t\tsigningSecret,
+\t\t\t\t)
+\t\t\t) {
+\t\t\t\treturn { webhookResponse: 'Rejected' };
+\t\t\t}
+\t\t}
+
+\t\t// Replay protection — reject events older than 5 minutes
+\t\tconst webhookTs = body.webhook_timestamp as number | undefined;
+\t\tif (webhookTs) {
+\t\t\tconst ageMs = Date.now() - webhookTs * 1000;
+\t\t\tif (ageMs < -60_000 || ageMs > 5 * 60 * 1000) {
+\t\t\t\treturn { webhookResponse: 'Rejected' };
+\t\t\t}
+\t\t}
+
+\t\t// Event filtering using type+event from payload
+\t\tif (event !== '*') {
+\t\t\tconst filter = EVENT_FILTER[event];
+\t\t\tif (filter) {
+\t\t\t\tconst bodyType = body.type as string | undefined;
+\t\t\t\tconst bodyEvent = body.event as string | undefined;
+\t\t\t\tif (bodyType !== filter.type || bodyEvent !== filter.event) {
+\t\t\t\t\treturn { webhookResponse: 'Event filtered' };
+\t\t\t\t}
+\t\t\t}
+\t\t}
+
+\t\treturn {
+\t\t\tworkflowData: [this.helpers.returnJsonArray(body)],
+\t\t};
+\t}
+}
+`;
+}
+
+// ============================================================================
+// ROUTER GENERATION (execute() dispatcher)
+// ============================================================================
+
+/** Detect special handler for an operation */
+function getSpecialHandler(resource: string, v2Op: string): string | null {
+ if (resource === 'file' && v2Op === 'create') return 'fileUpload';
+ if (resource === 'message' && (v2Op === 'create' || v2Op === 'update')) return 'messageButtons';
+ if (resource === 'form' && v2Op === 'create') return 'formBlocks';
+ if (resource === 'bot' && v2Op === 'update') return 'botWebhook';
+ if (resource === 'user' && v2Op === 'getAll') return 'userGetAllFilters';
+ if (resource === 'export' && v2Op === 'get') return 'exportDownload';
+ if ((resource === 'profile' || resource === 'user') && v2Op === 'updateAvatar') return 'avatarUpload';
+ return null;
+}
+
+/** Build one FieldMap entry as TypeScript literal */
+function buildFieldMapStr(resource: string, op: OperationInfo, f: BodyField): string {
+ const n8nName = getParamName(resource, op.v1Op, f.name);
+ const parts: string[] = [`api: '${f.name}'`, `n8n: '${n8nName}'`];
+
+ if (isPrimitiveArray(f)) {
+ parts.push('isArray: true');
+ parts.push(`arrayType: '${getArrayItemType(f)}'`);
+ }
+
+ // resourceLocator for body fields (e.g., message.entity_id → searchEntities)
+ if (BODY_FIELD_SEARCH[resource]?.[f.name]) {
+ parts.push('locator: true');
+ }
+
+ // fixedCollection subKey
+ if (f.type === 'array' && f.items?.properties) {
+ const subKey = V1_COMPAT_SUBCOLLECTIONS[n8nName] ?? singularize(n8nName);
+ parts.push(`subKey: '${subKey}'`);
+ }
+
+ return `{ ${parts.join(', ')} }`;
+}
+
+/** Build one RouteConfig entry for a regular operation */
+function buildRouteEntry(resource: string, op: OperationInfo): string {
+ const { v2Op, endpoint, fields, queryParams, pathParams, hasPagination, wrapperKey } = op;
+ const parts: string[] = [];
+
+ parts.push(`method: '${endpoint.method}' as IHttpRequestMethods`);
+ parts.push(`path: '${endpoint.path}'`);
+
+ // Path params
+ const ppEntries: string[] = [];
+ for (const param of pathParams) {
+ const n8nName = getParamName(resource, op.v1Op, param.name);
+ const locator = !!PATH_PARAM_SEARCH[resource]?.[param.name];
+ const fallbackInfo = V1_ID_FALLBACKS[resource];
+ const isSharedOp = fallbackInfo?.sharedOps.includes(v2Op);
+ const v1Fallback = isSharedOp && n8nName === 'id' ? fallbackInfo!.v1Name : undefined;
+
+ const pp: string[] = [`api: '${param.name}'`, `n8n: '${n8nName}'`];
+ if (locator) pp.push('locator: true');
+ if (v1Fallback) pp.push(`v1Fallback: '${v1Fallback}'`);
+ ppEntries.push(`{ ${pp.join(', ')} }`);
+ }
+ if (ppEntries.length) parts.push(`pathParams: [${ppEntries.join(', ')}]`);
+
+ if (wrapperKey) parts.push(`wrapperKey: '${wrapperKey}'`);
+
+ // Sibling fields (stay outside wrapper)
+ const siblingFields = fields.filter(f => f.isSibling).map(f => f.name);
+ if (siblingFields.length) parts.push(`siblingFields: [${siblingFields.map(f => `'${f}'`).join(', ')}]`);
+
+ if (hasPagination) parts.push('paginated: true');
+ if (resource === 'export') parts.push('noDataWrapper: true');
+
+ const special = getSpecialHandler(resource, v2Op);
+ if (special) parts.push(`special: '${special}'`);
+
+ // For file upload / avatar upload, skip body/query — handled entirely by special handler
+ if (special === 'fileUpload' || special === 'avatarUpload') {
+ return `\t\t${v2Op}: {\n\t\t\t${parts.join(',\n\t\t\t')},\n\t\t}`;
+ }
+
+ // Required body fields
+ const promotedSet = PROMOTED_TOP_LEVEL_FIELDS[resource]?.[v2Op] ?? PROMOTED_TOP_LEVEL_FIELDS[resource]?.[op.v1Op];
+ let requiredFields = fields.filter(f => (f.required || promotedSet?.has(f.name)) && !f.readOnly);
+ const optionalFields = fields.filter(f => !f.required && !promotedSet?.has(f.name) && !f.readOnly);
+
+ // Filter out special fields handled by special handlers
+ if (resource === 'bot') requiredFields = requiredFields.filter(f => f.name !== 'webhook');
+ if (resource === 'form') requiredFields = requiredFields.filter(f => f.name !== 'blocks');
+
+ const bodyMapEntries: string[] = [];
+ for (const f of requiredFields) {
+ bodyMapEntries.push(buildFieldMapStr(resource, op, f));
+ }
+ if (bodyMapEntries.length) {
+ parts.push(`bodyMap: [\n\t\t\t\t${bodyMapEntries.join(',\n\t\t\t\t')},\n\t\t\t]`);
+ }
+
+ // Optional body fields (skip buttons for message — handled by messageButtons special)
+ let filteredOptional = optionalFields;
+ if (resource === 'message') filteredOptional = filteredOptional.filter(f => f.name !== 'buttons');
+
+ const optBodyEntries: string[] = [];
+ for (const f of filteredOptional) {
+ optBodyEntries.push(buildFieldMapStr(resource, op, f));
+ }
+ // v1 compat: extra body fields not in OpenAPI (e.g. groupTag color)
+ const extraBodyFields = V1_EXTRA_BODY_FIELDS[resource]?.[v2Op];
+ if (extraBodyFields) {
+ for (const [api, n8n] of extraBodyFields) {
+ optBodyEntries.push(`{ api: '${api}', n8n: '${n8n}' }`);
+ }
+ }
+ if (optBodyEntries.length) {
+ parts.push(`optionalBodyMap: [\n\t\t\t\t${optBodyEntries.join(',\n\t\t\t\t')},\n\t\t\t]`);
+ }
+
+ // v1 collection name override
+ const v1Resource = V1_COMPAT_RESOURCES[resource] ?? resource;
+ const collOverride = V1_COMPAT_COLLECTIONS[v1Resource]?.[op.v1Op]?.['additionalFields'];
+ if (collOverride && collOverride !== 'additionalFields') {
+ parts.push(`v1Collection: '${collOverride}'`);
+ }
+
+ // Query params
+ const nonPaginationParams = queryParams.filter(
+ p => !['limit', 'cursor', 'per', 'page'].includes(p.name) && !p.name.includes('{')
+ );
+ const QUERY_FILTER_RESOURCES = new Set(['search', 'security']);
+ const PRIMARY_QUERY_PARAMS = new Set(['query']);
+ const shouldWrapFilters = QUERY_FILTER_RESOURCES.has(resource);
+ // BUG 1 fix: user.getAll query param must be in optionalQueryMap (read from v1Collection with fallback)
+ const isUserGetAll = resource === 'user' && op.v2Op === 'getAll';
+
+ // Optional boolean query params always go to optionalQueryMap (same logic as Description generation)
+ const isOptionalBool = (p: Parameter) => !p.required && queryParamN8nType(p.schema) === 'boolean';
+ const topLevelQueryParams = shouldWrapFilters
+ ? nonPaginationParams.filter(p => p.required || PRIMARY_QUERY_PARAMS.has(p.name))
+ : isUserGetAll ? [] : nonPaginationParams.filter(p => !isOptionalBool(p));
+ const filterQueryParams = shouldWrapFilters
+ ? nonPaginationParams.filter(p => !p.required && !PRIMARY_QUERY_PARAMS.has(p.name))
+ : isUserGetAll ? nonPaginationParams : nonPaginationParams.filter(p => isOptionalBool(p));
+
+ const queryMapEntries: string[] = [];
+ for (const p of topLevelQueryParams) {
+ const n8nName = getParamName(resource, op.v1Op, p.name);
+ const locator = !!SEARCHABLE_QUERY_PARAMS[p.name];
+ const qm: string[] = [`api: '${p.name}'`, `n8n: '${n8nName}'`];
+ if (locator) qm.push('locator: true');
+ if (p.required) qm.push('required: true');
+ if (isQueryParamArray(p)) {
+ qm.push('isArray: true');
+ qm.push(`arrayType: '${getQueryArrayItemType(p)}'`);
+ }
+ queryMapEntries.push(`{ ${qm.join(', ')} }`);
+ }
+ if (queryMapEntries.length) parts.push(`queryMap: [${queryMapEntries.join(', ')}]`);
+
+ const optQueryEntries: string[] = [];
+ for (const p of filterQueryParams) {
+ const n8nName = getParamName(resource, op.v1Op, p.name);
+ const qm: string[] = [`api: '${p.name}'`, `n8n: '${n8nName}'`];
+ if (isQueryParamArray(p)) {
+ qm.push('isArray: true');
+ qm.push(`arrayType: '${getQueryArrayItemType(p)}'`);
+ }
+ optQueryEntries.push(`{ ${qm.join(', ')} }`);
+ }
+ // v1 compat: pagination fields from V1-specific collections (not in OpenAPI spec)
+ const v1Pagination = V1_COMPAT_PAGINATION[resource]?.[v2Op];
+ if (v1Pagination) {
+ for (const [api, n8n] of v1Pagination) {
+ optQueryEntries.push(`{ api: '${api}', n8n: '${n8n}' }`);
+ }
+ }
+ if (optQueryEntries.length) parts.push(`optionalQueryMap: [${optQueryEntries.join(', ')}]`);
+
+ return `\t\t${v2Op}: {\n\t\t\t${parts.join(',\n\t\t\t')},\n\t\t}`;
+}
+
+/** Build one RouteConfig entry for a v1 alias operation */
+function buildAliasRouteEntry(
+ resource: string,
+ aliasOp: string,
+ routing: { method: string; url: string; wrapperKey?: string; pagination?: boolean; splitComma?: [string, string, 'int' | 'string'][] },
+): string {
+ const parts: string[] = [];
+ parts.push(`method: '${routing.method}' as IHttpRequestMethods`);
+
+ // Convert declarative URL '=/path/{{$parameter["x"]}}/sub' → '/path/{x}/sub'
+ let apiPath = routing.url.replace(/^=/, '');
+ const pathParamNames: string[] = [];
+ apiPath = apiPath.replace(/\{\{\$parameter\["(\w+)"\]\}\}/g, (_, paramName) => {
+ pathParamNames.push(paramName);
+ return `{${paramName}}`;
+ });
+ parts.push(`path: '${apiPath}'`);
+
+ if (pathParamNames.length) {
+ const ppEntries = pathParamNames.map(p => `{ api: '${p}', n8n: '${p}' }`);
+ parts.push(`pathParams: [${ppEntries.join(', ')}]`);
+ }
+
+ if (routing.pagination) parts.push('paginated: true');
+
+ // v1 collection name override for alias ops
+ const v1Resource = V1_COMPAT_RESOURCES[resource] ?? resource;
+ const collOverride = V1_COMPAT_COLLECTIONS[v1Resource]?.[aliasOp]?.['additionalFields'];
+ if (collOverride && collOverride !== 'additionalFields') {
+ parts.push(`v1Collection: '${collOverride}'`);
+ }
+
+ // v1 optional query params for alias ops (e.g. role for chat.getMembers)
+ const aliasQueryParams = V1_ALIAS_QUERY_PARAMS[resource]?.[aliasOp];
+ if (aliasQueryParams?.length) {
+ const qEntries = aliasQueryParams.map(([api, n8n]) => `{ api: '${api}', n8n: '${n8n}' }`);
+ parts.push(`optionalQueryMap: [${qEntries.join(', ')}]`);
+ }
+
+ // Special handler for alias ops
+ const aliasSpecial = V1_ALIAS_SPECIALS[resource]?.[aliasOp];
+ if (aliasSpecial) parts.push(`special: '${aliasSpecial}'`);
+
+ // Collect body fields from splitComma + V1_ALIAS_FIELDS routing
+ const bodyEntries: string[] = [];
+ const optBodyEntries: string[] = [];
+ if (routing.splitComma) {
+ for (const [n8n, api, type] of routing.splitComma) {
+ bodyEntries.push(`{ api: '${api}', n8n: '${n8n}', isArray: true, arrayType: '${type}' }`);
+ }
+ }
+ const aliasFieldDefs = V1_ALIAS_FIELDS[resource]?.[aliasOp];
+ if (aliasFieldDefs) {
+ for (const f of aliasFieldDefs.fields) {
+ if (f.routing) {
+ const entry = `{ api: '${f.routing.send.property}', n8n: '${f.name}' }`;
+ if (f.required) {
+ bodyEntries.push(entry);
+ } else {
+ optBodyEntries.push(entry);
+ }
+ }
+ }
+ }
+ if (bodyEntries.length) parts.push(`bodyMap: [${bodyEntries.join(', ')}]`);
+ if (optBodyEntries.length) parts.push(`optionalBodyMap: [${optBodyEntries.join(', ')}]`);
+
+ return `\t\t${aliasOp}: {\n\t\t\t${parts.join(',\n\t\t\t')},\n\t\t}`;
+}
+
+/** Generate the complete Router.ts file */
+function generateRouter(resourceOperations: Map): string {
+ // --- Build ROUTES table entries ---
+ const routeBlocks: string[] = [];
+ for (const [resource, operations] of resourceOperations) {
+ const opEntries: string[] = [];
+ for (const op of operations) {
+ opEntries.push(buildRouteEntry(resource, op));
+ }
+ // V1 alias operation routes
+ for (const aliasOp of (V1_ALIAS_OPS[resource] ?? [])) {
+ const routing = V1_ALIAS_ROUTING[resource]?.[aliasOp];
+ if (routing) opEntries.push(buildAliasRouteEntry(resource, aliasOp, routing));
+ }
+ // V1-only form operations (no real API call)
+ if (resource === 'form') {
+ opEntries.push(`\t\tprocessSubmission: {\n\t\t\tmethod: 'GET' as IHttpRequestMethods,\n\t\t\tpath: '/profile',\n\t\t\tspecial: 'formProcessSubmission',\n\t\t}`);
+ }
+ if (opEntries.length) {
+ routeBlocks.push(`\t${resource}: {\n${opEntries.join(',\n')},\n\t}`);
+ }
+ }
+
+ // --- Build V1_RESOURCE_MAP (inverse of V1_COMPAT_RESOURCES) ---
+ const v1ResEntries = Object.entries(V1_COMPAT_RESOURCES)
+ .map(([v2, v1]) => `\t${v1}: '${v2}'`).join(',\n');
+
+ // --- Build V1_OP_MAP (inverse of V1_COMPAT_OPS) ---
+ const v1OpEntries = Object.entries(V1_COMPAT_OPS).map(([resource, ops]) => {
+ const inverted = Object.entries(ops).map(([v2, v1]) => `${v1}: '${v2}'`).join(', ');
+ return `\t${resource}: { ${inverted} }`;
+ }).join(',\n');
+
+ return `// ============================================================================
+// SharedRouter.ts — Generated execute() dispatcher for Pachca node (shared by V1 and V2)
+// DO NOT EDIT — this file is auto-generated by generate-n8n.ts
+// ============================================================================
+
+import type { IExecuteFunctions, INodeExecutionData, IDataObject, IHttpRequestMethods } from 'n8n-workflow';
+import {
+\tmakeApiRequest,
+\tmakeApiRequestAllPages,
+\tresolveResourceLocator,
+\tbuildButtonRows,
+\tcleanFileAttachments,
+\tresolveFormBlocksFromParams,
+\tuploadFileToS3,
+\tuploadAvatar,
+\tsplitAndValidateCommaList,
+\tsimplifyItem,
+\tsanitizeBaseUrl,
+} from './GenericFunctions';
+
+// ============================================================================
+// Types
+// ============================================================================
+
+interface PathParam {
+\tapi: string;
+\tn8n: string;
+\tlocator?: boolean;
+\tv1Fallback?: string;
+}
+
+interface FieldMap {
+\tapi: string;
+\tn8n: string;
+\tisArray?: boolean;
+\tarrayType?: 'int' | 'string';
+\tlocator?: boolean;
+\tsubKey?: string;
+}
+
+interface QueryMap {
+\tapi: string;
+\tn8n: string;
+\tlocator?: boolean;
+\trequired?: boolean;
+\tisArray?: boolean;
+\tarrayType?: 'int' | 'string';
+}
+
+interface RouteConfig {
+\tmethod: IHttpRequestMethods;
+\tpath: string;
+\tpathParams?: PathParam[];
+\twrapperKey?: string;
+\tsiblingFields?: string[];
+\tpaginated?: boolean;
+\tnoDataWrapper?: boolean;
+\tbodyMap?: FieldMap[];
+\toptionalBodyMap?: FieldMap[];
+\tqueryMap?: QueryMap[];
+\toptionalQueryMap?: QueryMap[];
+\tv1Collection?: string;
+\tspecial?: string;
+}
+
+// ============================================================================
+// V1 Compatibility Maps
+// ============================================================================
+
+/** v1 resource name → v2 resource name */
+const V1_RESOURCE_MAP: Record = {
+${v1ResEntries},
+};
+
+/** v1 operation name → v2 operation name (per v2 resource) */
+const V1_OP_MAP: Record> = {
+${v1OpEntries},
+};
+
+// ============================================================================
+// Routes Table
+// ============================================================================
+
+const ROUTES: Record> = {
+${routeBlocks.join(',\n')},
+};
+
+// ============================================================================
+// Router Entry Point
+// ============================================================================
+
+export async function router(this: IExecuteFunctions): Promise {
+\tconst items = this.getInputData();
+\tconst returnData: INodeExecutionData[] = [];
+\tconst nodeVersion = this.getNode().typeVersion;
+
+\tfor (let i = 0; i < items.length; i++) {
+\t\ttry {
+\t\t\tlet resource = this.getNodeParameter('resource', i) as string;
+\t\t\tlet operation = this.getNodeParameter('operation', i) as string;
+
+\t\t\t// v1 compat: map old resource/operation names to v2
+\t\t\tif (nodeVersion === 1) {
+\t\t\t\tresource = V1_RESOURCE_MAP[resource] ?? resource;
+\t\t\t\toperation = V1_OP_MAP[resource]?.[operation] ?? operation;
+\t\t\t}
+
+\t\t\tconst route = ROUTES[resource]?.[operation];
+\t\t\tif (!route) {
+\t\t\t\tthrow new Error(\`Unknown operation: \${resource}.\${operation}\`);
+\t\t\t}
+
+\t\t\tconst result = await executeRoute.call(this, route, resource, i, nodeVersion);
+\t\t\treturnData.push(...result.map(item => ({ ...item, pairedItem: { item: i } })));
+\t\t} catch (error) {
+\t\t\tif (this.continueOnFail()) {
+\t\t\t\treturnData.push({ json: { error: (error as Error).message }, pairedItem: { item: i } });
+\t\t\t} else {
+\t\t\t\tthrow error;
+\t\t\t}
+\t\t}
+\t}
+
+\treturn [returnData];
+}
+
+// ============================================================================
+// Generic Route Executor
+// ============================================================================
+
+async function executeRoute(
+\tthis: IExecuteFunctions,
+\troute: RouteConfig,
+\tresource: string,
+\ti: number,
+\tnodeVersion: number,
+): Promise {
+\t// === Special-only operations (no API call needed) ===
+\tif (route.special === 'fileUpload') {
+\t\tconst result = await uploadFileToS3(this, i);
+\t\treturn [{ json: result }];
+\t}
+\tif (route.special === 'formProcessSubmission') {
+\t\t// v1 only: pass through form submission data from webhook input
+\t\tconst inputData = this.getInputData()[i].json;
+\t\treturn [{ json: inputData }];
+\t}
+\tif (route.special === 'exportDownload') {
+\t\tconst exportId = this.getNodeParameter('id', i) as number;
+\t\tconst credentials = await this.getCredentials('pachcaApi');
+\t\tconst base = sanitizeBaseUrl(credentials.baseUrl as string);
+\t\tconst resp = await this.helpers.httpRequestWithAuthentication.call(this, 'pachcaApi', {
+\t\t\tmethod: 'GET',
+\t\t\turl: \`\${base}/chats/exports/\${exportId}\`,
+\t\t\tignoreHttpStatusErrors: true,
+\t\t\treturnFullResponse: true,
+\t\t\tdisableFollowRedirect: true,
+\t\t}) as { statusCode: number; headers: Record; body: unknown };
+\t\tconst location = resp.headers?.location;
+\t\tif (location) {
+\t\t\treturn [{ json: { id: exportId, url: location } as unknown as IDataObject }];
+\t\t}
+\t\tif (typeof resp.body === 'object' && resp.body) {
+\t\t\treturn [{ json: resp.body as IDataObject }];
+\t\t}
+\t\treturn [{ json: { id: exportId, success: true } as unknown as IDataObject }];
+\t}
+\tif (route.special === 'avatarUpload') {
+\t\tlet avatarUrl = route.path;
+\t\tfor (const pp of route.pathParams ?? []) {
+\t\t\tconst value = this.getNodeParameter(pp.n8n, i) as number;
+\t\t\tavatarUrl = avatarUrl.replace(\`{\${pp.api}}\`, String(value));
+\t\t}
+\t\tconst result = await uploadAvatar(this, i, avatarUrl);
+\t\treturn [{ json: result }];
+\t}
+\t// === Build URL with path params ===
+\tlet url = route.path;
+\tfor (const pp of route.pathParams ?? []) {
+\t\tlet value: number | string;
+\t\tif (pp.locator) {
+\t\t\tvalue = resolveResourceLocator(this, pp.n8n, i, pp.v1Fallback);
+\t\t} else {
+\t\t\ttry {
+\t\t\t\tvalue = this.getNodeParameter(pp.n8n, i) as number;
+\t\t\t} catch (e) {
+\t\t\t\tif (pp.v1Fallback) {
+\t\t\t\t\tvalue = this.getNodeParameter(pp.v1Fallback, i) as number;
+\t\t\t\t} else {
+\t\t\t\t\tthrow e;
+\t\t\t\t}
+\t\t\t}
+\t\t}
+\t\tif (value === undefined || value === null || value === '') {
+\t\t\tthrow new Error(\`Missing required path parameter: \${pp.n8n}\`);
+\t\t}
+\t\turl = url.replace(\`{\${pp.api}}\`, String(value));
+\t}
+
+\t// === Build body from required fields ===
+\tconst body: IDataObject = {};
+\tfor (const fm of route.bodyMap ?? []) {
+\t\tlet raw: unknown;
+\t\tif (fm.locator) {
+\t\t\traw = resolveResourceLocator(this, fm.n8n, i);
+\t\t} else {
+\t\t\traw = this.getNodeParameter(fm.n8n, i);
+\t\t}
+\t\tif (fm.isArray && typeof raw === 'string') {
+\t\t\tbody[fm.api] = splitAndValidateCommaList(this, raw, fm.n8n, fm.arrayType!, i);
+\t\t} else {
+\t\t\tbody[fm.api] = raw as IDataObject;
+\t\t}
+\t}
+
+\t// === Read optional body fields from collection ===
+\tconst collectionName = (nodeVersion === 1 && route.v1Collection) ? route.v1Collection : 'additionalFields';
+\tlet additional: IDataObject = {};
+\ttry { additional = this.getNodeParameter(collectionName, i, {}) as IDataObject; } catch { /* no collection */ }
+
+\tfor (const fm of route.optionalBodyMap ?? []) {
+\t\tlet val: unknown = additional[fm.n8n];
+
+\t\t// If not in collection, try as top-level param (v1 compat for V1_TOP_LEVEL_PARAMS)
+\t\tif (val === undefined) {
+\t\t\ttry { val = this.getNodeParameter(fm.n8n, i, undefined); } catch { /* not present */ }
+\t\t}
+
+\t\tif (val === undefined || val === null || val === '') continue;
+
+\t\t// fixedCollection: extract inner array using subKey
+\t\tif (fm.subKey && typeof val === 'object' && !Array.isArray(val)) {
+\t\t\tval = (val as IDataObject)[fm.subKey] ?? val;
+\t\t}
+\t\tif (fm.isArray && typeof val === 'string') {
+\t\t\tbody[fm.api] = splitAndValidateCommaList(this, val, fm.n8n, fm.arrayType!, i);
+\t\t} else if (fm.locator && typeof val === 'object' && val !== null && (val as IDataObject).__rl) {
+\t\t\tbody[fm.api] = (val as IDataObject).value;
+\t\t} else {
+\t\t\tbody[fm.api] = val as IDataObject;
+\t\t}
+\t}
+
+\t// === Read top-level query params ===
+\tconst qs: IDataObject = {};
+\tfor (const qm of route.queryMap ?? []) {
+\t\ttry {
+\t\t\tlet val: unknown;
+\t\t\tif (qm.locator) {
+\t\t\t\tval = resolveResourceLocator(this, qm.n8n, i);
+\t\t\t} else {
+\t\t\t\tval = this.getNodeParameter(qm.n8n, i);
+\t\t\t}
+\t\t\tif (val !== undefined && val !== null && val !== '') {
+\t\t\t\tif (qm.isArray && typeof val === 'string') {
+\t\t\t\t\tqs[qm.api] = splitAndValidateCommaList(this, val, qm.n8n, qm.arrayType!, i);
+\t\t\t\t} else {
+\t\t\t\t\tqs[qm.api] = val as IDataObject;
+\t\t\t\t}
+\t\t\t}
+\t\t} catch (e) {
+\t\t\tif (qm.required) throw e;
+\t\t}
+\t}
+
+\t// Read query params from collection, with top-level fallback (same pattern as optionalBodyMap)
+\tfor (const qm of route.optionalQueryMap ?? []) {
+\t\tlet val: unknown = additional[qm.n8n];
+\t\tif (val === undefined) {
+\t\t\ttry { val = this.getNodeParameter(qm.n8n, i, undefined); } catch { /* not present */ }
+\t\t}
+\t\tif (val !== undefined && val !== null && val !== '') {
+\t\t\tif (qm.isArray && typeof val === 'string') {
+\t\t\t\tqs[qm.api] = splitAndValidateCommaList(this, val, qm.n8n, qm.arrayType!, i);
+\t\t\t} else {
+\t\t\t\tqs[qm.api] = val;
+\t\t\t}
+\t\t}
+\t}
+
+\t// === Special handlers ===
+\tif (route.special === 'messageButtons') {
+\t\tconst buttons = buildButtonRows(this, i);
+\t\tif (buttons.length) body.buttons = buttons;
+\t\tconst files = cleanFileAttachments(this, i);
+\t\tif (files.length) body.files = files;
+\t}
+\tif (route.special === 'formBlocks') {
+\t\tconst blocks = resolveFormBlocksFromParams(this, i);
+\t\tif (blocks.length) body.blocks = blocks;
+\t}
+\tif (route.special === 'unfurlLinkPreviews') {
+\t\t// v1 compat: linkPreviews was a fixedCollection { preview: [{ url, title, description, imageUrl }] }
+\t\t// v2: linkPreviews is a JSON string. API expects { "url": { title, description, image_url } }
+\t\tconst raw = body.link_previews;
+\t\tif (raw && typeof raw === 'object' && !Array.isArray(raw) && (raw as IDataObject).preview) {
+\t\t\tconst previews = (raw as IDataObject).preview as IDataObject[];
+\t\t\tconst converted: IDataObject = {};
+\t\t\tfor (const p of previews) {
+\t\t\t\tif (!p.url) continue;
+\t\t\t\tconst entry: IDataObject = {};
+\t\t\t\tif (p.title) entry.title = p.title;
+\t\t\t\tif (p.description) entry.description = p.description;
+\t\t\t\tif (p.imageUrl) entry.image_url = p.imageUrl;
+\t\t\t\tconverted[p.url as string] = entry;
+\t\t\t}
+\t\t\tbody.link_previews = converted;
+\t\t} else if (typeof raw === 'string') {
+\t\t\ttry { body.link_previews = JSON.parse(raw); } catch { /* leave as-is */ }
+\t\t}
+\t}
+\tif (route.special === 'botWebhook') {
+\t\tlet webhookUrl: string | undefined;
+\t\ttry { webhookUrl = this.getNodeParameter('webhookUrl', i, '') as string; } catch { /* */ }
+\t\tif (webhookUrl) {
+\t\t\tbody.webhook = { outgoing_url: webhookUrl };
+\t\t}
+\t}
+
+\t// === Wrap body in key ===
+\tlet finalBody: IDataObject | undefined;
+\tif (Object.keys(body).length > 0) {
+\t\tif (route.wrapperKey) {
+\t\t\tconst inner: IDataObject = {};
+\t\t\tconst outer: IDataObject = {};
+\t\t\tconst siblingSet = new Set(route.siblingFields ?? []);
+\t\t\tfor (const [k, v] of Object.entries(body)) {
+\t\t\t\tif (siblingSet.has(k)) {
+\t\t\t\t\touter[k] = v;
+\t\t\t\t} else {
+\t\t\t\t\tinner[k] = v;
+\t\t\t\t}
+\t\t\t}
+\t\t\tfinalBody = { [route.wrapperKey]: inner, ...outer };
+\t\t} else {
+\t\t\tfinalBody = body;
+\t\t}
+\t}
+
+\t// === Execute API call ===
+\tif (route.paginated) {
+\t\tlet results = await makeApiRequestAllPages.call(
+\t\t\tthis, route.method, url, qs, i, resource, nodeVersion,
+\t\t);
+
+\t\t// v1 client-side post-filters for user.getAll
+\t\tif (route.special === 'userGetAllFilters' && nodeVersion === 1) {
+\t\t\tlet filterOptions: IDataObject = {};
+\t\t\ttry { filterOptions = this.getNodeParameter('filterOptions', i, {}) as IDataObject; } catch { /* */ }
+
+\t\t\tconst filterRole = filterOptions.filterRole as string[] | undefined;
+\t\t\tconst filterBot = filterOptions.filterBot as string | undefined;
+\t\t\tconst filterSuspended = filterOptions.filterSuspended as string | undefined;
+\t\t\tconst filterInviteStatus = filterOptions.filterInviteStatus as string[] | undefined;
+
+\t\t\tif (filterRole?.length || (filterBot && filterBot !== 'all') ||
+\t\t\t\t(filterSuspended && filterSuspended !== 'all') || filterInviteStatus?.length) {
+\t\t\t\tresults = results.filter(item => {
+\t\t\t\t\tconst d = item.json;
+\t\t\t\t\tif (filterRole?.length && !filterRole.includes(d.role as string)) return false;
+\t\t\t\t\tif (filterBot === 'users' && d.bot === true) return false;
+\t\t\t\t\tif (filterBot === 'bots' && d.bot !== true) return false;
+\t\t\t\t\tif (filterSuspended === 'active' && d.suspended === true) return false;
+\t\t\t\t\tif (filterSuspended === 'suspended' && d.suspended !== true) return false;
+\t\t\t\t\tif (filterInviteStatus?.length && !filterInviteStatus.includes(d.invite_status as string)) return false;
+\t\t\t\t\treturn true;
+\t\t\t\t});
+\t\t\t}
+\t\t}
+
+\t\treturn results;
+\t}
+
+\tconst response = await makeApiRequest.call(
+\t\tthis, route.method, url, finalBody,
+\t\tObject.keys(qs).length > 0 ? qs : undefined, i,
+\t);
+
+\t// === Handle response ===
+\tif (route.method === 'DELETE') {
+\t\treturn [{ json: { success: true } }];
+\t}
+
+\t// Handle 204 No Content for non-DELETE methods (archive, unarchive, pin, addMembers, etc.)
+\tif (!response || (typeof response === 'object' && Object.keys(response).length === 0)) {
+\t\treturn [{ json: { success: true } }];
+\t}
+
+\tif (route.noDataWrapper) {
+\t\tif (!response || (typeof response === 'object' && Object.keys(response).length === 0)) {
+\t\t\treturn [{ json: { success: true } as unknown as IDataObject }];
+\t\t}
+\t\treturn [{ json: response }];
+\t}
+
+\tconst data = (response.data as IDataObject) ?? response;
+
+\t// Simplify for GET single item (v2 only)
+\tif (nodeVersion >= 2 && route.method === 'GET' && !route.paginated) {
+\t\tlet doSimplify = false;
+\t\ttry { doSimplify = this.getNodeParameter('simplify', i, false) as boolean; } catch { /* */ }
+\t\tif (doSimplify) {
+\t\t\treturn [{ json: simplifyItem(data, resource) }];
+\t\t}
+\t}
+
+\treturn [{ json: data }];
+}
+`;
+}
+
+async function main() {
+ console.log('Loading workflows and skill config...');
+ await loadWorkflowsAndSkills();
+ console.log(`Loaded ${Object.keys(WORKFLOWS).length} workflow groups, ${SKILL_TAG_MAP.length} skills`);
+
+ console.log('Parsing EN OpenAPI spec...');
+ const api = parseOpenAPI(EN_SPEC_PATH);
+ console.log(`Found ${api.endpoints.length} endpoints, ${api.tags.length} tags`);
+
+ // Also parse EN spec into lookup maps for getFieldDescription/getEnumDescriptions
+ const enApi = api;
+ for (const ep of enApi.endpoints) {
+ enEndpoints.set(ep.id, ep);
+ for (const param of ep.parameters) {
+ if (param.description) enParamDescs.set(`${ep.id}:${param.name}`, param.description);
+ }
+ const enFields = extractBodyFields(ep.requestBody);
+ for (const field of enFields) {
+ if (field.description) enBodyDescs.set(`${ep.id}:${field.name}`, field.description);
+ // Enum descriptions from EN spec
+ const enResolved = resolveAllOf(field.schema);
+ const enEnumDesc = enResolved['x-enum-descriptions'] as Record | undefined;
+ if (enEnumDesc) enEnumDescs.set(`${ep.id}:${field.name}`, enEnumDesc);
+ // Sub-field descriptions for fixedCollection items
+ if (field.items?.properties) {
+ const subSchema = resolveAllOf(field.items);
+ if (subSchema.properties) {
+ for (const [subName, subProp] of Object.entries(subSchema.properties)) {
+ if (subProp.description) enSubFieldDescs.set(`${ep.id}:${field.name}:${subName}`, subProp.description);
+ }
+ }
+ }
+ }
+ // Query param enum descriptions
+ for (const param of ep.parameters) {
+ const pResolved = resolveQuerySchema(param.schema);
+ const pEnumDesc = pResolved?.['x-enum-descriptions'] as Record | undefined;
+ if (pEnumDesc) enEnumDescs.set(`${ep.id}:${param.name}`, pEnumDesc);
+ }
+ }
+ console.log(` EN descriptions: ${enParamDescs.size} params, ${enBodyDescs.size} body fields, ${enSubFieldDescs.size} sub-fields`);
+
+ // Load scope → roles mapping from x-scope-roles in TokenScope schema
+ // Parse the YAML section directly to avoid adding js-yaml dependency
+ const specContent = fs.readFileSync(EN_SPEC_PATH, 'utf8');
+ const scopeRolesStart = specContent.indexOf('x-scope-roles:');
+ if (scopeRolesStart !== -1) {
+ // Find the indentation level of x-scope-roles
+ const lineStart = specContent.lastIndexOf('\n', scopeRolesStart) + 1;
+ const baseIndent = scopeRolesStart - lineStart;
+ const lines = specContent.substring(scopeRolesStart).split('\n');
+ let currentScope = '';
+ for (let i = 1; i < lines.length; i++) {
+ const line = lines[i];
+ if (line.trim() === '') continue;
+ const indent = line.length - line.trimStart().length;
+ if (indent <= baseIndent) break; // End of x-scope-roles section
+ const trimmed = line.trim();
+ if (trimmed.endsWith(':') && !trimmed.startsWith('-')) {
+ currentScope = trimmed.slice(0, -1);
+ scopeRolesMap.set(currentScope, []);
+ } else if (trimmed.startsWith('- ') && currentScope) {
+ scopeRolesMap.get(currentScope)!.push(trimmed.slice(2));
+ }
+ }
+ }
+ console.log(` Scope-roles: ${scopeRolesMap.size} scopes loaded`);
+
+ let byTag = groupEndpointsByTag(api.endpoints);
+ byTag = resolveCommonEndpoints(byTag);
+
+ const generatedResources: string[] = [];
+ const resourceOperations = new Map();
+
+ // Ensure output directories exist
+ fs.mkdirSync(OUTPUT_DIR, { recursive: true });
+ fs.mkdirSync(CREDS_DIR, { recursive: true });
+ const ROOT_NODE_DIR = path.resolve(__dirname, '../nodes/Pachca');
+
+ for (const [tag, endpoints] of byTag) {
+ const resource = tag === 'CustomProperty' ? 'customProperty'
+ : tag === 'File' ? 'file'
+ : tag === 'Export' ? 'export'
+ : tagToResource(tag);
+
+ if (resource === 'common') continue; // Skip unmapped common endpoints
+
+ const operations: OperationInfo[] = [];
+
+ for (const ep of endpoints) {
+ const v2Op = endpointToOperation(ep, resource);
+ const v1Op = getV1OpValue(resource, v2Op);
+ const fields = extractBodyFields(ep.requestBody);
+ const queryParams = ep.parameters.filter(p => p.in === 'query');
+ const pathParams = ep.parameters.filter(p => p.in === 'path');
+ const hasPagination = queryParams.some(p => p.name === 'cursor');
+ const wrapperKey = getWrapperKey(ep.requestBody);
+ const epTag = ep.tags[0] || tag;
+ const workflowDesc = getWorkflowDescription(epTag, ep.path);
+ const docUrl = getDocUrl(ep);
+ const enEp = enEndpoints.get(ep.id);
+ const baseDesc = workflowDesc ?? enEp?.summary ?? enEp?.description ?? ep.summary ?? ep.description ?? '';
+ const description = docUrl ? `${baseDesc}. API docs` : baseDesc;
+
+ operations.push({
+ v2Op, v1Op, endpoint: ep, fields, queryParams, pathParams,
+ hasPagination, wrapperKey, description,
+ });
+ }
+
+ if (operations.length === 0) continue;
+
+ const code = generateResourceDescription(resource, operations);
+ const fileName = `${snakeToPascal(resource)}Description.ts`;
+ const filePath = path.join(OUTPUT_DIR, fileName);
+ fs.writeFileSync(filePath, code);
+ console.log(` Generated ${fileName} (${operations.length} operations)`);
+ generatedResources.push(resource);
+ resourceOperations.set(resource, operations);
+ }
+
+ // Post-process: inject file upload fields
+ injectFileUploadFields(OUTPUT_DIR);
+
+ // Generate Router.ts (execute() dispatcher with ROUTES table)
+ const routerCode = generateRouter(resourceOperations);
+ fs.writeFileSync(path.join(ROOT_NODE_DIR, 'SharedRouter.ts'), routerCode);
+ console.log(` Generated SharedRouter.ts (${resourceOperations.size} resources)`);
+
+ // Generate main node file
+ const mainNode = generateV2Node(generatedResources);
+ fs.writeFileSync(path.join(OUTPUT_DIR, 'PachcaV2.node.ts'), mainNode);
+ console.log(` Generated PachcaV2.node.ts (${generatedResources.length} resources)`);
+
+ // Generate VersionedNodeType wrapper (root level)
+ const wrapperNode = generateVersionedWrapper();
+ fs.writeFileSync(path.join(ROOT_NODE_DIR, 'Pachca.node.ts'), wrapperNode);
+ console.log(' Generated Pachca.node.ts (VersionedNodeType wrapper)');
+
+ // Generate credentials
+ const credentials = generateCredentials();
+ fs.writeFileSync(path.join(CREDS_DIR, 'PachcaApi.credentials.ts'), credentials);
+ console.log(' Generated PachcaApi.credentials.ts');
+
+ // Generate trigger node from webhook payload schemas
+ const webhookEvents = extractWebhookEvents(api.schemas);
+ const triggerNode = generateTriggerNode(webhookEvents);
+ fs.writeFileSync(path.join(ROOT_NODE_DIR, 'PachcaTrigger.node.ts'), triggerNode);
+ console.log(` Generated PachcaTrigger.node.ts (${webhookEvents.length} event types)`);
+
+ // Generate Codex files for node discoverability
+ const codex = {
+ categories: ['Communication'],
+ subcategories: { Communication: ['Team Messaging'] },
+ resources: { primaryDocumentation: [{ url: 'https://dev.pachca.com/guides/n8n/overview' }] },
+ alias: ['pachca', 'messenger', 'chat', 'team', 'corporate messenger'],
+ };
+ fs.writeFileSync(path.join(ROOT_NODE_DIR, 'Pachca.node.json'), JSON.stringify(codex, null, '\t') + '\n');
+ fs.writeFileSync(path.join(ROOT_NODE_DIR, 'PachcaTrigger.node.json'), JSON.stringify(codex, null, '\t') + '\n');
+ console.log(' Generated Pachca.node.json + PachcaTrigger.node.json (codex)');
+
+ console.log(`\nDone! Generated ${generatedResources.length} resources, 1 main node, 1 trigger node, 1 credentials, 2 codex.`);
+}
+
+main().catch(err => {
+ console.error('Generation failed:', err);
+ process.exit(1);
+});
diff --git a/integrations/n8n/scripts/utils.ts b/integrations/n8n/scripts/utils.ts
new file mode 100644
index 00000000..c1b97b5c
--- /dev/null
+++ b/integrations/n8n/scripts/utils.ts
@@ -0,0 +1,165 @@
+/**
+ * Utility functions adapted from packages/cli/scripts/generate-cli.ts
+ * for the n8n generator. Uses types from @pachca/openapi-parser.
+ */
+
+import type { Schema, RequestBody } from '@pachca/openapi-parser';
+import { resolveAllOf, getSchemaType } from '@pachca/openapi-parser';
+
+export interface BodyField {
+ name: string;
+ type: string;
+ format?: string;
+ required: boolean;
+ description?: string;
+ enum?: unknown[];
+ maxLength?: number;
+ maximum?: number;
+ minimum?: number;
+ readOnly?: boolean;
+ nullable?: boolean;
+ default?: unknown;
+ items?: Schema;
+ properties?: Record;
+ allOf?: Schema[];
+ oneOf?: Schema[];
+ /** True if this field is a sibling of the wrapper (not inside it) */
+ isSibling?: boolean;
+ /** Full schema reference for complex fields */
+ schema: Schema;
+}
+
+/**
+ * Extract body fields from request body, unwrapping wrapper objects.
+ * e.g., { message: { content, entity_id } } → [content, entity_id] with wrapper key "message"
+ */
+export function extractBodyFields(requestBody?: RequestBody): BodyField[] {
+ if (!requestBody) return [];
+
+ const jsonContent = requestBody.content['application/json'];
+ const multipartContent = requestBody.content['multipart/form-data'];
+ const content = jsonContent || multipartContent;
+ if (!content) return [];
+
+ const schema = content.schema;
+ if (!schema) return [];
+
+ const resolved = resolveAllOf(schema);
+ const properties = resolved.properties || {};
+ const requiredFields = new Set(resolved.required || []);
+
+ // Check if top-level has exactly one object property that can be unwrapped
+ const topKeys = Object.keys(properties);
+ const objectKeys = topKeys.filter((k) => {
+ const inner = resolveAllOf(properties[k]);
+ return inner.properties && Object.keys(inner.properties).length > 0;
+ });
+
+ if (objectKeys.length === 1) {
+ const wrapperKey = objectKeys[0];
+ const wrapper = properties[wrapperKey];
+ const innerResolved = resolveAllOf(wrapper);
+ if (innerResolved.properties) {
+ const innerRequired = new Set(innerResolved.required || []);
+ const fields: BodyField[] = Object.entries(innerResolved.properties)
+ .filter(([, v]) => !v.readOnly)
+ .map(([name, propSchema]) => {
+ const resolved2 = resolveAllOf(propSchema);
+ return {
+ name,
+ type: getSchemaType(propSchema),
+ format: propSchema.format,
+ required: innerRequired.has(name),
+ description: propSchema.description,
+ enum: propSchema.enum ?? resolved2.enum,
+ maxLength: propSchema.maxLength,
+ maximum: propSchema.maximum,
+ minimum: propSchema.minimum,
+ readOnly: propSchema.readOnly,
+ nullable: propSchema.nullable,
+ default: propSchema.default,
+ items: propSchema.items ?? resolved2.items,
+ properties: resolved2.properties,
+ allOf: propSchema.allOf,
+ oneOf: propSchema.oneOf,
+ schema: propSchema,
+ };
+ });
+
+ // Add sibling scalar fields (non-object top-level properties)
+ for (const key of topKeys) {
+ if (key === wrapperKey) continue;
+ const propSchema = resolveAllOf(properties[key]);
+ fields.push({
+ name: key,
+ type: getSchemaType(propSchema),
+ format: propSchema.format,
+ required: requiredFields.has(key),
+ description: propSchema.description,
+ enum: propSchema.enum,
+ maxLength: propSchema.maxLength,
+ maximum: propSchema.maximum,
+ minimum: propSchema.minimum,
+ readOnly: propSchema.readOnly,
+ nullable: propSchema.nullable,
+ default: propSchema.default,
+ items: propSchema.items,
+ properties: propSchema.properties,
+ schema: propSchema,
+ isSibling: true,
+ });
+ }
+
+ return fields;
+ }
+ }
+
+ // Flat properties
+ return Object.entries(properties)
+ .filter(([, v]) => !v.readOnly)
+ .map(([name, propSchema]) => {
+ const resolved2 = resolveAllOf(propSchema);
+ return {
+ name,
+ type: getSchemaType(propSchema),
+ format: propSchema.format,
+ required: requiredFields.has(name),
+ description: propSchema.description,
+ enum: propSchema.enum ?? resolved2.enum,
+ maxLength: propSchema.maxLength,
+ maximum: propSchema.maximum,
+ minimum: propSchema.minimum,
+ readOnly: propSchema.readOnly,
+ nullable: propSchema.nullable,
+ default: propSchema.default,
+ items: propSchema.items ?? resolved2.items,
+ properties: resolved2.properties,
+ allOf: propSchema.allOf,
+ oneOf: propSchema.oneOf,
+ schema: propSchema,
+ };
+ });
+}
+
+/**
+ * Detect body wrapper key from request schema.
+ * e.g., { user: { email, first_name } } → "user"
+ */
+export function getWrapperKey(requestBody?: RequestBody): string | null {
+ if (!requestBody) return null;
+ const jsonContent = requestBody.content['application/json'];
+ if (!jsonContent?.schema) return null;
+
+ const schema = jsonContent.schema;
+ const resolved = resolveAllOf(schema);
+ if (!resolved.properties) return null;
+
+ const keys = Object.keys(resolved.properties);
+ const objectKeys = keys.filter((k) => {
+ const inner = resolveAllOf(resolved.properties![k]);
+ return inner.properties && Object.keys(inner.properties).length > 0;
+ });
+
+ if (objectKeys.length === 1) return objectKeys[0];
+ return null;
+}
diff --git a/integrations/n8n/tests/compatibility.test.ts b/integrations/n8n/tests/compatibility.test.ts
new file mode 100644
index 00000000..0a93c3de
--- /dev/null
+++ b/integrations/n8n/tests/compatibility.test.ts
@@ -0,0 +1,701 @@
+import { describe, it, expect, beforeAll } from 'vitest';
+import * as fs from 'fs';
+import * as path from 'path';
+
+/**
+ * Compatibility tests for the n8n Pachca node.
+ *
+ * With the VersionedNodeType refactoring, the node structure is:
+ * - ROOT_DIR: Pachca.node.ts (VersionedNodeType wrapper), SharedRouter.ts, GenericFunctions.ts, PachcaTrigger.node.ts
+ * - V1_DIR: Frozen V1 description files (from npm v1.0.27)
+ * - V2_DIR: Generated V2 description files (clean, no v1 compat)
+ *
+ * These tests verify that:
+ * 1. All expected files exist in the correct directories
+ * 2. V1 descriptions maintain v1 operation names, parameter names, and alias operations
+ * 3. V2 descriptions have clean structure (no @version, no v1 ops)
+ * 4. SharedRouter.ts contains V1_RESOURCE_MAP and V1_OP_MAP for runtime v1→v2 mapping
+ * 5. Pachca.node.ts is a VersionedNodeType wrapper with defaultVersion: 2
+ */
+
+const ROOT_DIR = path.resolve(__dirname, '../nodes/Pachca');
+const V1_DIR = path.join(ROOT_DIR, 'V1');
+const V2_DIR = path.join(ROOT_DIR, 'V2');
+const CREDS_DIR = path.resolve(__dirname, '../credentials');
+
+// ============================================================================
+// STRUCTURAL TESTS
+// ============================================================================
+
+describe('Generated files exist', () => {
+ const rootFiles = [
+ 'Pachca.node.ts',
+ 'PachcaTrigger.node.ts',
+ 'GenericFunctions.ts',
+ 'SharedRouter.ts',
+ ];
+
+ for (const file of rootFiles) {
+ it(`should have ${file} in root`, () => {
+ expect(fs.existsSync(path.join(ROOT_DIR, file))).toBe(true);
+ });
+ }
+
+ it('should have V1/ directory', () => {
+ expect(fs.existsSync(V1_DIR)).toBe(true);
+ });
+
+ it('should have V2/ directory', () => {
+ expect(fs.existsSync(V2_DIR)).toBe(true);
+ });
+
+ const v1Files = [
+ 'PachcaV1.node.ts',
+ 'MessageDescription.ts',
+ 'UserDescription.ts',
+ 'ChatDescription.ts',
+ 'TaskDescription.ts',
+ 'BotDescription.ts',
+ 'GroupTagDescription.ts',
+ 'ThreadDescription.ts',
+ 'ReactionsDescription.ts',
+ 'StatusDescription.ts',
+ 'CustomFieldsDescription.ts',
+ 'FileDescription.ts',
+ 'FormDescription.ts',
+ ];
+
+ for (const file of v1Files) {
+ it(`should have V1/${file}`, () => {
+ expect(fs.existsSync(path.join(V1_DIR, file))).toBe(true);
+ });
+ }
+
+ const v2Files = [
+ 'PachcaV2.node.ts',
+ 'MessageDescription.ts',
+ 'UserDescription.ts',
+ 'ChatDescription.ts',
+ 'TaskDescription.ts',
+ 'BotDescription.ts',
+ 'GroupTagDescription.ts',
+ 'MemberDescription.ts',
+ 'ReactionDescription.ts',
+ 'ThreadDescription.ts',
+ 'ProfileDescription.ts',
+ 'SearchDescription.ts',
+ 'FormDescription.ts',
+ 'SecurityDescription.ts',
+ 'CustomPropertyDescription.ts',
+ 'FileDescription.ts',
+ 'LinkPreviewDescription.ts',
+ 'ReadMemberDescription.ts',
+ 'ExportDescription.ts',
+ ];
+
+ for (const file of v2Files) {
+ it(`should have V2/${file}`, () => {
+ expect(fs.existsSync(path.join(V2_DIR, file))).toBe(true);
+ });
+ }
+
+ it('should have PachcaApi.credentials.ts', () => {
+ expect(fs.existsSync(path.join(CREDS_DIR, 'PachcaApi.credentials.ts'))).toBe(true);
+ });
+});
+
+// ============================================================================
+// MAIN NODE TESTS (VersionedNodeType wrapper)
+// ============================================================================
+
+describe('Pachca.node.ts (VersionedNodeType wrapper)', () => {
+ let content: string;
+
+ beforeAll(() => {
+ content = fs.readFileSync(path.join(ROOT_DIR, 'Pachca.node.ts'), 'utf-8');
+ });
+
+ it('should export Pachca class extending VersionedNodeType', () => {
+ expect(content).toContain('export class Pachca extends VersionedNodeType');
+ });
+
+ it('should have defaultVersion: 2', () => {
+ expect(content).toContain('defaultVersion: 2');
+ });
+
+ it('should import and instantiate PachcaV1', () => {
+ expect(content).toContain('PachcaV1');
+ expect(content).toContain('new PachcaV1');
+ });
+
+ it('should import and instantiate PachcaV2', () => {
+ expect(content).toContain('PachcaV2');
+ expect(content).toContain('new PachcaV2');
+ });
+
+ it('should have nodeVersions with keys 1 and 2', () => {
+ expect(content).toMatch(/1:\s*new PachcaV1/);
+ expect(content).toMatch(/2:\s*new PachcaV2/);
+ });
+});
+
+// ============================================================================
+// V1 NODE CLASS
+// ============================================================================
+
+describe('PachcaV1.node.ts', () => {
+ let content: string;
+
+ beforeAll(() => {
+ content = fs.readFileSync(path.join(V1_DIR, 'PachcaV1.node.ts'), 'utf-8');
+ });
+
+ it('should export PachcaV1 class', () => {
+ expect(content).toContain('export class PachcaV1');
+ });
+
+ it('should have version: 1', () => {
+ expect(content).toContain('version: 1');
+ });
+
+ it('should reference pachcaApi credentials', () => {
+ expect(content).toContain("name: 'pachcaApi'");
+ });
+
+ it('should have v1 resource values (reactions, status, customFields)', () => {
+ expect(content).toContain("value: 'reactions'");
+ expect(content).toContain("value: 'status'");
+ expect(content).toContain("value: 'customFields'");
+ });
+
+ it('should use router from SharedRouter', () => {
+ expect(content).toContain("from '../SharedRouter'");
+ });
+});
+
+// ============================================================================
+// V2 NODE CLASS
+// ============================================================================
+
+describe('PachcaV2.node.ts', () => {
+ let content: string;
+
+ beforeAll(() => {
+ content = fs.readFileSync(path.join(V2_DIR, 'PachcaV2.node.ts'), 'utf-8');
+ });
+
+ it('should export PachcaV2 class', () => {
+ expect(content).toContain('export class PachcaV2');
+ });
+
+ it('should have version: 2', () => {
+ expect(content).toContain('version: 2');
+ });
+
+ it('should have usableAsTool: true', () => {
+ expect(content).toContain('usableAsTool: true');
+ });
+
+ it('should reference pachcaApi credentials', () => {
+ expect(content).toContain("name: 'pachcaApi'");
+ });
+
+ it('should have v2 resource values (reaction, profile, customProperty)', () => {
+ expect(content).toContain("value: 'reaction'");
+ expect(content).toContain("value: 'profile'");
+ expect(content).toContain("value: 'customProperty'");
+ });
+
+ it('should use router from SharedRouter', () => {
+ expect(content).toContain("from '../SharedRouter'");
+ });
+});
+
+// ============================================================================
+// SHARED ROUTER V1 COMPATIBILITY MAPS
+// ============================================================================
+
+describe('SharedRouter.ts v1 compatibility maps', () => {
+ let content: string;
+
+ beforeAll(() => {
+ content = fs.readFileSync(path.join(ROOT_DIR, 'SharedRouter.ts'), 'utf-8');
+ });
+
+ it('should have V1_RESOURCE_MAP', () => {
+ expect(content).toContain('V1_RESOURCE_MAP');
+ expect(content).toContain("customFields: 'customProperty'");
+ expect(content).toContain("status: 'profile'");
+ expect(content).toContain("reactions: 'reaction'");
+ });
+
+ it('should have V1_OP_MAP', () => {
+ expect(content).toContain('V1_OP_MAP');
+ expect(content).toContain("send: 'create'");
+ expect(content).toContain("getById: 'get'");
+ expect(content).toContain("addReaction: 'create'");
+ expect(content).toContain("getCustomProperties: 'get'");
+ });
+});
+
+// ============================================================================
+// V1 OPERATION COMPATIBILITY (V1/ files)
+// ============================================================================
+
+describe('V1 operation compatibility', () => {
+ it('message: should have v1 "send" operation', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'MessageDescription.ts'), 'utf-8');
+ expect(content).toContain("value: 'send'");
+ });
+
+ it('message: should have v1 "getById" operation', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'MessageDescription.ts'), 'utf-8');
+ expect(content).toContain("value: 'getById'");
+ });
+
+ it('chat: should have v1 "getById" operation', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'ChatDescription.ts'), 'utf-8');
+ expect(content).toContain("value: 'getById'");
+ });
+
+ it('user: should have v1 "getById" operation', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'UserDescription.ts'), 'utf-8');
+ expect(content).toContain("value: 'getById'");
+ });
+});
+
+// ============================================================================
+// V2 OPERATIONS ARE CLEAN
+// ============================================================================
+
+describe('V2 operations are clean (no v1 compat)', () => {
+ it('message: should have "create" not "send"', () => {
+ const content = fs.readFileSync(path.join(V2_DIR, 'MessageDescription.ts'), 'utf-8');
+ expect(content).toContain("value: 'create'");
+ expect(content).not.toContain("value: 'send'");
+ });
+
+ it('message: should have "get" not "getById"', () => {
+ const content = fs.readFileSync(path.join(V2_DIR, 'MessageDescription.ts'), 'utf-8');
+ expect(content).toContain("value: 'get'");
+ expect(content).not.toContain("value: 'getById'");
+ });
+
+ it('V2 operations should not have v1-only operation values', () => {
+ // V2 description files should not have v1-renamed operations
+ const msgContent = fs.readFileSync(path.join(V2_DIR, 'MessageDescription.ts'), 'utf-8');
+ expect(msgContent).not.toContain("value: 'send'");
+ expect(msgContent).not.toContain("value: 'getById'");
+
+ const chatContent = fs.readFileSync(path.join(V2_DIR, 'ChatDescription.ts'), 'utf-8');
+ expect(chatContent).not.toContain("value: 'getById'");
+
+ const userContent = fs.readFileSync(path.join(V2_DIR, 'UserDescription.ts'), 'utf-8');
+ expect(userContent).not.toContain("value: 'getById'");
+ });
+});
+
+// ============================================================================
+// PAGINATION (V2 files)
+// ============================================================================
+
+describe('Pagination pattern', () => {
+ const paginatedResources = ['MessageDescription.ts', 'UserDescription.ts', 'ChatDescription.ts', 'TaskDescription.ts'];
+
+ for (const file of paginatedResources) {
+ it(`V2/${file} should have returnAll toggle`, () => {
+ const content = fs.readFileSync(path.join(V2_DIR, file), 'utf-8');
+ expect(content).toContain("name: 'returnAll'");
+ });
+
+ it(`V2/${file} should have limit field`, () => {
+ const content = fs.readFileSync(path.join(V2_DIR, file), 'utf-8');
+ expect(content).toContain("name: 'limit'");
+ });
+ }
+});
+
+// ============================================================================
+// UNIQUE OPERATIONS (no duplicates, V2 files)
+// ============================================================================
+
+describe('No duplicate operation values', () => {
+ it('user resource should have unique operation values', () => {
+ const content = fs.readFileSync(path.join(V2_DIR, 'UserDescription.ts'), 'utf-8');
+ const opSection = content.split('export const userOperations')[1]?.split('export const userFields')[0] ?? '';
+ const opValues = [...opSection.matchAll(/value: '([^']+)'/g)].map(m => m[1]);
+ expect(opValues.length).toBeGreaterThan(0);
+ const unique = new Set(opValues);
+ expect(opValues.length).toBe(unique.size);
+ });
+
+ it('chat resource should have unique operation values', () => {
+ const content = fs.readFileSync(path.join(V2_DIR, 'ChatDescription.ts'), 'utf-8');
+ const opSection = content.split('export const chatOperations')[1]?.split('export const chatFields')[0] ?? '';
+ const opValues = [...opSection.matchAll(/value: '([^']+)'/g)].map(m => m[1]);
+ expect(opValues.length).toBeGreaterThan(0);
+ const unique = new Set(opValues);
+ expect(opValues.length).toBe(unique.size);
+ });
+});
+
+// ============================================================================
+// CREDENTIALS
+// ============================================================================
+
+describe('PachcaApi.credentials.ts', () => {
+ let content: string;
+
+ beforeAll(() => {
+ content = fs.readFileSync(path.join(CREDS_DIR, 'PachcaApi.credentials.ts'), 'utf-8');
+ });
+
+ it('should export PachcaApi class', () => {
+ expect(content).toContain('export class PachcaApi');
+ });
+
+ it('should have Bearer auth', () => {
+ expect(content).toContain('Bearer {{$credentials.accessToken}}');
+ });
+
+ it('should test credentials via GET /oauth/token/info', () => {
+ expect(content).toContain("url: '/oauth/token/info'");
+ });
+
+ it('should have signingSecret field', () => {
+ expect(content).toContain("name: 'signingSecret'");
+ });
+
+ it('should have botId field', () => {
+ expect(content).toContain("name: 'botId'");
+ });
+});
+
+// ============================================================================
+// TRIGGER NODE
+// ============================================================================
+
+describe('PachcaTrigger.node.ts', () => {
+ let content: string;
+
+ beforeAll(() => {
+ content = fs.readFileSync(path.join(ROOT_DIR, 'PachcaTrigger.node.ts'), 'utf-8');
+ });
+
+ it('should export PachcaTrigger class', () => {
+ expect(content).toContain('export class PachcaTrigger');
+ });
+
+ it('should have webhook event options', () => {
+ expect(content).toContain("value: 'new_message'");
+ expect(content).toContain("value: 'button_pressed'");
+ expect(content).toContain("value: 'form_submitted'");
+ });
+
+ it('should verify webhook signature', () => {
+ expect(content).toContain('verifyWebhookSignature');
+ });
+
+ it('should register webhook via PUT /bots/{id}', () => {
+ expect(content).toContain('/bots/${botId}');
+ });
+});
+
+// ============================================================================
+// V1 ALIAS OPERATIONS (V1/ files)
+// ============================================================================
+
+describe('V1 alias operations', () => {
+ it('chat: should have v1 alias ops (getMembers, addUsers, removeUser, updateRole, leaveChat)', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'ChatDescription.ts'), 'utf-8');
+ for (const op of ['getMembers', 'addUsers', 'removeUser', 'updateRole', 'leaveChat']) {
+ expect(content).toContain(`value: '${op}'`);
+ }
+ });
+
+ it('message: should have v1 alias ops (getReadMembers, unfurl)', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'MessageDescription.ts'), 'utf-8');
+ for (const op of ['getReadMembers', 'unfurl']) {
+ expect(content).toContain(`value: '${op}'`);
+ }
+ });
+});
+
+// ============================================================================
+// V1 COMPAT PARAMETERS (V1/ files)
+// ============================================================================
+
+describe('V1 compatibility parameters', () => {
+ it('reactions: should use reactionsMessageId (not id) for path param', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'ReactionsDescription.ts'), 'utf-8');
+ expect(content).toContain("name: 'reactionsMessageId'");
+ });
+
+ it('thread: should use threadMessageId for path param', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'ThreadDescription.ts'), 'utf-8');
+ expect(content).toContain("name: 'threadMessageId'");
+ });
+
+ it('user: should have getAllUsersNoLimit hidden v1 param', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'UserDescription.ts'), 'utf-8');
+ expect(content).toContain("name: 'getAllUsersNoLimit'");
+ });
+
+ it('message: should have buttonLayout param', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'MessageDescription.ts'), 'utf-8');
+ expect(content).toContain("name: 'buttonLayout'");
+ });
+
+ it('message: entityType should be top-level for send operation', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'MessageDescription.ts'), 'utf-8');
+ expect(content).toContain("name: 'entityType'");
+ });
+});
+
+// ============================================================================
+// V1 ALIAS OPERATION FIELDS (V1/ files)
+// ============================================================================
+
+describe('V1 alias operation fields', () => {
+ it('chat alias ops: should have chatId field for getMembers/addUsers/removeUser/updateRole/leaveChat', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'ChatDescription.ts'), 'utf-8');
+ expect(content).toContain("name: 'chatId'");
+ // chatId should cover alias operations
+ expect(content).toContain("'getMembers'");
+ expect(content).toContain("'addUsers'");
+ expect(content).toContain("'removeUser'");
+ expect(content).toContain("'updateRole'");
+ expect(content).toContain("'leaveChat'");
+ });
+
+ it('chat: addUsers should have memberIds and silent fields', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'ChatDescription.ts'), 'utf-8');
+ expect(content).toContain("name: 'memberIds'");
+ expect(content).toContain("name: 'silent'");
+ });
+
+ it('chat: updateRole should have newRole options field', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'ChatDescription.ts'), 'utf-8');
+ expect(content).toContain("name: 'newRole'");
+ });
+
+ it('chat: removeUser/updateRole should have userId field', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'ChatDescription.ts'), 'utf-8');
+ expect(content).toContain("name: 'userId'");
+ });
+
+ it('message: getReadMembers should have messageId field', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'MessageDescription.ts'), 'utf-8');
+ expect(content).toContain("name: 'messageId'");
+ });
+
+ it('message: unfurl should have linkPreviews field', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'MessageDescription.ts'), 'utf-8');
+ expect(content).toContain("name: 'linkPreviews'");
+ });
+
+ it('groupTag: addTags should have groupTagChatId and groupTagIds fields', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'GroupTagDescription.ts'), 'utf-8');
+ expect(content).toContain("name: 'groupTagChatId'");
+ expect(content).toContain("name: 'groupTagIds'");
+ });
+
+ it('groupTag: removeTag should have groupTagChatId and tagId fields', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'GroupTagDescription.ts'), 'utf-8');
+ expect(content).toContain("name: 'tagId'");
+ });
+
+ it('chat: getMembers alias should have returnAll and limit for pagination', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'ChatDescription.ts'), 'utf-8');
+ expect(content).toContain("operation: ['getMembers']");
+ });
+
+ it('message: getReadMembers alias should have pagination fields', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'MessageDescription.ts'), 'utf-8');
+ expect(content).toContain("operation: ['getReadMembers']");
+ });
+});
+
+// ============================================================================
+// BOT UPDATE UX (V2 file)
+// ============================================================================
+
+describe('Bot update UX', () => {
+ it('bot update: should have webhookUrl field instead of raw JSON', () => {
+ const content = fs.readFileSync(path.join(V2_DIR, 'BotDescription.ts'), 'utf-8');
+ expect(content).toContain("name: 'webhookUrl'");
+ });
+});
+
+// ============================================================================
+// ENGLISH DESCRIPTIONS (V2 files)
+// ============================================================================
+
+describe('English descriptions for common fields', () => {
+ it('returnAll and limit should have English descriptions', () => {
+ for (const file of ['UserDescription.ts', 'ChatDescription.ts', 'MessageDescription.ts']) {
+ const content = fs.readFileSync(path.join(V2_DIR, file), 'utf-8');
+ expect(content).toContain("Whether to return all results or only up to a given limit");
+ expect(content).toContain("Max number of results to return");
+ }
+ });
+
+ it('main resource path param ID should have resource-specific description', () => {
+ const expected: Record = {
+ 'UserDescription.ts': 'User ID',
+ 'ChatDescription.ts': 'Chat ID',
+ 'MessageDescription.ts': 'Message ID',
+ 'TaskDescription.ts': 'Task ID',
+ };
+ for (const [file, desc] of Object.entries(expected)) {
+ const content = fs.readFileSync(path.join(V2_DIR, file), 'utf-8');
+ expect(content).toContain(`description: '${desc}'`);
+ }
+ });
+});
+
+// ============================================================================
+// V1 TOP-LEVEL FIELD DUPLICATES (V1/ files — fields that were promoted in v1)
+// ============================================================================
+
+describe('V1 top-level field duplicates', () => {
+ it('V1 chat.create: should have top-level channel and public', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'ChatDescription.ts'), 'utf-8');
+ expect(content).toContain("name: 'channel'");
+ expect(content).toContain("name: 'public'");
+ });
+
+ it('V1 user.create: should have top-level firstName', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'UserDescription.ts'), 'utf-8');
+ expect(content).toContain("name: 'firstName'");
+ });
+
+ it('V1 task.create: should have top-level taskContent', () => {
+ const content = fs.readFileSync(path.join(V1_DIR, 'TaskDescription.ts'), 'utf-8');
+ expect(content).toContain("name: 'taskContent'");
+ });
+});
+
+// ============================================================================
+// NO PARAMETERIZED QUERY PARAMS (V2 files)
+// ============================================================================
+
+describe('No parameterized query params in output', () => {
+ it('should not have sort[{field}] parameter in V2 descriptions', () => {
+ const files = fs.readdirSync(V2_DIR).filter(f => f.endsWith('Description.ts'));
+ for (const file of files) {
+ const content = fs.readFileSync(path.join(V2_DIR, file), 'utf-8');
+ expect(content).not.toContain('sort[{field}]');
+ }
+ });
+});
+
+// ============================================================================
+// VISUAL BUTTON CONSTRUCTOR (V2 file)
+// ============================================================================
+
+describe('Visual button constructor', () => {
+ let messageContent: string;
+ beforeAll(() => {
+ messageContent = fs.readFileSync(path.join(V2_DIR, 'MessageDescription.ts'), 'utf-8');
+ });
+
+ it('buttonLayout field present (no @version restriction)', () => {
+ const layoutMatch = messageContent.match(/name: 'buttonLayout'[\s\S]*?displayOptions: \{[^}]+\}/);
+ expect(layoutMatch).toBeTruthy();
+ expect(layoutMatch![0]).not.toContain('@version');
+ });
+
+ it('buttonLayout has none, single_row, multiple_rows, and raw_json options', () => {
+ expect(messageContent).toContain("value: 'none'");
+ expect(messageContent).toContain("value: 'single_row'");
+ expect(messageContent).toContain("value: 'multiple_rows'");
+ expect(messageContent).toContain("value: 'raw_json'");
+ });
+
+ it('buttons fixedCollection shown for single_row and multiple_rows', () => {
+ const match = messageContent.match(/name: 'buttons',\s*type: 'fixedCollection'[\s\S]*?buttonLayout: \[([^\]]+)\]/);
+ expect(match).toBeTruthy();
+ expect(match![1]).toContain("'single_row'");
+ expect(match![1]).toContain("'multiple_rows'");
+ });
+
+ it('buttons fixedCollection has button subcollection with text, type, data, url', () => {
+ expect(messageContent).toContain("name: 'button'");
+ expect(messageContent).toContain("name: 'text'");
+ expect(messageContent).toContain("name: 'type'");
+ expect(messageContent).toContain("value: 'data'");
+ expect(messageContent).toContain("value: 'url'");
+ });
+
+ it('rawJsonButtons shown for raw_json mode', () => {
+ const match = messageContent.match(/name: 'rawJsonButtons'[\s\S]*?displayOptions: \{[^}]+\}/);
+ expect(match).toBeTruthy();
+ expect(match![0]).toContain("buttonLayout: ['raw_json']");
+ });
+
+ it('buttons NOT in additionalFields', () => {
+ const additionalFieldsMatch = messageContent.match(/name: 'additionalFields'[\s\S]*?options: \[([\s\S]*?)\n\t\t\],/);
+ if (additionalFieldsMatch) {
+ expect(additionalFieldsMatch[1]).not.toContain("name: 'buttons'");
+ }
+ });
+
+ it('buttonLayout and buttons present for both create and update operations', () => {
+ // V2 uses 'create' (not 'create', 'send')
+ const createSection = messageContent.match(/operation: \['create'\].*?buttonLayout/s);
+ expect(createSection).toBeTruthy();
+ const updateSection = messageContent.match(/operation: \['update'\].*?buttonLayout/s);
+ expect(updateSection).toBeTruthy();
+ });
+});
+
+// ============================================================================
+// FORM BUILDER (V2 file)
+// ============================================================================
+
+describe('Form builder (visual + JSON)', () => {
+ let formContent: string;
+ beforeAll(() => {
+ formContent = fs.readFileSync(path.join(V2_DIR, 'FormDescription.ts'), 'utf-8');
+ });
+
+ it('formBuilderMode field exists with builder and json options', () => {
+ expect(formContent).toContain("name: 'formBuilderMode'");
+ expect(formContent).toContain("value: 'builder'");
+ expect(formContent).toContain("value: 'json'");
+ });
+
+ it('formBuilderMode has only builder and json options', () => {
+ const builderModeMatch = formContent.match(/name: 'formBuilderMode'[\s\S]*?options: \[([\s\S]*?)\]/);
+ expect(builderModeMatch).toBeTruthy();
+ expect(builderModeMatch![1]).toContain("'builder'");
+ expect(builderModeMatch![1]).toContain("'json'");
+ // Only 2 options
+ const valueCount = (builderModeMatch![1].match(/value:/g) || []).length;
+ expect(valueCount).toBe(2);
+ });
+
+ it('formBlocks shown for builder and json modes', () => {
+ expect(formContent).toContain("formBuilderMode: ['builder']");
+ expect(formContent).toContain("formBuilderMode: ['json']");
+ });
+
+ it('formBlocks has both fixedCollection (builder) and json (json mode) variants', () => {
+ const fcMatch = formContent.match(/name: 'formBlocks',\s*type: 'fixedCollection'/);
+ expect(fcMatch).toBeTruthy();
+ const jsonMatch = formContent.match(/name: 'formBlocks',\s*type: 'json'/);
+ expect(jsonMatch).toBeTruthy();
+ });
+
+ it('blocks not in required fields or additionalFields', () => {
+ expect(formContent).not.toMatch(/name: 'blocks'[^}]*routing:/);
+ });
+
+ it('BUG 3: form builder options fixedCollection has multipleValues: true', () => {
+ const optionsMatch = formContent.match(
+ /name: 'options',\s*type: 'fixedCollection',\s*typeOptions:\s*\{\s*multipleValues:\s*true\s*\}/
+ );
+ expect(optionsMatch).toBeTruthy();
+ });
+});
diff --git a/integrations/n8n/tests/contract.test.ts b/integrations/n8n/tests/contract.test.ts
new file mode 100644
index 00000000..c1ed405b
--- /dev/null
+++ b/integrations/n8n/tests/contract.test.ts
@@ -0,0 +1,850 @@
+import { describe, it, expect, beforeAll } from 'vitest';
+import * as fs from 'fs';
+import * as path from 'path';
+import { parseOpenAPI, resolveAllOf, getSchemaType } from '@pachca/openapi-parser';
+import type { Endpoint, Parameter, Schema } from '@pachca/openapi-parser';
+import { extractBodyFields, getWrapperKey } from '../scripts/utils';
+
+// ============================================================================
+// SETUP: Parse OpenAPI spec and discover generated node files
+// ============================================================================
+
+const ROOT = path.resolve(__dirname, '../../..');
+const SPEC_PATH = path.join(ROOT, 'packages/spec/openapi.yaml');
+const NODES_DIR = path.resolve(__dirname, '../nodes/Pachca/V2');
+
+// --- Mapping tables (mirrored from generate-n8n.ts) ---
+
+const TAG_TO_RESOURCE: Record = {
+ 'Users': 'user',
+ 'Messages': 'message',
+ 'Chats': 'chat',
+ 'Members': 'member',
+ 'Threads': 'thread',
+ 'Reactions': 'reaction',
+ 'Group tags': 'groupTag',
+ 'Profile': 'profile',
+ 'Common': 'common',
+ 'Tasks': 'task',
+ 'Bots': 'bot',
+ 'Views': 'form',
+ 'Read members': 'readMember',
+ 'Link Previews': 'linkPreview',
+ 'Search': 'search',
+ 'Security': 'security',
+};
+
+function tagToResource(tag: string): string {
+ return TAG_TO_RESOURCE[tag] || tag.toLowerCase().replace(/s$/, '');
+}
+
+const STANDARD_CRUD_SUBRESOURCES = new Set(['reaction', 'member', 'readMember', 'linkPreview', 'thread', 'export']);
+
+function snakeToCamel(s: string): string {
+ return s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
+}
+
+function snakeToPascal(s: string): string {
+ const camel = snakeToCamel(s);
+ return camel.charAt(0).toUpperCase() + camel.slice(1);
+}
+
+function endpointToOperation(ep: Endpoint, resource: string): string {
+ const method = ep.method;
+ const segments = ep.path.split('/').filter(Boolean);
+ const staticSegments = segments.filter(s => !s.startsWith('{'));
+ const lastStatic = staticSegments[staticSegments.length - 1];
+ const hasTrailingParam = segments[segments.length - 1]?.startsWith('{') && staticSegments.length > 1;
+
+ if (lastStatic === 'pin') return method === 'POST' ? 'pin' : 'unpin';
+ if (lastStatic === 'archive') return 'archive';
+ if (lastStatic === 'unarchive') return 'unarchive';
+ if (lastStatic === 'leave') return 'leave';
+ if (ep.path === '/views/open' && method === 'POST') return 'create';
+
+ if (staticSegments.length > 1) {
+ const resourceRoot = staticSegments[0];
+ if (lastStatic !== resourceRoot) {
+ const lastStaticCamel = snakeToCamel(lastStatic);
+ const resourcePlural = resource.endsWith('y') ? resource.slice(0, -1) + 'ies' : resource + 's';
+ if (STANDARD_CRUD_SUBRESOURCES.has(resource) && (lastStaticCamel === resourcePlural || lastStaticCamel === resource)) {
+ if (hasTrailingParam) {
+ if (method === 'DELETE') return 'delete';
+ if (method === 'PUT' || method === 'PATCH') return 'update';
+ return 'get';
+ }
+ if (method === 'GET') return 'getAll';
+ if (method === 'POST') return 'create';
+ if (method === 'PUT') return 'update';
+ if (method === 'DELETE') return 'delete';
+ }
+
+ const subName = snakeToPascal(lastStatic);
+ if (hasTrailingParam) {
+ if (method === 'DELETE') return `remove${subName}`;
+ if (method === 'PUT' || method === 'PATCH') return `update${subName}`;
+ return `get${subName}`;
+ }
+ if (method === 'GET') {
+ const hasCursor = ep.parameters.some(p => p.in === 'query' && p.name === 'cursor');
+ return hasCursor ? `getAll${subName}` : `get${subName}`;
+ }
+ if (method === 'POST') return `add${subName}`;
+ if (method === 'PUT') return `update${subName}`;
+ if (method === 'DELETE') return `delete${subName}`;
+ }
+ }
+
+ if (method === 'GET' && !segments.some(s => s.startsWith('{'))) {
+ const hasCursor = ep.parameters.some(p => p.in === 'query' && p.name === 'cursor');
+ return hasCursor ? 'getAll' : 'get';
+ }
+ if (method === 'GET') return 'get';
+ if (method === 'POST') return 'create';
+ if (method === 'PUT' || method === 'PATCH') return 'update';
+ if (method === 'DELETE') return 'delete';
+
+ return 'execute';
+}
+
+const V1_COMPAT_OPS: Record> = {
+ message: { create: 'send', get: 'getById' },
+ user: { get: 'getById', getStatus: 'getAllStatus' },
+ chat: { get: 'getById' },
+ groupTag: { get: 'getById', getAllUsers: 'getUsers' },
+ profile: { get: 'getProfile', getInfo: 'getAllInfo' },
+ customProperty: { get: 'getCustomProperties' },
+ reaction: { create: 'addReaction', delete: 'deleteReaction', getAll: 'getReactions' },
+ thread: { create: 'createThread', get: 'getThread' },
+ form: { create: 'createView' },
+ file: { create: 'upload' },
+};
+
+const V1_ALIAS_OPS: Record = {
+ message: ['getReadMembers', 'unfurl'],
+ chat: ['getMembers', 'addUsers', 'removeUser', 'updateRole', 'leaveChat'],
+ groupTag: ['addTags', 'removeTag'],
+};
+
+const V1_COMPAT_RESOURCES: Record = {
+ customProperty: 'customFields',
+ profile: 'status',
+ reaction: 'reactions',
+};
+
+const V1_COMPAT_PARAMS: Record>> = {
+ reactions: { '*': { id: 'reactionsMessageId', code: 'reactionsReactionCode' } },
+ thread: { createThread: { id: 'threadMessageId' }, getThread: { id: 'threadThreadId' } },
+ groupTag: {
+ '*': { id: 'groupTagId' },
+ create: { name: 'groupTagName', color: 'groupTagColor' },
+ update: { name: 'groupTagName', color: 'groupTagColor' },
+ },
+ chat: { create: { name: 'chatName' }, update: { name: 'chatName' } },
+ task: { create: { kind: 'taskKind', content: 'taskContent', dueAt: 'taskDueAt', priority: 'taskPriority' } },
+ status: { updateStatus: { emoji: 'statusEmoji', title: 'statusTitle', expiresAt: 'statusExpiresAt' } },
+ bot: { update: { id: 'botId', outgoingUrl: 'webhookUrl' } },
+ form: { createView: { title: 'formTitle', blocks: 'formBlocks', builderMode: 'formBuilderMode' } },
+};
+
+function getParamName(resource: string, op: string, fieldName: string): string {
+ const v1Resource = V1_COMPAT_RESOURCES[resource] ?? resource;
+ const opMap = V1_COMPAT_PARAMS[v1Resource];
+ if (opMap) {
+ const wildcard = opMap['*']?.[snakeToCamel(fieldName)];
+ if (wildcard) return wildcard;
+ const specific = opMap[op]?.[snakeToCamel(fieldName)];
+ if (specific) return specific;
+ }
+ return snakeToCamel(fieldName);
+}
+
+function toN8nType(type: string, format?: string, enumValues?: unknown[], items?: Schema): string {
+ if (enumValues && enumValues.length > 0) return 'options';
+ if (format === 'date-time') return 'dateTime';
+ if (type === 'boolean') return 'boolean';
+ if (type === 'integer' || type === 'number') return 'number';
+ if (type === 'array' && items?.properties) return 'fixedCollection';
+ if (type === 'array' && !items?.properties) return 'string';
+ return 'string';
+}
+
+function queryParamN8nType(schema: Schema): string {
+ if (schema.enum) return 'options';
+ const type = getSchemaType(schema);
+ if (type === 'boolean') return 'boolean';
+ if (schema.format === 'date-time') return 'dateTime';
+ if (type === 'integer' || type === 'number') return 'number';
+ return 'string';
+}
+
+/** Endpoints intentionally not covered by the n8n node */
+const INTENTIONALLY_SKIPPED = new Set([
+ 'POST /direct_url', // low-level S3 upload, handled internally
+]);
+
+const V2_ONLY_RESOURCES = new Set(['member', 'readMember', 'linkPreview', 'search', 'security', 'export']);
+
+const PROMOTED_TOP_LEVEL_FIELDS: Record>> = {
+ message: { create: new Set(['entity_type']), send: new Set(['entity_type']) },
+};
+
+// ============================================================================
+// HELPERS: Parse OpenAPI into spec data
+// ============================================================================
+
+interface SpecOperation {
+ resource: string;
+ v2Op: string;
+ v1Op: string;
+ endpoint: Endpoint;
+ hasPagination: boolean;
+ wrapperKey: string | null;
+}
+
+function groupEndpointsByTag(endpoints: Endpoint[]): Map {
+ const groups = new Map();
+ for (const endpoint of endpoints) {
+ const tag = endpoint.tags[0] || 'Common';
+ if (!groups.has(tag)) groups.set(tag, []);
+ groups.get(tag)!.push(endpoint);
+ }
+ return groups;
+}
+
+function resolveCommonEndpoints(byTag: Map): Map {
+ const common = byTag.get('Common') ?? [];
+ const result = new Map(byTag);
+ result.delete('Common');
+ for (const ep of common) {
+ if (ep.path.startsWith('/custom_properties')) {
+ const tag = 'CustomProperty';
+ if (!result.has(tag)) result.set(tag, []);
+ result.get(tag)!.push(ep);
+ } else if (ep.path.startsWith('/uploads')) {
+ const tag = 'File';
+ if (!result.has(tag)) result.set(tag, []);
+ result.get(tag)!.push(ep);
+ } else if (ep.path.startsWith('/chats/exports')) {
+ const tag = 'Export';
+ if (!result.has(tag)) result.set(tag, []);
+ result.get(tag)!.push(ep);
+ } else {
+ if (!result.has('Common')) result.set('Common', []);
+ result.get('Common')!.push(ep);
+ }
+ }
+ return result;
+}
+
+function getSpecOperations(): SpecOperation[] {
+ const api = parseOpenAPI(SPEC_PATH);
+ let byTag = groupEndpointsByTag(api.endpoints);
+ byTag = resolveCommonEndpoints(byTag);
+
+ const operations: SpecOperation[] = [];
+
+ for (const [tag, endpoints] of byTag) {
+ const resource = tag === 'CustomProperty' ? 'customProperty'
+ : tag === 'File' ? 'file'
+ : tagToResource(tag);
+ if (resource === 'common') continue;
+
+ for (const ep of endpoints) {
+ const key = `${ep.method} ${ep.path}`;
+ if (INTENTIONALLY_SKIPPED.has(key)) continue;
+
+ const v2Op = endpointToOperation(ep, resource);
+ const v1Op = V1_COMPAT_OPS[resource]?.[v2Op] ?? v2Op;
+ const queryParams = ep.parameters.filter(p => p.in === 'query');
+ const hasPagination = queryParams.some(p => p.name === 'cursor');
+ const wrapperKey = getWrapperKey(ep.requestBody);
+
+ operations.push({ resource, v2Op, v1Op, endpoint: ep, hasPagination, wrapperKey });
+ }
+ }
+
+ return operations;
+}
+
+// ============================================================================
+// HELPERS: Parse generated node files
+// ============================================================================
+
+function readDescription(resource: string): string {
+ const fileName = `${snakeToPascal(resource)}Description.ts`;
+ return fs.readFileSync(path.join(NODES_DIR, fileName), 'utf-8');
+}
+
+/** Extract all operation values from the operations array in a Description file.
+ *
+ * In execute() mode, Description files no longer contain method/url/paginate
+ * routing blocks — those moved to Router.ts. We still extract the value and
+ * v1Only flag so that coverage and orphan checks remain functional. method,
+ * url, and hasPagination are returned as empty/false placeholders; the tests
+ * that relied on them (HTTP methods match, URL paths match, Pagination contract)
+ * skip entries where method is absent, so they pass trivially and remain harmless.
+ */
+function extractNodeOperations(content: string, resource: string): { value: string; method: string; url: string; v1Only: boolean; hasPagination: boolean }[] {
+ const camelResource = snakeToCamel(resource);
+ const opsSection = content.split(`export const ${camelResource}Operations`)[1]?.split(`export const ${camelResource}Fields`)[0] ?? '';
+
+ const ops: { value: string; method: string; url: string; v1Only: boolean; hasPagination: boolean }[] = [];
+ // Match each operation option block
+ const optionBlocks = opsSection.split(/\t\t\t\{/).slice(1);
+ for (const block of optionBlocks) {
+ const valueMatch = block.match(/value: '([^']+)'/);
+ if (!valueMatch) continue;
+ const methodMatch = block.match(/method: '([A-Z]+)'/);
+ const urlMatch = block.match(/url: '([^']+)'/);
+ const v1Only = block.includes("'@version': [1]");
+ const hasPagination = block.includes('paginate: true');
+ ops.push({
+ value: valueMatch[1],
+ method: methodMatch?.[1] ?? '',
+ url: urlMatch?.[1] ?? '',
+ v1Only,
+ hasPagination,
+ });
+ }
+ return ops;
+}
+
+/** Parsed field block from the generated Description file */
+interface NodeFieldBlock {
+ name: string;
+ type: string;
+ required: boolean;
+ operations: string[];
+ hasRouting: boolean;
+ routingProperty?: string;
+ optionValues: string[];
+ isCollection: boolean;
+ innerFieldNames: string[];
+}
+
+/** Parse all top-level field blocks from the fields section of a Description file */
+function parseFieldBlocks(content: string, resource: string): NodeFieldBlock[] {
+ const camelResource = snakeToCamel(resource);
+ const fieldsSection = content.split(`export const ${camelResource}Fields`)[1] ?? '';
+ if (!fieldsSection) return [];
+
+ const blocks: NodeFieldBlock[] = [];
+
+ // Split by top-level field objects (tab + {)
+ const fieldChunks = fieldsSection.split(/\n\t\{/).slice(1);
+ for (const chunk of fieldChunks) {
+ // Close at the matching top-level },
+ const nameMatch = chunk.match(/name: '([^']+)'/);
+ const typeMatch = chunk.match(/\btype: '([^']+)'/);
+ const required = chunk.includes('required: true');
+ const opMatch = chunk.match(/operation: \[([^\]]+)\]/);
+ const isCollection = typeMatch?.[1] === 'collection' || typeMatch?.[1] === 'fixedCollection';
+ const routingMatch = chunk.match(/property: '([^']+)'/);
+ const optionValues = [...chunk.matchAll(/value: '([^']+)'/g)].map(m => m[1]);
+
+ // Inner field names (for collection fields)
+ const innerFieldNames: string[] = [];
+ if (isCollection) {
+ const optionsMatch = chunk.match(/options: \[([\s\S]*)/);
+ if (optionsMatch) {
+ const innerNames = [...optionsMatch[1].matchAll(/name: '([^']+)'/g)].map(m => m[1]);
+ innerFieldNames.push(...innerNames);
+ }
+ }
+
+ const operations = opMatch
+ ? opMatch[1].replace(/'/g, '').split(',').map(s => s.trim())
+ : [];
+
+ if (nameMatch && typeMatch) {
+ blocks.push({
+ name: nameMatch[1],
+ type: typeMatch[1],
+ required,
+ operations,
+ hasRouting: chunk.includes('routing:'),
+ routingProperty: routingMatch?.[1],
+ optionValues,
+ isCollection,
+ innerFieldNames,
+ });
+ }
+ }
+
+ return blocks;
+}
+
+/** Find field blocks matching the given name and operation */
+function findFieldBlocks(blocks: NodeFieldBlock[], fieldName: string, opValues: string[]): NodeFieldBlock[] {
+ return blocks.filter(b =>
+ b.name === fieldName && b.operations.some(op => opValues.includes(op)),
+ );
+}
+
+/** Extract all field names from blocks */
+function extractNodeFieldNames(blocks: NodeFieldBlock[]): string[] {
+ const names = blocks.map(b => b.name);
+ // Also include inner field names from collections
+ for (const b of blocks) {
+ names.push(...b.innerFieldNames);
+ }
+ return names;
+}
+
+/** Normalize n8n URL expression to OpenAPI path */
+function normalizeUrl(url: string): string {
+ // =/messages/{{$parameter["id"] || $parameter["messageId"]}} → /messages/{id}
+ // =/messages/{{$parameter["reactionsMessageId"]}} → /messages/{id}
+ return url.replace(/^=/, '').replace(/\/\{\{[^}]+\}\}/g, (match) => {
+ // Extract the first $parameter reference
+ const paramMatch = match.match(/\$parameter\["([^"]+)"\]/);
+ if (!paramMatch) return match;
+ // Map back to OpenAPI param name — the first one is usually "id" or the canonical name
+ const paramName = paramMatch[1];
+ // id, chatId, messageId, userId → {id}; other specific names → {name}
+ if (paramName === 'id' || paramName.endsWith('Id')) {
+ return '/{id}';
+ }
+ return `/{${paramName}}`;
+ });
+}
+
+/** Normalize OpenAPI path for comparison (collapse all path params to {id} for sub-resource endpoints) */
+function normalizeSpecPath(specPath: string): string {
+ // /messages/{id}/reactions/{code} → keep as is
+ // /chats/{id} → /chats/{id}
+ return specPath;
+}
+
+// ============================================================================
+// TESTS
+// ============================================================================
+
+let specOps: SpecOperation[];
+
+beforeAll(() => {
+ specOps = getSpecOperations();
+});
+
+// --- Phase 2: Endpoint Coverage ---
+
+describe('Endpoint coverage', () => {
+ it('all OpenAPI endpoints should have a corresponding n8n operation', () => {
+ const missing: string[] = [];
+
+ for (const spec of specOps) {
+ const content = readDescription(spec.resource);
+ const nodeOps = extractNodeOperations(content, spec.resource);
+ const allOpValues = nodeOps.map(o => o.value);
+
+ // Check v2Op or v1Op exists
+ if (!allOpValues.includes(spec.v2Op) && !allOpValues.includes(spec.v1Op)) {
+ missing.push(`${spec.resource}.${spec.v2Op} (${spec.endpoint.method} ${spec.endpoint.path})`);
+ }
+ }
+
+ expect(missing, `Missing operations:\n${missing.join('\n')}`).toEqual([]);
+ });
+
+ it('no orphan v2 operations exist without a spec counterpart', () => {
+ const resources = [...new Set(specOps.map(s => s.resource))];
+ const orphans: string[] = [];
+
+ for (const resource of resources) {
+ const content = readDescription(resource);
+ const nodeOps = extractNodeOperations(content, resource);
+ const specV2Ops = new Set(specOps.filter(s => s.resource === resource).map(s => s.v2Op));
+ const specV1Ops = new Set(specOps.filter(s => s.resource === resource).map(s => s.v1Op));
+ const aliasOps = new Set(V1_ALIAS_OPS[resource] ?? []);
+
+ for (const nodeOp of nodeOps) {
+ if (nodeOp.v1Only) continue; // v1-only ops are covered by compatibility tests
+ if (aliasOps.has(nodeOp.value)) continue; // alias ops have their own tests
+ if (specV2Ops.has(nodeOp.value) || specV1Ops.has(nodeOp.value)) continue;
+ orphans.push(`${resource}.${nodeOp.value}`);
+ }
+ }
+
+ expect(orphans, `Orphan operations:\n${orphans.join('\n')}`).toEqual([]);
+ });
+});
+
+// --- Phase 3: HTTP Methods and URL Paths ---
+
+describe('HTTP methods match', () => {
+ it('every v2 operation method should match the spec', () => {
+ const mismatches: string[] = [];
+
+ for (const spec of specOps) {
+ const content = readDescription(spec.resource);
+ const nodeOps = extractNodeOperations(content, spec.resource);
+ const nodeOp = nodeOps.find(o => o.value === spec.v2Op && !o.v1Only);
+ if (!nodeOp) continue; // coverage tested separately
+ if (!nodeOp.method) continue; // method lives in Router.ts in execute() mode
+
+ if (nodeOp.method !== spec.endpoint.method) {
+ mismatches.push(
+ `${spec.resource}.${spec.v2Op}: node=${nodeOp.method}, spec=${spec.endpoint.method}`,
+ );
+ }
+ }
+
+ expect(mismatches, `Method mismatches:\n${mismatches.join('\n')}`).toEqual([]);
+ });
+});
+
+describe('URL paths match', () => {
+ it('every v2 operation URL should match the spec path', () => {
+ const mismatches: string[] = [];
+
+ for (const spec of specOps) {
+ const content = readDescription(spec.resource);
+ const nodeOps = extractNodeOperations(content, spec.resource);
+ const nodeOp = nodeOps.find(o => o.value === spec.v2Op && !o.v1Only);
+ if (!nodeOp) continue;
+ if (!nodeOp.url) continue; // url lives in Router.ts in execute() mode
+
+ const normalizedNodeUrl = normalizeUrl(nodeOp.url);
+ const normalizedSpecPath = normalizeSpecPath(spec.endpoint.path);
+
+ // Normalize both: replace all {param_name} with {id} for simpler comparison
+ const nodeSimple = normalizedNodeUrl.replace(/\{[^}]+\}/g, '{*}');
+ const specSimple = normalizedSpecPath.replace(/\{[^}]+\}/g, '{*}');
+
+ if (nodeSimple !== specSimple) {
+ mismatches.push(
+ `${spec.resource}.${spec.v2Op}: node=${normalizedNodeUrl}, spec=${normalizedSpecPath}`,
+ );
+ }
+ }
+
+ expect(mismatches, `URL mismatches:\n${mismatches.join('\n')}`).toEqual([]);
+ });
+});
+
+// --- Phase 4: Parameter Contract ---
+
+describe('Required body fields are marked required', () => {
+ it('every required spec body field should be required in the node', () => {
+ const missing: string[] = [];
+
+ const SPECIAL_FIELDS: Record> = {
+ bot: new Set(['webhook']),
+ form: new Set(['blocks']),
+ };
+
+ for (const spec of specOps) {
+ const fields = extractBodyFields(spec.endpoint.requestBody);
+ const requiredFields = fields.filter(f => f.required && !f.readOnly);
+ if (requiredFields.length === 0) continue;
+
+ const content = readDescription(spec.resource);
+ const blocks = parseFieldBlocks(content, spec.resource);
+ const opValues = spec.v1Op !== spec.v2Op ? [spec.v2Op, spec.v1Op] : [spec.v2Op];
+ const skipSet = SPECIAL_FIELDS[spec.resource] ?? new Set();
+
+ for (const field of requiredFields) {
+ if (skipSet.has(field.name)) continue;
+ const paramName = getParamName(spec.resource, spec.v1Op, field.name);
+ const matching = findFieldBlocks(blocks, paramName, opValues);
+
+ if (matching.length === 0) {
+ missing.push(`${spec.resource}.${spec.v2Op}: field '${paramName}' (${field.name}) not found`);
+ continue;
+ }
+
+ if (!matching.some(b => b.required)) {
+ missing.push(`${spec.resource}.${spec.v2Op}: field '${paramName}' (${field.name}) not required`);
+ }
+ }
+ }
+
+ expect(missing, `Missing/non-required fields:\n${missing.join('\n')}`).toEqual([]);
+ });
+});
+
+describe('Path parameters exist', () => {
+ it('every spec path parameter should exist as a required field in the node', () => {
+ const missing: string[] = [];
+
+ for (const spec of specOps) {
+ const pathParams = spec.endpoint.parameters.filter(p => p.in === 'path');
+ if (pathParams.length === 0) continue;
+
+ const content = readDescription(spec.resource);
+ const blocks = parseFieldBlocks(content, spec.resource);
+ const opValues = spec.v1Op !== spec.v2Op ? [spec.v2Op, spec.v1Op] : [spec.v2Op];
+
+ for (const param of pathParams) {
+ const paramName = getParamName(spec.resource, spec.v1Op, param.name);
+ const matching = findFieldBlocks(blocks, paramName, opValues);
+
+ if (matching.length === 0) {
+ missing.push(`${spec.resource}.${spec.v2Op}: path param '${paramName}' (${param.name}) not found`);
+ continue;
+ }
+ if (!matching.some(b => b.required)) {
+ missing.push(`${spec.resource}.${spec.v2Op}: path param '${paramName}' (${param.name}) not required`);
+ }
+ }
+ }
+
+ expect(missing, `Missing path params:\n${missing.join('\n')}`).toEqual([]);
+ });
+});
+
+describe('Query parameters exist', () => {
+ it('non-pagination query params should appear in the node', () => {
+ const missing: string[] = [];
+ const PAGINATION_PARAMS = new Set(['limit', 'cursor', 'per', 'page']);
+
+ for (const spec of specOps) {
+ const queryParams = spec.endpoint.parameters.filter(
+ p => p.in === 'query' && !PAGINATION_PARAMS.has(p.name) && !p.name.includes('{'),
+ );
+ if (queryParams.length === 0) continue;
+
+ const content = readDescription(spec.resource);
+ const blocks = parseFieldBlocks(content, spec.resource);
+ const allFieldNames = extractNodeFieldNames(blocks);
+ const opValues = spec.v1Op !== spec.v2Op ? [spec.v2Op, spec.v1Op] : [spec.v2Op];
+
+ for (const param of queryParams) {
+ const paramName = getParamName(spec.resource, spec.v1Op, param.name);
+ // Check in direct fields or inside collection fields for this operation
+ const directMatch = findFieldBlocks(blocks, paramName, opValues);
+ const inCollection = blocks
+ .filter(b => b.isCollection && b.operations.some(op => opValues.includes(op)))
+ .some(b => b.innerFieldNames.includes(paramName));
+
+ if (directMatch.length === 0 && !inCollection) {
+ missing.push(`${spec.resource}.${spec.v2Op}: query param '${paramName}' (${param.name}) not found`);
+ }
+ }
+ }
+
+ expect(missing, `Missing query params:\n${missing.join('\n')}`).toEqual([]);
+ });
+});
+
+// --- Phase 5: Type and Enum Contract ---
+
+describe('Enum values match', () => {
+ it('spec enum values should match node option values', () => {
+ const mismatches: string[] = [];
+
+ for (const spec of specOps) {
+ const fields = extractBodyFields(spec.endpoint.requestBody);
+ const content = readDescription(spec.resource);
+ const blocks = parseFieldBlocks(content, spec.resource);
+ const opValues = spec.v1Op !== spec.v2Op ? [spec.v2Op, spec.v1Op] : [spec.v2Op];
+
+ for (const field of fields) {
+ const resolved = field.allOf ? resolveAllOf(field.schema) : field.schema;
+ const enumValues = field.enum ?? resolved.enum;
+ if (!enumValues || enumValues.length === 0) continue;
+ if (field.name === 'buttons' || field.name === 'blocks') continue;
+
+ const paramName = getParamName(spec.resource, spec.v1Op, field.name);
+ const matching = findFieldBlocks(blocks, paramName, opValues);
+ if (matching.length === 0) continue;
+
+ // Use the options block that has type 'options'
+ const optionsBlock = matching.find(b => b.type === 'options');
+ if (!optionsBlock) continue;
+
+ const nodeValues = optionsBlock.optionValues.sort();
+ const specValues = enumValues.map(String).sort();
+
+ if (JSON.stringify(nodeValues) !== JSON.stringify(specValues)) {
+ mismatches.push(
+ `${spec.resource}.${spec.v2Op}.${paramName}: node=[${nodeValues}], spec=[${specValues}]`,
+ );
+ }
+ }
+
+ // Also check query parameter enums
+ const queryParams = spec.endpoint.parameters.filter(p => p.in === 'query' && p.schema?.enum);
+ for (const param of queryParams) {
+ const paramName = getParamName(spec.resource, spec.v1Op, param.name);
+ const specValues = param.schema!.enum!.map(String).sort();
+
+ const matching = findFieldBlocks(blocks, paramName, opValues);
+ // Also check inside collection fields
+ const allBlocks = [...matching];
+ for (const b of blocks) {
+ if (b.isCollection && b.operations.some(op => opValues.includes(op)) && b.innerFieldNames.includes(paramName)) {
+ // For collection inner fields, use regex on the content (collections have nested blocks)
+ const fieldPattern = new RegExp(
+ `name: '${paramName}'[\\s\\S]*?options: \\[([^\\]]+)\\]`,
+ );
+ const fieldMatch = content.match(fieldPattern);
+ if (fieldMatch) {
+ const nodeValues = [...fieldMatch[1].matchAll(/value: '([^']+)'/g)].map(m => m[1]).sort();
+ if (JSON.stringify(nodeValues) !== JSON.stringify(specValues)) {
+ mismatches.push(
+ `${spec.resource}.${spec.v2Op}.${paramName} (query): node=[${nodeValues}], spec=[${specValues}]`,
+ );
+ }
+ }
+ }
+ }
+
+ const optionsBlock = allBlocks.find(b => b.type === 'options');
+ if (optionsBlock) {
+ const nodeValues = optionsBlock.optionValues.sort();
+ if (JSON.stringify(nodeValues) !== JSON.stringify(specValues)) {
+ mismatches.push(
+ `${spec.resource}.${spec.v2Op}.${paramName} (query): node=[${nodeValues}], spec=[${specValues}]`,
+ );
+ }
+ }
+ }
+ }
+
+ expect(mismatches, `Enum mismatches:\n${mismatches.join('\n')}`).toEqual([]);
+ });
+});
+
+describe('Field types match', () => {
+ it('body field types should match the expected n8n type mapping', () => {
+ const mismatches: string[] = [];
+
+ const SPECIAL_TYPE_FIELDS: Record> = {
+ bot: new Set(['webhook']),
+ form: new Set(['blocks']),
+ message: new Set(['buttons']),
+ };
+
+ for (const spec of specOps) {
+ const fields = extractBodyFields(spec.endpoint.requestBody);
+ const content = readDescription(spec.resource);
+ const blocks = parseFieldBlocks(content, spec.resource);
+ const opValues = spec.v1Op !== spec.v2Op ? [spec.v2Op, spec.v1Op] : [spec.v2Op];
+ const skipSet = SPECIAL_TYPE_FIELDS[spec.resource] ?? new Set();
+
+ for (const field of fields) {
+ if (field.readOnly) continue;
+ if (skipSet.has(field.name)) continue;
+
+ const paramName = getParamName(spec.resource, spec.v1Op, field.name);
+ const resolved = field.allOf ? resolveAllOf(field.schema) : field.schema;
+ const expectedType = toN8nType(
+ field.type,
+ field.format,
+ field.enum ?? resolved.enum,
+ field.items ?? resolved.items,
+ );
+
+ const matching = findFieldBlocks(blocks, paramName, opValues);
+ if (matching.length === 0) continue; // field existence tested separately
+
+ // Use first non-collection match, or the required one, or just first
+ const best = matching.find(b => !b.isCollection) ?? matching[0];
+ const actualType = best.type;
+
+ if (actualType !== expectedType) {
+ // Allow resourceLocator where number is expected (searchable dropdowns)
+ if (actualType === 'resourceLocator' && expectedType === 'number') continue;
+ // Allow json where object is expected
+ if (actualType === 'json' && expectedType === 'string') continue;
+ mismatches.push(
+ `${spec.resource}.${spec.v2Op}.${paramName}: node='${actualType}', expected='${expectedType}'`,
+ );
+ }
+ }
+ }
+
+ expect(mismatches, `Type mismatches:\n${mismatches.join('\n')}`).toEqual([]);
+ });
+});
+
+// --- Phase 6: Pagination ---
+
+describe('Pagination contract', () => {
+ it('paginated spec endpoints should have pagination in the node', () => {
+ const missing: string[] = [];
+
+ for (const spec of specOps) {
+ if (!spec.hasPagination) continue;
+
+ const content = readDescription(spec.resource);
+ const nodeOps = extractNodeOperations(content, spec.resource);
+ const nodeOp = nodeOps.find(o => o.value === spec.v2Op && !o.v1Only);
+ if (!nodeOp) continue;
+
+ // paginate: true is no longer in Description files (execute() mode uses Router.ts).
+ // Check for returnAll and limit fields which are still in Description files.
+ if (!content.includes("name: 'returnAll'")) {
+ missing.push(`${spec.resource}.${spec.v2Op}: missing returnAll field`);
+ }
+ if (!content.includes("name: 'limit'")) {
+ missing.push(`${spec.resource}.${spec.v2Op}: missing limit field`);
+ }
+ }
+
+ expect(missing, `Pagination issues:\n${missing.join('\n')}`).toEqual([]);
+ });
+
+ it('non-paginated spec endpoints should NOT have paginate: true', () => {
+ const extra: string[] = [];
+
+ for (const spec of specOps) {
+ if (spec.hasPagination) continue;
+
+ const content = readDescription(spec.resource);
+ const nodeOps = extractNodeOperations(content, spec.resource);
+ const nodeOp = nodeOps.find(o => o.value === spec.v2Op && !o.v1Only);
+ if (!nodeOp) continue;
+
+ if (nodeOp.hasPagination) {
+ extra.push(`${spec.resource}.${spec.v2Op}: has paginate:true but spec has no cursor param`);
+ }
+ }
+
+ expect(extra, `Unexpected pagination:\n${extra.join('\n')}`).toEqual([]);
+ });
+});
+
+// Wrapper keys (wrapBodyInKey) are no longer in Description files — they moved to
+// Router.ts in the execute() migration. The "Wrapper keys match" section has been removed.
+
+describe('Body field routing uses snake_case property names', () => {
+ it('routing.send.property should use original API snake_case names', () => {
+ const mismatches: string[] = [];
+
+ for (const spec of specOps) {
+ const fields = extractBodyFields(spec.endpoint.requestBody);
+ const content = readDescription(spec.resource);
+ const blocks = parseFieldBlocks(content, spec.resource);
+ const opValues = spec.v1Op !== spec.v2Op ? [spec.v2Op, spec.v1Op] : [spec.v2Op];
+
+ for (const field of fields) {
+ if (field.readOnly) continue;
+ if (spec.resource === 'bot' && field.name === 'webhook') continue;
+ if (spec.resource === 'form' && field.name === 'blocks') continue;
+ if (spec.resource === 'message' && field.name === 'buttons') continue;
+
+ const paramName = getParamName(spec.resource, spec.v1Op, field.name);
+ const camelName = snakeToCamel(field.name);
+ // If camelCase matches snake_case, no explicit routing property needed
+ if (camelName === field.name) continue;
+
+ const matching = findFieldBlocks(blocks, paramName, opValues);
+ if (matching.length === 0) continue;
+
+ for (const block of matching) {
+ if (block.hasRouting && block.routingProperty && block.routingProperty !== field.name) {
+ mismatches.push(
+ `${spec.resource}.${paramName}: routing property='${block.routingProperty}', expected='${field.name}'`,
+ );
+ break; // report once per field
+ }
+ }
+ }
+ }
+
+ expect(mismatches, `Property name mismatches:\n${mismatches.join('\n')}`).toEqual([]);
+ });
+});
diff --git a/integrations/n8n/tests/error-paths.test.ts b/integrations/n8n/tests/error-paths.test.ts
new file mode 100644
index 00000000..395712d7
--- /dev/null
+++ b/integrations/n8n/tests/error-paths.test.ts
@@ -0,0 +1,469 @@
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
+import type { IDataObject, IExecuteFunctions, IHookFunctions, INode } from 'n8n-workflow';
+import { NodeApiError } from 'n8n-workflow';
+import {
+ makeApiRequest,
+ makeApiRequestAllPages,
+ sanitizeBaseUrl,
+ resolveBotId,
+} from '../nodes/Pachca/GenericFunctions';
+
+// ============================================================================
+// Mock helpers
+// ============================================================================
+
+const MOCK_NODE: INode = {
+ id: 'test-node-id',
+ name: 'Pachca',
+ type: 'n8n-nodes-pachca.pachca',
+ typeVersion: 2,
+ position: [0, 0],
+ parameters: {},
+};
+
+function createExecCtx(overrides: {
+ httpResponse?: unknown;
+ httpResponses?: unknown[];
+ params?: Record;
+}): IExecuteFunctions & { _callIndex: number } {
+ const params = overrides.params ?? {};
+ let callIndex = 0;
+ const responses = overrides.httpResponses ?? [overrides.httpResponse ?? { statusCode: 200, body: { data: {} } }];
+
+ return {
+ _callIndex: 0,
+ getCredentials: vi.fn(async () => ({
+ baseUrl: 'https://api.pachca.com/api/shared/v1',
+ accessToken: 'test-token',
+ })),
+ getNodeParameter: vi.fn((name: string, _index: number, defaultVal?: unknown) => {
+ if (name in params) return params[name];
+ if (defaultVal !== undefined) return defaultVal;
+ throw new Error(`Missing parameter: ${name}`);
+ }),
+ getNode: vi.fn(() => MOCK_NODE),
+ helpers: {
+ httpRequestWithAuthentication: vi.fn(async () => {
+ const idx = callIndex++;
+ const resp = responses[idx % responses.length];
+ if (resp instanceof Error) throw resp;
+ return resp;
+ }),
+ },
+ } as unknown as IExecuteFunctions & { _callIndex: number };
+}
+
+// ============================================================================
+// sanitizeBaseUrl
+// ============================================================================
+
+describe('sanitizeBaseUrl', () => {
+ it('should strip trailing slashes', () => {
+ expect(sanitizeBaseUrl('https://api.example.com/')).toBe('https://api.example.com');
+ expect(sanitizeBaseUrl('https://api.example.com///')).toBe('https://api.example.com');
+ });
+
+ it('should pass through clean URLs', () => {
+ expect(sanitizeBaseUrl('https://api.example.com')).toBe('https://api.example.com');
+ });
+
+ it('should throw for non-http URLs', () => {
+ expect(() => sanitizeBaseUrl('ftp://files.example.com')).toThrow('Invalid Base URL');
+ expect(() => sanitizeBaseUrl('javascript:alert(1)')).toThrow('Invalid Base URL');
+ expect(() => sanitizeBaseUrl('')).toThrow('Invalid Base URL');
+ });
+
+ it('should accept http:// URLs', () => {
+ expect(sanitizeBaseUrl('http://localhost:8080')).toBe('http://localhost:8080');
+ });
+});
+
+// ============================================================================
+// makeApiRequest — error paths
+// ============================================================================
+
+describe('makeApiRequest error paths', () => {
+ it('should throw NodeApiError on 401', async () => {
+ const ctx = createExecCtx({
+ httpResponse: {
+ statusCode: 401,
+ body: { error: 'Unauthorized' },
+ headers: {},
+ },
+ });
+
+ await expect(
+ makeApiRequest.call(ctx, 'GET', '/users', undefined, undefined, 0),
+ ).rejects.toThrow(NodeApiError);
+ });
+
+ it('should throw NodeApiError on 404', async () => {
+ const ctx = createExecCtx({
+ httpResponse: {
+ statusCode: 404,
+ body: { error: 'Not found' },
+ headers: {},
+ },
+ });
+
+ await expect(
+ makeApiRequest.call(ctx, 'GET', '/users/999', undefined, undefined, 0),
+ ).rejects.toThrow(NodeApiError);
+ });
+
+ it('should format field-level validation errors (422)', async () => {
+ const ctx = createExecCtx({
+ httpResponse: {
+ statusCode: 422,
+ body: {
+ errors: [
+ { key: 'entity_id', value: 'is required' },
+ { key: 'first_name', value: 'is too short' },
+ ],
+ },
+ headers: {},
+ },
+ });
+
+ try {
+ await makeApiRequest.call(ctx, 'POST', '/users', {}, undefined, 0);
+ expect.unreachable('Should have thrown');
+ } catch (error) {
+ expect(error).toBeInstanceOf(NodeApiError);
+ const apiError = error as NodeApiError;
+ // Should use display names from FIELD_DISPLAY_NAMES
+ expect(apiError.message).toContain('Entity ID');
+ expect(apiError.message).toContain('First Name');
+ }
+ });
+
+ it('should attach Retry-After header to error', async () => {
+ const ctx = createExecCtx({
+ httpResponse: {
+ statusCode: 429,
+ body: { error: 'Rate limited' },
+ headers: { 'retry-after': '5' },
+ },
+ });
+
+ try {
+ await makeApiRequest.call(ctx, 'GET', '/users', undefined, undefined, 0);
+ expect.unreachable('Should have thrown');
+ } catch (error) {
+ expect(error).toBeInstanceOf(NodeApiError);
+ expect((error as NodeApiError & { retryAfter?: number }).retryAfter).toBe(5);
+ }
+ });
+
+ it('should handle 204 No Content as success', async () => {
+ const ctx = createExecCtx({
+ httpResponse: {
+ statusCode: 204,
+ body: null,
+ headers: {},
+ },
+ });
+
+ const result = await makeApiRequest.call(ctx, 'DELETE', '/messages/1', undefined, undefined, 0);
+ expect(result).toEqual({ success: true });
+ });
+
+ it('should handle 500 with generic error message', async () => {
+ const ctx = createExecCtx({
+ httpResponse: {
+ statusCode: 500,
+ body: {},
+ headers: {},
+ },
+ });
+
+ try {
+ await makeApiRequest.call(ctx, 'GET', '/users', undefined, undefined, 0);
+ expect.unreachable('Should have thrown');
+ } catch (error) {
+ expect(error).toBeInstanceOf(NodeApiError);
+ expect((error as NodeApiError).message).toContain('500');
+ }
+ });
+
+ it('should not send Content-Type or body for GET requests', async () => {
+ const ctx = createExecCtx({
+ httpResponse: { statusCode: 200, body: { data: [] }, headers: {} },
+ });
+ const httpMock = ctx.helpers.httpRequestWithAuthentication as ReturnType;
+
+ await makeApiRequest.call(ctx, 'GET', '/users', { should: 'be-ignored' }, { per: 50 }, 0);
+
+ const callArgs = httpMock.mock.calls[0];
+ const options = callArgs[1];
+ expect(options.body).toBeUndefined();
+ expect(options.headers['Content-Type']).toBeUndefined();
+ expect(options.qs).toEqual({ per: 50 });
+ });
+
+ it('should not send Content-Type or body for DELETE requests', async () => {
+ const ctx = createExecCtx({
+ httpResponse: { statusCode: 204, body: null, headers: {} },
+ });
+ const httpMock = ctx.helpers.httpRequestWithAuthentication as ReturnType;
+
+ await makeApiRequest.call(ctx, 'DELETE', '/messages/1', undefined, undefined, 0);
+
+ const options = httpMock.mock.calls[0][1];
+ expect(options.body).toBeUndefined();
+ expect(options.headers['Content-Type']).toBeUndefined();
+ });
+});
+
+// ============================================================================
+// makeApiRequestAllPages — retry and pagination error paths
+// ============================================================================
+
+describe('makeApiRequestAllPages error paths', () => {
+ // Stub setTimeout to resolve immediately so retry tests don't wait
+ beforeEach(() => {
+ vi.spyOn(globalThis, 'setTimeout').mockImplementation((fn: TimerHandler) => {
+ if (typeof fn === 'function') fn();
+ return 0 as unknown as ReturnType;
+ });
+ });
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ it('should retry on 429 and succeed', async () => {
+ const rateLimitResponse = {
+ statusCode: 429,
+ body: { error: 'Rate limited' },
+ headers: { 'retry-after': '1' },
+ };
+ const successResponse = {
+ statusCode: 200,
+ body: { data: [{ id: 1 }], meta: { paginate: {} } },
+ headers: {},
+ };
+
+ const ctx = createExecCtx({
+ httpResponses: [rateLimitResponse, successResponse],
+ params: { returnAll: false, limit: 10 },
+ });
+
+ const results = await makeApiRequestAllPages.call(
+ ctx, 'GET', '/users', {}, 0, 'user', 2,
+ );
+ expect(results).toEqual([{ json: { id: 1 } }]);
+ expect((ctx.helpers.httpRequestWithAuthentication as ReturnType).mock.calls).toHaveLength(2);
+ });
+
+ it('should retry on 502 and succeed', async () => {
+ const errorResponse = {
+ statusCode: 502,
+ body: { error: 'Bad Gateway' },
+ headers: {},
+ };
+ const successResponse = {
+ statusCode: 200,
+ body: { data: [{ id: 1 }], meta: { paginate: {} } },
+ headers: {},
+ };
+
+ const ctx = createExecCtx({
+ httpResponses: [errorResponse, successResponse],
+ params: { returnAll: false, limit: 10 },
+ });
+
+ const results = await makeApiRequestAllPages.call(
+ ctx, 'GET', '/users', {}, 0, 'user', 2,
+ );
+ expect(results).toEqual([{ json: { id: 1 } }]);
+ });
+
+ it('should throw after MAX_RETRIES exceeded', async () => {
+ const rateLimitResponse = {
+ statusCode: 429,
+ body: { error: 'Rate limited' },
+ headers: { 'retry-after': '1' },
+ };
+
+ // initial + 5 retries = 6 calls, then 7th throws
+ const ctx = createExecCtx({
+ httpResponses: Array(7).fill(rateLimitResponse),
+ params: { returnAll: false, limit: 10 },
+ });
+
+ await expect(
+ makeApiRequestAllPages.call(ctx, 'GET', '/users', {}, 0, 'user', 2),
+ ).rejects.toThrow(NodeApiError);
+ // 6 calls total: 1 initial + 5 retries, 6th exceeds MAX_RETRIES
+ expect((ctx.helpers.httpRequestWithAuthentication as ReturnType).mock.calls).toHaveLength(6);
+ });
+
+ it('should throw non-retryable errors immediately (e.g. 403)', async () => {
+ const ctx = createExecCtx({
+ httpResponses: [{
+ statusCode: 403,
+ body: { error: 'Forbidden' },
+ headers: {},
+ }],
+ params: { returnAll: false, limit: 10 },
+ });
+
+ await expect(
+ makeApiRequestAllPages.call(ctx, 'GET', '/users', {}, 0, 'user', 2),
+ ).rejects.toThrow(NodeApiError);
+ // Only 1 call — no retries
+ expect((ctx.helpers.httpRequestWithAuthentication as ReturnType).mock.calls).toHaveLength(1);
+ });
+
+ it('should break on duplicate cursor (infinite loop guard)', async () => {
+ const page1 = {
+ statusCode: 200,
+ body: { data: [{ id: 1 }], meta: { paginate: { next_page: 'cursor-A' } } },
+ headers: {},
+ };
+ const page2 = {
+ statusCode: 200,
+ body: { data: [{ id: 2 }], meta: { paginate: { next_page: 'cursor-A' } } },
+ headers: {},
+ };
+
+ const ctx = createExecCtx({
+ httpResponses: [page1, page2],
+ params: { returnAll: true },
+ });
+
+ const results = await makeApiRequestAllPages.call(
+ ctx, 'GET', '/users', {}, 0, 'user', 2,
+ );
+ // Guard compares nextCursor with current cursor:
+ // Page 1: cursor=undefined, nextCursor=cursor-A → no match → cursor becomes cursor-A
+ // Page 2: cursor=cursor-A, nextCursor=cursor-A → match → break
+ expect(results).toHaveLength(2);
+ });
+
+ it('should respect limit and not fetch extra pages', async () => {
+ const page1 = {
+ statusCode: 200,
+ body: {
+ data: Array.from({ length: 50 }, (_, i) => ({ id: i })),
+ meta: { paginate: { next_page: 'cursor-B' } },
+ },
+ headers: {},
+ };
+
+ const ctx = createExecCtx({
+ httpResponses: [page1],
+ params: { returnAll: false, limit: 10 },
+ });
+
+ const results = await makeApiRequestAllPages.call(
+ ctx, 'GET', '/users', {}, 0, 'user', 2,
+ );
+ // Should slice to limit
+ expect(results).toHaveLength(10);
+ // Only 1 HTTP call — didn't fetch page 2 because 50 >= limit(10)
+ expect((ctx.helpers.httpRequestWithAuthentication as ReturnType).mock.calls).toHaveLength(1);
+ });
+
+ it('should handle empty data array gracefully', async () => {
+ const ctx = createExecCtx({
+ httpResponses: [{
+ statusCode: 200,
+ body: { data: [], meta: { paginate: {} } },
+ headers: {},
+ }],
+ params: { returnAll: true },
+ });
+
+ const results = await makeApiRequestAllPages.call(
+ ctx, 'GET', '/users', {}, 0, 'user', 2,
+ );
+ expect(results).toEqual([]);
+ });
+
+ it('should pass Retry-After value to setTimeout', async () => {
+ const rateLimitResponse = {
+ statusCode: 429,
+ body: { error: 'Rate limited' },
+ headers: { 'retry-after': '3' },
+ };
+ const successResponse = {
+ statusCode: 200,
+ body: { data: [{ id: 1 }], meta: {} },
+ headers: {},
+ };
+
+ const ctx = createExecCtx({
+ httpResponses: [rateLimitResponse, successResponse],
+ params: { returnAll: false, limit: 10 },
+ });
+
+ const setTimeoutSpy = globalThis.setTimeout as unknown as ReturnType;
+ const results = await makeApiRequestAllPages.call(ctx, 'GET', '/users', {}, 0, 'user', 2);
+
+ expect(results).toEqual([{ json: { id: 1 } }]);
+ // Retry-After: 3 → retryAfter = parseInt('3') || 2 = 3 → setTimeout(fn, 3000)
+ expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 3000);
+ });
+});
+
+// ============================================================================
+// resolveBotId
+// ============================================================================
+
+describe('resolveBotId', () => {
+ function createHookCtx(httpResponse: unknown) {
+ return {
+ helpers: {
+ httpRequestWithAuthentication: vi.fn(async () => httpResponse),
+ },
+ } as unknown as IHookFunctions;
+ }
+
+ it('should return explicit botId from credentials', async () => {
+ const ctx = createHookCtx({});
+ const credentials = { botId: 42, baseUrl: 'https://api.pachca.com/api/shared/v1' };
+ const result = await resolveBotId(ctx, credentials);
+ expect(result).toBe(42);
+ // Should not make any HTTP call
+ expect((ctx.helpers.httpRequestWithAuthentication as ReturnType)).not.toHaveBeenCalled();
+ });
+
+ it('should detect bot token via token/info API', async () => {
+ const ctx = createHookCtx({
+ data: { name: null, user_id: 12345 },
+ });
+ const credentials = { botId: 0, baseUrl: 'https://api.pachca.com/api/shared/v1' };
+ const result = await resolveBotId(ctx, credentials);
+ expect(result).toBe(12345);
+ });
+
+ it('should return 0 for personal token (name is not null)', async () => {
+ const ctx = createHookCtx({
+ data: { name: 'My Token', user_id: 99 },
+ });
+ const credentials = { botId: 0, baseUrl: 'https://api.pachca.com/api/shared/v1' };
+ const result = await resolveBotId(ctx, credentials);
+ expect(result).toBe(0);
+ });
+
+ it('should return 0 when data is missing', async () => {
+ const ctx = createHookCtx({});
+ const credentials = { botId: 0, baseUrl: 'https://api.pachca.com/api/shared/v1' };
+ const result = await resolveBotId(ctx, credentials);
+ expect(result).toBe(0);
+ });
+
+ it('should propagate network errors (no silent catch)', async () => {
+ const ctx = {
+ helpers: {
+ httpRequestWithAuthentication: vi.fn(async () => {
+ throw new Error('Network timeout');
+ }),
+ },
+ } as unknown as IHookFunctions;
+ const credentials = { botId: 0, baseUrl: 'https://api.pachca.com/api/shared/v1' };
+
+ await expect(resolveBotId(ctx, credentials)).rejects.toThrow('Network timeout');
+ });
+});
diff --git a/integrations/n8n/tests/execute-helpers.test.ts b/integrations/n8n/tests/execute-helpers.test.ts
new file mode 100644
index 00000000..aefacc0c
--- /dev/null
+++ b/integrations/n8n/tests/execute-helpers.test.ts
@@ -0,0 +1,820 @@
+import { describe, it, expect, vi } from 'vitest';
+import type { IDataObject, IExecuteFunctions, INode } from 'n8n-workflow';
+import { NodeOperationError } from 'n8n-workflow';
+import {
+ simplifyItem,
+ resolveResourceLocator,
+ buildButtonRows,
+ cleanFileAttachments,
+ resolveFormBlocksFromParams,
+ splitAndValidateCommaList,
+ uploadFileToS3,
+ buildMultipartBody,
+} from '../nodes/Pachca/GenericFunctions';
+
+// ============================================================================
+// Mock helpers
+// ============================================================================
+
+const MOCK_NODE: INode = {
+ id: 'test-node-id',
+ name: 'Pachca',
+ type: 'n8n-nodes-pachca.pachca',
+ typeVersion: 2,
+ position: [0, 0],
+ parameters: {},
+};
+
+/**
+ * Creates a minimal IExecuteFunctions mock.
+ * Pass a dictionary of paramName -> value. getNodeParameter will look up values
+ * from this dictionary, throwing for missing keys unless a default is provided.
+ */
+function createMockCtx(params: Record = {}): IExecuteFunctions {
+ const getNodeParameter = vi.fn(
+ (paramName: string, _itemIndex: number, fallbackValue?: unknown) => {
+ if (paramName in params) {
+ return params[paramName];
+ }
+ if (fallbackValue !== undefined) {
+ return fallbackValue;
+ }
+ throw new Error(`Parameter "${paramName}" not found`);
+ },
+ );
+
+ return {
+ getNodeParameter,
+ getNode: vi.fn(() => MOCK_NODE),
+ } as unknown as IExecuteFunctions;
+}
+
+// ============================================================================
+// simplifyItem
+// ============================================================================
+
+describe('simplifyItem', () => {
+ it('should keep only key fields for message', () => {
+ const item: IDataObject = {
+ id: 1,
+ entity_id: 100,
+ chat_id: 200,
+ content: 'Hello',
+ user_id: 42,
+ created_at: '2026-01-01',
+ thread: null,
+ files: [],
+ extra_field: 'should be removed',
+ };
+ const result = simplifyItem(item, 'message');
+ expect(result).toEqual({
+ id: 1,
+ entity_id: 100,
+ chat_id: 200,
+ content: 'Hello',
+ user_id: 42,
+ created_at: '2026-01-01',
+ });
+ });
+
+ it('should keep only key fields for chat', () => {
+ const item: IDataObject = {
+ id: 10,
+ name: 'General',
+ channel: true,
+ public: true,
+ member_ids: [1, 2, 3],
+ created_at: '2026-01-01',
+ description: 'should be removed',
+ owner_id: 1,
+ };
+ const result = simplifyItem(item, 'chat');
+ expect(result).toEqual({
+ id: 10,
+ name: 'General',
+ channel: true,
+ public: true,
+ member_ids: [1, 2, 3],
+ created_at: '2026-01-01',
+ });
+ });
+
+ it('should keep only key fields for user', () => {
+ const item: IDataObject = {
+ id: 5,
+ first_name: 'John',
+ last_name: 'Doe',
+ nickname: 'johnd',
+ email: 'john@example.com',
+ role: 'admin',
+ suspended: false,
+ phone_number: '+1234567890',
+ department: 'Engineering',
+ };
+ const result = simplifyItem(item, 'user');
+ expect(result).toEqual({
+ id: 5,
+ first_name: 'John',
+ last_name: 'Doe',
+ nickname: 'johnd',
+ email: 'john@example.com',
+ role: 'admin',
+ suspended: false,
+ });
+ });
+
+ it('should keep only key fields for task', () => {
+ const item: IDataObject = {
+ id: 7,
+ content: 'Fix bug',
+ kind: 'task',
+ status: 'open',
+ priority: 1,
+ due_at: '2026-02-01',
+ created_at: '2026-01-15',
+ performer_ids: [1, 2],
+ assignee: 'Jane',
+ };
+ const result = simplifyItem(item, 'task');
+ expect(result).toEqual({
+ id: 7,
+ content: 'Fix bug',
+ kind: 'task',
+ status: 'open',
+ priority: 1,
+ due_at: '2026-02-01',
+ created_at: '2026-01-15',
+ });
+ });
+
+ it('should keep only key fields for bot', () => {
+ const item: IDataObject = { id: 3, webhook: { outgoing_url: 'https://example.com' }, token: 'secret' };
+ const result = simplifyItem(item, 'bot');
+ expect(result).toEqual({ id: 3, webhook: { outgoing_url: 'https://example.com' } });
+ });
+
+ it('should keep only key fields for groupTag', () => {
+ const item: IDataObject = { id: 9, name: 'Developers', users_count: 12, color: '#ff0000' };
+ const result = simplifyItem(item, 'groupTag');
+ expect(result).toEqual({ id: 9, name: 'Developers', users_count: 12 });
+ });
+
+ it('should keep only key fields for reaction', () => {
+ const item: IDataObject = { code: ':thumbsup:', name: 'thumbsup', user_id: 42, created_at: '2026-01-01', message_id: 999 };
+ const result = simplifyItem(item, 'reaction');
+ expect(result).toEqual({ code: ':thumbsup:', name: 'thumbsup', user_id: 42, created_at: '2026-01-01' });
+ });
+
+ it('should keep only key fields for export', () => {
+ const item: IDataObject = { id: 20, status: 'completed', created_at: '2026-01-01', url: 'https://...', size: 1024 };
+ const result = simplifyItem(item, 'export');
+ expect(result).toEqual({ id: 20, status: 'completed', created_at: '2026-01-01' });
+ });
+
+ it('should return full item for unknown resource', () => {
+ const item: IDataObject = { id: 1, foo: 'bar', baz: 42 };
+ const result = simplifyItem(item, 'unknownResource');
+ expect(result).toEqual(item);
+ expect(result).toBe(item); // same reference
+ });
+});
+
+// ============================================================================
+// resolveResourceLocator
+// ============================================================================
+
+describe('resolveResourceLocator', () => {
+ it('should return plain number value', () => {
+ const ctx = createMockCtx({ chatId: 123 });
+ const result = resolveResourceLocator(ctx, 'chatId', 0);
+ expect(result).toBe(123);
+ });
+
+ it('should return plain string value', () => {
+ const ctx = createMockCtx({ name: 'general' });
+ const result = resolveResourceLocator(ctx, 'name', 0);
+ expect(result).toBe('general');
+ });
+
+ it('should extract value from resourceLocator object with __rl', () => {
+ const ctx = createMockCtx({
+ chatId: { mode: 'list', value: 456, __rl: true },
+ });
+ const result = resolveResourceLocator(ctx, 'chatId', 0);
+ expect(result).toBe(456);
+ });
+
+ it('should extract string value from resourceLocator object', () => {
+ const ctx = createMockCtx({
+ chatId: { mode: 'id', value: 'abc-123', __rl: true },
+ });
+ const result = resolveResourceLocator(ctx, 'chatId', 0);
+ expect(result).toBe('abc-123');
+ });
+
+ it('should fall back to fallbackParam when primary is missing', () => {
+ const ctx = createMockCtx({ entity_id: 789 });
+ const result = resolveResourceLocator(ctx, 'chatId', 0, 'entity_id');
+ expect(result).toBe(789);
+ });
+
+ it('should throw NodeOperationError when param is missing and no fallback', () => {
+ const ctx = createMockCtx({});
+ expect(() => resolveResourceLocator(ctx, 'chatId', 0)).toThrow(NodeOperationError);
+ expect(() => resolveResourceLocator(ctx, 'chatId', 0)).toThrow('Missing required parameter: chatId');
+ });
+
+ it('should throw NodeOperationError when both primary and fallback are missing', () => {
+ const ctx = createMockCtx({});
+ // fallbackParam also not in params, so getNodeParameter for fallback will also throw
+ expect(() => resolveResourceLocator(ctx, 'chatId', 0, 'entityId')).toThrow();
+ });
+});
+
+// ============================================================================
+// buildButtonRows
+// ============================================================================
+
+describe('buildButtonRows', () => {
+ it('should return empty array when buttonLayout is none', () => {
+ const ctx = createMockCtx({ buttonLayout: 'none' });
+ expect(buildButtonRows(ctx, 0)).toEqual([]);
+ });
+
+ it('should return empty array when buttonLayout param is missing', () => {
+ const ctx = createMockCtx({});
+ expect(buildButtonRows(ctx, 0)).toEqual([]);
+ });
+
+ it('should build a single row from visual builder', () => {
+ const ctx = createMockCtx({
+ buttonLayout: 'single_row',
+ buttons: {
+ button: [
+ { text: 'Click me', type: 'callback', data: 'btn1' },
+ { text: 'Open URL', type: 'url', url: 'https://example.com' },
+ ],
+ },
+ });
+ const result = buildButtonRows(ctx, 0);
+ expect(result).toEqual([
+ [
+ { text: 'Click me', data: 'btn1' },
+ { text: 'Open URL', url: 'https://example.com' },
+ ],
+ ]);
+ });
+
+ it('should build multiple rows from visual builder', () => {
+ const ctx = createMockCtx({
+ buttonLayout: 'multiple_rows',
+ buttons: {
+ buttonRow: [
+ { text: 'Row 1', data: 'r1' },
+ { text: 'Row 2', data: 'r2' },
+ ],
+ },
+ });
+ const result = buildButtonRows(ctx, 0);
+ expect(result).toEqual([
+ [{ text: 'Row 1', data: 'r1' }],
+ [{ text: 'Row 2', data: 'r2' }],
+ ]);
+ });
+
+ it('should parse raw JSON flat array into single row', () => {
+ const ctx = createMockCtx({
+ buttonLayout: 'raw_json',
+ rawJsonButtons: JSON.stringify([
+ { text: 'A', data: 'a' },
+ { text: 'B', data: 'b' },
+ ]),
+ });
+ const result = buildButtonRows(ctx, 0);
+ expect(result).toEqual([
+ [
+ { text: 'A', data: 'a' },
+ { text: 'B', data: 'b' },
+ ],
+ ]);
+ });
+
+ it('should parse raw JSON nested array as multiple rows', () => {
+ const ctx = createMockCtx({
+ buttonLayout: 'raw_json',
+ rawJsonButtons: JSON.stringify([
+ [{ text: 'Row1-A' }],
+ [{ text: 'Row2-A' }, { text: 'Row2-B' }],
+ ]),
+ });
+ const result = buildButtonRows(ctx, 0);
+ expect(result).toEqual([
+ [{ text: 'Row1-A' }],
+ [{ text: 'Row2-A' }, { text: 'Row2-B' }],
+ ]);
+ });
+
+ it('should return empty array for raw_json with empty array string', () => {
+ const ctx = createMockCtx({
+ buttonLayout: 'raw_json',
+ rawJsonButtons: '[]',
+ });
+ expect(buildButtonRows(ctx, 0)).toEqual([]);
+ });
+
+ it('should throw NodeOperationError for invalid JSON', () => {
+ const ctx = createMockCtx({
+ buttonLayout: 'raw_json',
+ rawJsonButtons: '{not valid json',
+ });
+ expect(() => buildButtonRows(ctx, 0)).toThrow(NodeOperationError);
+ expect(() => buildButtonRows(ctx, 0)).toThrow('The buttons JSON is not valid');
+ });
+
+ it('should throw NodeOperationError when JSON is not an array', () => {
+ const ctx = createMockCtx({
+ buttonLayout: 'raw_json',
+ rawJsonButtons: '{"text": "not an array"}',
+ });
+ expect(() => buildButtonRows(ctx, 0)).toThrow(NodeOperationError);
+ expect(() => buildButtonRows(ctx, 0)).toThrow('Buttons JSON must be an array');
+ });
+});
+
+// ============================================================================
+// cleanFileAttachments
+// ============================================================================
+
+describe('cleanFileAttachments', () => {
+ it('should return empty array when no files', () => {
+ const ctx = createMockCtx({ additionalFields: {} });
+ expect(cleanFileAttachments(ctx, 0)).toEqual([]);
+ });
+
+ it('should return empty array when additionalFields param is missing', () => {
+ const ctx = createMockCtx({});
+ expect(cleanFileAttachments(ctx, 0)).toEqual([]);
+ });
+
+ it('should handle files as a plain array', () => {
+ const ctx = createMockCtx({
+ additionalFields: {
+ files: [
+ { key: 'uploads/test.pdf', name: 'test.pdf', fileType: 'file' },
+ ],
+ },
+ });
+ const result = cleanFileAttachments(ctx, 0);
+ expect(result).toEqual([
+ { key: 'uploads/test.pdf', name: 'test.pdf', file_type: 'file' },
+ ]);
+ });
+
+ it('should handle files as fixedCollection format { file: [...] }', () => {
+ const ctx = createMockCtx({
+ additionalFields: {
+ files: {
+ file: [
+ { key: 'uploads/img.png', name: 'img.png', fileType: 'image' },
+ ],
+ },
+ },
+ });
+ const result = cleanFileAttachments(ctx, 0);
+ expect(result).toEqual([
+ { key: 'uploads/img.png', name: 'img.png', file_type: 'image' },
+ ]);
+ });
+
+ it('should strip empty string, null, and undefined fields', () => {
+ const ctx = createMockCtx({
+ additionalFields: {
+ files: [
+ { key: 'uploads/a.txt', name: '', description: null, extra: undefined, fileType: 'file' },
+ ],
+ },
+ });
+ const result = cleanFileAttachments(ctx, 0);
+ expect(result).toEqual([
+ { key: 'uploads/a.txt', file_type: 'file' },
+ ]);
+ });
+
+ it('should strip zero-value height and width', () => {
+ const ctx = createMockCtx({
+ additionalFields: {
+ files: [
+ { key: 'uploads/img.png', height: 0, width: '', fileType: 'image' },
+ ],
+ },
+ });
+ const result = cleanFileAttachments(ctx, 0);
+ expect(result).toEqual([
+ { key: 'uploads/img.png', file_type: 'image' },
+ ]);
+ });
+
+ it('should keep non-zero height and width', () => {
+ const ctx = createMockCtx({
+ additionalFields: {
+ files: [
+ { key: 'uploads/img.png', height: 100, width: 200, fileType: 'image' },
+ ],
+ },
+ });
+ const result = cleanFileAttachments(ctx, 0);
+ expect(result).toEqual([
+ { key: 'uploads/img.png', height: 100, width: 200, file_type: 'image' },
+ ]);
+ });
+
+ it('should map fileType to file_type', () => {
+ const ctx = createMockCtx({
+ additionalFields: {
+ files: [{ key: 'k', fileType: 'image' }],
+ },
+ });
+ const result = cleanFileAttachments(ctx, 0);
+ expect(result[0]).toHaveProperty('file_type', 'image');
+ expect(result[0]).not.toHaveProperty('fileType');
+ });
+});
+
+// ============================================================================
+// resolveFormBlocksFromParams
+// ============================================================================
+
+describe('resolveFormBlocksFromParams', () => {
+ describe('json mode', () => {
+ it('should parse a valid JSON array of blocks', () => {
+ const blocks = [
+ { type: 'header', text: 'Title' },
+ { type: 'input', name: 'field1', label: 'Field 1' },
+ ];
+ const ctx = createMockCtx({
+ formBuilderMode: 'json',
+ formBlocks: JSON.stringify(blocks),
+ });
+ const result = resolveFormBlocksFromParams(ctx, 0);
+ expect(result).toEqual(blocks);
+ });
+
+ it('should unwrap { blocks: [...] } object', () => {
+ const blocks = [{ type: 'header', text: 'Hello' }];
+ const ctx = createMockCtx({
+ formBuilderMode: 'json',
+ formBlocks: JSON.stringify({ blocks }),
+ });
+ const result = resolveFormBlocksFromParams(ctx, 0);
+ expect(result).toEqual(blocks);
+ });
+
+ it('should throw NodeOperationError for invalid JSON', () => {
+ const ctx = createMockCtx({
+ formBuilderMode: 'json',
+ formBlocks: '{not valid',
+ });
+ expect(() => resolveFormBlocksFromParams(ctx, 0)).toThrow(NodeOperationError);
+ expect(() => resolveFormBlocksFromParams(ctx, 0)).toThrow('The JSON is not valid');
+ });
+
+ it('should throw for JSON that is neither array nor object with blocks', () => {
+ const ctx = createMockCtx({
+ formBuilderMode: 'json',
+ formBlocks: JSON.stringify({ foo: 'bar' }),
+ });
+ expect(() => resolveFormBlocksFromParams(ctx, 0)).toThrow(NodeOperationError);
+ expect(() => resolveFormBlocksFromParams(ctx, 0)).toThrow('Expected a JSON array of blocks');
+ });
+
+ it('should return empty array for empty formBlocks', () => {
+ const ctx = createMockCtx({
+ formBuilderMode: 'json',
+ formBlocks: '',
+ });
+ expect(resolveFormBlocksFromParams(ctx, 0)).toEqual([]);
+ });
+ });
+
+
+ describe('builder mode', () => {
+ it('should build header block', () => {
+ const ctx = createMockCtx({
+ formBuilderMode: 'builder',
+ formBlocks: {
+ block: [{ type: 'header', text: 'My Form' }],
+ },
+ });
+ const result = resolveFormBlocksFromParams(ctx, 0);
+ expect(result).toEqual([{ type: 'header', text: 'My Form' }]);
+ });
+
+ it('should build divider block (no extra fields)', () => {
+ const ctx = createMockCtx({
+ formBuilderMode: 'builder',
+ formBlocks: {
+ block: [{ type: 'divider' }],
+ },
+ });
+ const result = resolveFormBlocksFromParams(ctx, 0);
+ expect(result).toEqual([{ type: 'divider' }]);
+ });
+
+ it('should build input block with all properties', () => {
+ const ctx = createMockCtx({
+ formBuilderMode: 'builder',
+ formBlocks: {
+ block: [
+ {
+ type: 'input',
+ name: 'comment',
+ label: 'Comment',
+ required: true,
+ hint: 'Enter text',
+ placeholder: 'Type here...',
+ multiline: true,
+ initial_value: 'default',
+ min_length: 5,
+ max_length: 500,
+ },
+ ],
+ },
+ });
+ const result = resolveFormBlocksFromParams(ctx, 0);
+ expect(result).toEqual([
+ {
+ type: 'input',
+ name: 'comment',
+ label: 'Comment',
+ required: true,
+ hint: 'Enter text',
+ placeholder: 'Type here...',
+ multiline: true,
+ initial_value: 'default',
+ min_length: 5,
+ max_length: 500,
+ },
+ ]);
+ });
+
+ it('should build select block with options', () => {
+ const ctx = createMockCtx({
+ formBuilderMode: 'builder',
+ formBlocks: {
+ block: [
+ {
+ type: 'select',
+ name: 'choice',
+ label: 'Pick one',
+ options: {
+ option: [
+ { text: 'Option A', value: 'a', selected: true },
+ { text: 'Option B', value: 'b' },
+ ],
+ },
+ },
+ ],
+ },
+ });
+ const result = resolveFormBlocksFromParams(ctx, 0);
+ expect(result).toEqual([
+ {
+ type: 'select',
+ name: 'choice',
+ label: 'Pick one',
+ options: [
+ { text: 'Option A', value: 'a', selected: true },
+ { text: 'Option B', value: 'b' },
+ ],
+ },
+ ]);
+ });
+
+ it('should build date block with initial_date', () => {
+ const ctx = createMockCtx({
+ formBuilderMode: 'builder',
+ formBlocks: {
+ block: [
+ { type: 'date', name: 'start_date', label: 'Start', initial_date: '2026-01-01' },
+ ],
+ },
+ });
+ const result = resolveFormBlocksFromParams(ctx, 0);
+ expect(result).toEqual([
+ { type: 'date', name: 'start_date', label: 'Start', initial_date: '2026-01-01' },
+ ]);
+ });
+
+ it('should build file_input block with filetypes and max_files', () => {
+ const ctx = createMockCtx({
+ formBuilderMode: 'builder',
+ formBlocks: {
+ block: [
+ { type: 'file_input', name: 'docs', label: 'Docs', filetypes: 'png, jpg, pdf', max_files: 3 },
+ ],
+ },
+ });
+ const result = resolveFormBlocksFromParams(ctx, 0);
+ expect(result).toEqual([
+ {
+ type: 'file_input',
+ name: 'docs',
+ label: 'Docs',
+ filetypes: ['png', 'jpg', 'pdf'],
+ max_files: 3,
+ },
+ ]);
+ });
+
+ it('should return empty array when no blocks in builder', () => {
+ const ctx = createMockCtx({
+ formBuilderMode: 'builder',
+ formBlocks: { block: [] },
+ });
+ expect(resolveFormBlocksFromParams(ctx, 0)).toEqual([]);
+ });
+
+ it('should skip empty hint and placeholder in input blocks', () => {
+ const ctx = createMockCtx({
+ formBuilderMode: 'builder',
+ formBlocks: {
+ block: [
+ { type: 'input', name: 'field', label: 'Field', hint: ' ', placeholder: ' ' },
+ ],
+ },
+ });
+ const result = resolveFormBlocksFromParams(ctx, 0);
+ expect(result).toEqual([{ type: 'input', name: 'field', label: 'Field' }]);
+ });
+
+ it('should build radio block with checked options', () => {
+ const ctx = createMockCtx({
+ formBuilderMode: 'builder',
+ formBlocks: {
+ block: [
+ {
+ type: 'radio',
+ name: 'priority',
+ label: 'Priority',
+ options: {
+ option: [
+ { text: 'High', value: 'high', checked: true },
+ { text: 'Low', value: 'low' },
+ ],
+ },
+ },
+ ],
+ },
+ });
+ const result = resolveFormBlocksFromParams(ctx, 0);
+ expect(result[0]!.options).toEqual([
+ { text: 'High', value: 'high', checked: true },
+ { text: 'Low', value: 'low' },
+ ]);
+ });
+ });
+
+ describe('default mode fallback', () => {
+ it('should default to json mode when formBuilderMode param is missing', () => {
+ const ctx = createMockCtx({
+ formBlocks: JSON.stringify([{ type: 'header', text: 'Default' }]),
+ });
+ const result = resolveFormBlocksFromParams(ctx, 0);
+ expect(result).toEqual([{ type: 'header', text: 'Default' }]);
+ });
+ });
+});
+
+// ============================================================================
+// splitAndValidateCommaList
+// ============================================================================
+
+describe('splitAndValidateCommaList', () => {
+ it('should split and convert valid integers', () => {
+ const ctx = createMockCtx({});
+ const result = splitAndValidateCommaList(ctx, '1, 2, 3', 'Member IDs', 'int', 0);
+ expect(result).toEqual([1, 2, 3]);
+ });
+
+ it('should split valid strings', () => {
+ const ctx = createMockCtx({});
+ const result = splitAndValidateCommaList(ctx, 'alpha, beta, gamma', 'Tags', 'string', 0);
+ expect(result).toEqual(['alpha', 'beta', 'gamma']);
+ });
+
+ it('should filter out empty segments', () => {
+ const ctx = createMockCtx({});
+ const result = splitAndValidateCommaList(ctx, '1,,2, ,3', 'IDs', 'int', 0);
+ expect(result).toEqual([1, 2, 3]);
+ });
+
+ it('should filter out empty segments for strings', () => {
+ const ctx = createMockCtx({});
+ const result = splitAndValidateCommaList(ctx, 'a,,b, ,c', 'Names', 'string', 0);
+ expect(result).toEqual(['a', 'b', 'c']);
+ });
+
+ it('should throw NodeOperationError for invalid int values', () => {
+ const ctx = createMockCtx({});
+ expect(() =>
+ splitAndValidateCommaList(ctx, '1, abc, 3', 'Member IDs', 'int', 0),
+ ).toThrow(NodeOperationError);
+ expect(() =>
+ splitAndValidateCommaList(ctx, '1, abc, 3', 'Member IDs', 'int', 0),
+ ).toThrow('Member IDs must be numbers. Invalid values: abc');
+ });
+
+ it('should handle single value', () => {
+ const ctx = createMockCtx({});
+ expect(splitAndValidateCommaList(ctx, '42', 'ID', 'int', 0)).toEqual([42]);
+ expect(splitAndValidateCommaList(ctx, 'hello', 'Tag', 'string', 0)).toEqual(['hello']);
+ });
+
+ it('should handle whitespace-heavy input', () => {
+ const ctx = createMockCtx({});
+ const result = splitAndValidateCommaList(ctx, ' 10 , 20 , 30 ', 'IDs', 'int', 0);
+ expect(result).toEqual([10, 20, 30]);
+ });
+});
+
+// ============================================================================
+// BUG 4: S3 upload retry must rebuild multipart body with fresh presigned params
+// ============================================================================
+
+describe('uploadFileToS3 retry rebuilds multipart body', () => {
+ it('uses fresh presigned params on retry', async () => {
+ const presigned1 = {
+ 'Content-Disposition': 'inline',
+ acl: 'public-read',
+ policy: 'policy-1',
+ 'x-amz-credential': 'cred-1',
+ 'x-amz-algorithm': 'AWS4-HMAC-SHA256',
+ 'x-amz-date': '20260101T000000Z',
+ 'x-amz-signature': 'sig-1',
+ key: 'uploads/${filename}',
+ direct_url: 'https://s3.example.com/bucket-1',
+ };
+ const presigned2 = {
+ ...presigned1,
+ policy: 'policy-2',
+ 'x-amz-signature': 'sig-2',
+ direct_url: 'https://s3.example.com/bucket-2',
+ };
+
+ let apiCallCount = 0;
+ let s3CallCount = 0;
+ const s3Bodies: Buffer[] = [];
+
+ const mockCtx = {
+ getNodeParameter: vi.fn((name: string) => {
+ if (name === 'fileSource') return 'url';
+ if (name === 'fileName') return 'test.txt';
+ if (name === 'contentType') return 'text/plain';
+ if (name === 'fileUrl') return 'https://example.com/test.txt';
+ return '';
+ }),
+ getNode: vi.fn(() => MOCK_NODE),
+ getCredentials: vi.fn(async () => ({
+ baseUrl: 'https://api.pachca.com/api/shared/v1',
+ accessToken: 'test-token',
+ })),
+ helpers: {
+ httpRequestWithAuthentication: vi.fn(async () => {
+ apiCallCount++;
+ return {
+ statusCode: 200,
+ body: apiCallCount === 1 ? presigned1 : presigned2,
+ };
+ }),
+ httpRequest: vi.fn(async (opts: { method?: string; url?: string; body?: Buffer }) => {
+ if (opts.method === 'GET') {
+ // File download
+ return Buffer.from('file-content');
+ }
+ // S3 POST upload
+ s3CallCount++;
+ s3Bodies.push(opts.body as Buffer);
+ if (s3CallCount === 1) {
+ throw new Error('S3 temporary failure');
+ }
+ return {};
+ }),
+ },
+ } as unknown as IExecuteFunctions;
+
+ const result = await uploadFileToS3(mockCtx, 0);
+
+ // Should have called /uploads twice (initial + retry)
+ expect(apiCallCount).toBe(2);
+ // Should have attempted S3 upload twice
+ expect(s3CallCount).toBe(2);
+ // Second S3 upload body must contain fresh signature
+ const body2 = s3Bodies[1].toString();
+ expect(body2).toContain('sig-2');
+ expect(body2).toContain('policy-2');
+ expect(body2).not.toContain('sig-1');
+ expect(body2).not.toContain('policy-1');
+ // Result should have the correct key
+ expect(result.key).toBe('uploads/test.txt');
+ });
+});
diff --git a/integrations/n8n/tests/generic-functions.test.ts b/integrations/n8n/tests/generic-functions.test.ts
new file mode 100644
index 00000000..86b83298
--- /dev/null
+++ b/integrations/n8n/tests/generic-functions.test.ts
@@ -0,0 +1,142 @@
+import { describe, it, expect } from 'vitest';
+import {
+ verifyWebhookSignature,
+ detectMimeType,
+ buildMultipartBody,
+} from '../nodes/Pachca/GenericFunctions';
+import * as crypto from 'crypto';
+
+// ============================================================================
+// verifyWebhookSignature
+// ============================================================================
+
+describe('verifyWebhookSignature', () => {
+ const secret = 'test-secret-key';
+
+ function sign(body: string): string {
+ return crypto.createHmac('sha256', secret).update(body).digest('hex');
+ }
+
+ it('should accept a valid signature', () => {
+ const body = '{"event":"new_message"}';
+ const signature = sign(body);
+ expect(verifyWebhookSignature(body, signature, secret)).toBe(true);
+ });
+
+ it('should reject an invalid signature', () => {
+ const body = '{"event":"new_message"}';
+ expect(verifyWebhookSignature(body, 'bad-signature', secret)).toBe(false);
+ });
+
+ it('should reject a signature from a different secret', () => {
+ const body = '{"event":"new_message"}';
+ const wrongSig = crypto
+ .createHmac('sha256', 'wrong-secret')
+ .update(body)
+ .digest('hex');
+ expect(verifyWebhookSignature(body, wrongSig, secret)).toBe(false);
+ });
+
+ it('should reject when body is tampered', () => {
+ const body = '{"event":"new_message"}';
+ const signature = sign(body);
+ expect(
+ verifyWebhookSignature(body + 'x', signature, secret),
+ ).toBe(false);
+ });
+
+ it('should reject empty signature', () => {
+ expect(verifyWebhookSignature('{}', '', secret)).toBe(false);
+ });
+
+ it('should handle empty body', () => {
+ const signature = sign('');
+ expect(verifyWebhookSignature('', signature, secret)).toBe(true);
+ });
+});
+
+// ============================================================================
+// detectMimeType
+// ============================================================================
+
+describe('detectMimeType', () => {
+ it('should detect common image types', () => {
+ expect(detectMimeType('photo.jpg')).toBe('image/jpeg');
+ expect(detectMimeType('photo.jpeg')).toBe('image/jpeg');
+ expect(detectMimeType('image.png')).toBe('image/png');
+ expect(detectMimeType('anim.gif')).toBe('image/gif');
+ expect(detectMimeType('pic.webp')).toBe('image/webp');
+ });
+
+ it('should detect document types', () => {
+ expect(detectMimeType('doc.pdf')).toBe('application/pdf');
+ expect(detectMimeType('report.docx')).toBe(
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ );
+ expect(detectMimeType('data.xlsx')).toBe(
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ );
+ });
+
+ it('should detect text types', () => {
+ expect(detectMimeType('readme.txt')).toBe('text/plain');
+ expect(detectMimeType('data.csv')).toBe('text/csv');
+ expect(detectMimeType('config.json')).toBe('application/json');
+ });
+
+ it('should return octet-stream for unknown extensions', () => {
+ expect(detectMimeType('file.xyz')).toBe('application/octet-stream');
+ expect(detectMimeType('noext')).toBe('application/octet-stream');
+ });
+
+ it('should be case-insensitive', () => {
+ expect(detectMimeType('PHOTO.JPG')).toBe('image/jpeg');
+ expect(detectMimeType('DOC.PDF')).toBe('application/pdf');
+ });
+});
+
+// ============================================================================
+// buildMultipartBody
+// ============================================================================
+
+describe('buildMultipartBody', () => {
+ it('should produce valid multipart structure', () => {
+ const fields = { acl: 'public-read', policy: 'encoded-policy' };
+ const fileBuffer = Buffer.from('file-content');
+ const result = buildMultipartBody(
+ fields,
+ fileBuffer,
+ 'test.txt',
+ 'text/plain',
+ );
+
+ expect(result.contentType).toMatch(/^multipart\/form-data; boundary=/);
+ const bodyStr = result.body.toString();
+ expect(bodyStr).toContain('name="acl"');
+ expect(bodyStr).toContain('public-read');
+ expect(bodyStr).toContain('name="policy"');
+ expect(bodyStr).toContain('encoded-policy');
+ expect(bodyStr).toContain('name="file"');
+ expect(bodyStr).toContain('filename="test.txt"');
+ expect(bodyStr).toContain('Content-Type: text/plain');
+ expect(bodyStr).toContain('file-content');
+ });
+
+ it('should place file last in the body', () => {
+ const fields = { key: 'uploads/file.txt' };
+ const result = buildMultipartBody(
+ fields,
+ Buffer.from('data'),
+ 'file.txt',
+ 'text/plain',
+ );
+ const bodyStr = result.body.toString();
+ const keyPos = bodyStr.indexOf('name="key"');
+ const filePos = bodyStr.indexOf('name="file"');
+ expect(keyPos).toBeLessThan(filePos);
+ });
+});
+
+// Functions wrapBodyInKey, splitCommaToArray, nestBotWebhook, handlePachcaError,
+// getCursorPaginator, transformButtons, resolveFormBlocks were removed from
+// GenericFunctions.ts in the execute() migration and are no longer tested here.
diff --git a/integrations/n8n/tests/load-options.test.ts b/integrations/n8n/tests/load-options.test.ts
new file mode 100644
index 00000000..5405132f
--- /dev/null
+++ b/integrations/n8n/tests/load-options.test.ts
@@ -0,0 +1,311 @@
+import { describe, it, expect, vi } from 'vitest';
+import type { ILoadOptionsFunctions } from 'n8n-workflow';
+import {
+ formatUserName,
+ searchChats,
+ searchUsers,
+ searchEntities,
+ getCustomProperties,
+} from '../nodes/Pachca/GenericFunctions';
+
+// ============================================================================
+// Helpers
+// ============================================================================
+
+const BASE_URL = 'https://api.pachca.com/api/shared/v1';
+
+function createLoadCtx(overrides: {
+ httpResponses?: unknown[];
+ params?: Record;
+} = {}): ILoadOptionsFunctions {
+ let callIndex = 0;
+ const responses = overrides.httpResponses ?? [{ data: [] }];
+ const params = overrides.params ?? {};
+
+ return {
+ getCredentials: vi.fn(async () => ({
+ baseUrl: BASE_URL,
+ accessToken: 'test-token',
+ })),
+ getNodeParameter: vi.fn((name: string) => {
+ if (name in params) return params[name];
+ throw new Error(`Missing parameter: ${name}`);
+ }),
+ getCurrentNodeParameter: vi.fn((name: string) => {
+ if (name in params) return params[name];
+ throw new Error(`Missing parameter: ${name}`);
+ }),
+ helpers: {
+ httpRequestWithAuthentication: vi.fn(async () => {
+ const idx = callIndex++;
+ const resp = responses[idx % responses.length];
+ if (resp instanceof Error) throw resp;
+ return resp;
+ }),
+ },
+ } as unknown as ILoadOptionsFunctions;
+}
+
+// ============================================================================
+// formatUserName
+// ============================================================================
+
+describe('formatUserName', () => {
+ it('should format full name with nickname', () => {
+ expect(formatUserName({ first_name: 'John', last_name: 'Doe', nickname: 'jdoe' }))
+ .toBe('John Doe (@jdoe)');
+ });
+
+ it('should format full name without nickname', () => {
+ expect(formatUserName({ first_name: 'John', last_name: 'Doe', nickname: '' }))
+ .toBe('John Doe');
+ });
+
+ it('should use only first name when last name is empty', () => {
+ expect(formatUserName({ first_name: 'John', last_name: '', nickname: '' }))
+ .toBe('John');
+ });
+
+ it('should use nickname when name is empty', () => {
+ expect(formatUserName({ first_name: '', last_name: '', nickname: 'jdoe' }))
+ .toBe('jdoe (@jdoe)');
+ });
+
+ it('should fall back to "User" when everything is empty', () => {
+ expect(formatUserName({ first_name: '', last_name: '', nickname: '' }))
+ .toBe('User');
+ });
+
+ it('should filter out "null" string values', () => {
+ expect(formatUserName({ first_name: 'null', last_name: 'null', nickname: 'real' }))
+ .toBe('real (@real)');
+ });
+
+ it('should handle null values via != null check', () => {
+ expect(formatUserName({ first_name: null as unknown as string, last_name: 'Smith', nickname: '' }))
+ .toBe('Smith');
+ });
+});
+
+// ============================================================================
+// searchChats
+// ============================================================================
+
+describe('searchChats', () => {
+ it('should search chats with filter query', async () => {
+ const ctx = createLoadCtx({
+ httpResponses: [{
+ data: [
+ { id: 1, name: 'General' },
+ { id: 2, name: 'Dev Team' },
+ ],
+ }],
+ });
+
+ const result = await searchChats.call(ctx, 'dev');
+ expect(result.results).toEqual([
+ { name: 'General', value: 1 },
+ { name: 'Dev Team', value: 2 },
+ ]);
+
+ const httpMock = ctx.helpers.httpRequestWithAuthentication as ReturnType;
+ expect(httpMock.mock.calls[0][1].url).toContain('/search/chats?query=dev');
+ });
+
+ it('should encode filter query', async () => {
+ const ctx = createLoadCtx({ httpResponses: [{ data: [] }] });
+ await searchChats.call(ctx, 'hello world');
+
+ const httpMock = ctx.helpers.httpRequestWithAuthentication as ReturnType;
+ expect(httpMock.mock.calls[0][1].url).toContain('query=hello%20world');
+ });
+
+ it('should list chats without filter (paginated)', async () => {
+ const ctx = createLoadCtx({
+ httpResponses: [{
+ data: [{ id: 1, name: 'Chat 1' }],
+ meta: { paginate: { next_page: 'cursor-abc' } },
+ }],
+ });
+
+ const result = await searchChats.call(ctx, undefined);
+ expect(result.results).toEqual([{ name: 'Chat 1', value: 1 }]);
+ expect(result.paginationToken).toBe('cursor-abc');
+
+ const httpMock = ctx.helpers.httpRequestWithAuthentication as ReturnType;
+ expect(httpMock.mock.calls[0][1].url).toContain('/chats?per=50');
+ });
+
+ it('should pass pagination cursor', async () => {
+ const ctx = createLoadCtx({
+ httpResponses: [{
+ data: [{ id: 2, name: 'Chat 2' }],
+ meta: { paginate: {} },
+ }],
+ });
+
+ const result = await searchChats.call(ctx, undefined, 'cursor-abc');
+ expect(result.paginationToken).toBeUndefined();
+
+ const httpMock = ctx.helpers.httpRequestWithAuthentication as ReturnType;
+ expect(httpMock.mock.calls[0][1].url).toContain('cursor=cursor-abc');
+ });
+
+ it('should return empty results when data is missing', async () => {
+ const ctx = createLoadCtx({ httpResponses: [{}] });
+ const result = await searchChats.call(ctx, 'test');
+ expect(result.results).toEqual([]);
+ });
+});
+
+// ============================================================================
+// searchUsers
+// ============================================================================
+
+describe('searchUsers', () => {
+ it('should search users with filter', async () => {
+ const ctx = createLoadCtx({
+ httpResponses: [{
+ data: [
+ { id: 10, first_name: 'Alice', last_name: 'Smith', nickname: 'alice' },
+ { id: 20, first_name: 'Bob', last_name: '', nickname: 'bob' },
+ ],
+ }],
+ });
+
+ const result = await searchUsers.call(ctx, 'ali');
+ expect(result.results).toEqual([
+ { name: 'Alice Smith (@alice)', value: 10 },
+ { name: 'Bob (@bob)', value: 20 },
+ ]);
+ });
+
+ it('should return empty results without filter', async () => {
+ const ctx = createLoadCtx();
+ const result = await searchUsers.call(ctx, undefined);
+ expect(result.results).toEqual([]);
+ });
+
+ it('should return empty results for empty filter', async () => {
+ const ctx = createLoadCtx();
+ const result = await searchUsers.call(ctx, '');
+ expect(result.results).toEqual([]);
+ });
+
+ it('should call search endpoint with encoded query', async () => {
+ const ctx = createLoadCtx({ httpResponses: [{ data: [] }] });
+ await searchUsers.call(ctx, 'Иван');
+
+ const httpMock = ctx.helpers.httpRequestWithAuthentication as ReturnType;
+ expect(httpMock.mock.calls[0][1].url).toContain('/search/users?query=');
+ expect(httpMock.mock.calls[0][1].url).toContain(encodeURIComponent('Иван'));
+ });
+});
+
+// ============================================================================
+// searchEntities
+// ============================================================================
+
+describe('searchEntities', () => {
+ it('should dispatch to searchUsers when entityType = user', async () => {
+ const ctx = createLoadCtx({
+ params: { entityType: 'user' },
+ httpResponses: [{
+ data: [{ id: 1, first_name: 'Test', last_name: 'User', nickname: 'tu' }],
+ }],
+ });
+
+ const result = await searchEntities.call(ctx, 'test');
+ expect(result.results[0].name).toContain('Test User');
+ });
+
+ it('should return empty for thread entityType', async () => {
+ const ctx = createLoadCtx({ params: { entityType: 'thread' } });
+ const result = await searchEntities.call(ctx, 'test');
+ expect(result.results).toEqual([]);
+ });
+
+ it('should dispatch to searchChats for discussion entityType', async () => {
+ const ctx = createLoadCtx({
+ params: { entityType: 'discussion' },
+ httpResponses: [{
+ data: [{ id: 5, name: 'General' }],
+ }],
+ });
+
+ const result = await searchEntities.call(ctx, 'gen');
+ expect(result.results).toEqual([{ name: 'General', value: 5 }]);
+ });
+
+ it('should default to discussion when entityType parameter is missing', async () => {
+ const ctx = createLoadCtx({
+ params: {}, // no entityType
+ httpResponses: [{
+ data: [{ id: 1, name: 'Chat' }],
+ }],
+ });
+
+ const result = await searchEntities.call(ctx, 'chat');
+ expect(result.results).toEqual([{ name: 'Chat', value: 1 }]);
+ });
+});
+
+// ============================================================================
+// getCustomProperties
+// ============================================================================
+
+describe('getCustomProperties', () => {
+ it('should load custom properties for task resource', async () => {
+ const ctx = createLoadCtx({
+ params: { resource: 'task' },
+ httpResponses: [{
+ data: [
+ { id: 1, name: 'Priority' },
+ { id: 2, name: 'Sprint' },
+ ],
+ }],
+ });
+
+ const result = await getCustomProperties.call(ctx);
+ expect(result).toEqual([
+ { name: 'Priority', value: 1 },
+ { name: 'Sprint', value: 2 },
+ ]);
+
+ const httpMock = ctx.helpers.httpRequestWithAuthentication as ReturnType