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 @@ [![npm](https://img.shields.io/npm/v/@pachca/sdk)](https://www.npmjs.com/package/@pachca/sdk) [![npm](https://img.shields.io/npm/v/@pachca/cli)](https://www.npmjs.com/package/@pachca/cli) [![npm](https://img.shields.io/npm/v/@pachca/generator)](https://www.npmjs.com/package/@pachca/generator) +[![npm](https://img.shields.io/npm/v/n8n-nodes-pachca)](https://www.npmjs.com/package/n8n-nodes-pachca) [![PyPI](https://img.shields.io/pypi/v/pachca-sdk)](https://pypi.org/project/pachca-sdk/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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 ( 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} 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 с визуальным редактором workflow](/images/n8n/n8n-interface.avif) - -*Визуальный редактор 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), указав почту, имя и пароль. - - ![Настройка аккаунта владельца n8n](/images/n8n/owner-account.avif) - -*Настройка 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](/images/n8n/community-nodes.avif) - -*Установка через 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 можно создать несколько — для разных операций и токенов. - - ![Создание Credentials для Пачки в n8n](/images/n8n/credentials.avif) - -*Настройка Pachca API Credentials* - - - ### Шаг 4. Создание Workflow - -Workflow — визуальный редактор, в котором выстраиваются цепочки триггеров и действий. - - ![Визуальный редактор workflow в n8n](/images/n8n/workflow-editor.avif) - -*Редактор workflow* - - - Пример: отправка сообщения от лица бота. Триггер — нажатие кнопки **«Execute Workflow»**, действие — **Send a message** в Пачке: - - - **Credential** — от чьего лица будет отправлено сообщение - - **Entity ID** — ID чата - - **Content** — содержание сообщения - - Не забудьте добавить бота в чат. - - ![Пример workflow с отправкой сообщения](/images/n8n/workflow-example.avif) - -*Пример отправки сообщения* - - - В платформу встроено более 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 @@ + +# Продвинутые функции + +## Загрузка файлов + +![Настройка загрузки файла в узле Pachca](/images/n8n/file-upload.avif) + +*Ресурс 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). + +--- + +## Кнопки в сообщениях + +![Настройка кнопок в сообщении в узле Pachca](/images/n8n/message-buttons.avif) + +*Кнопки в параметрах сообщения* + + +При создании или обновлении сообщения можно добавить интерактивные кнопки через поле **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 как инструмент AI Agent в n8n](/images/n8n/ai-agent-tool.avif) + +*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 с визуальным редактором workflow](/images/n8n/n8n-interface.avif) + +*Визуальный редактор 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](/images/n8n/resource-dropdown.avif) + +*Выбор ресурса в узле Pachca* + + +Для каждого ресурса доступен свой набор операций. + +![Выпадающий список операций для ресурса Message](/images/n8n/operation-dropdown.avif) + +*Операции для ресурса 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 с Entity ID и Return All](/images/n8n/message-get-many.avif) + +*Настройка 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 в узле Pachca](/images/n8n/return-all.avif) + +*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 в узле Pachca](/images/n8n/searchable-dropdown.avif) + +*Поиск чата по имени в поле 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), указав почту, имя и пароль. + + ![Настройка аккаунта владельца n8n](/images/n8n/owner-account.avif) + +*Настройка Owner Account* + + + После входа вы увидите главный экран n8n с пустым списком workflow. + + ![Главная страница n8n после авторизации](/images/n8n/main-dashboard.avif) + +*Главная страница 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 в n8n](/images/n8n/credentials-list.avif) + +*Список Credentials* + + + Найдите **Pachca API** в списке и заполните поля: + + ![Поиск Pachca API в списке Credentials](/images/n8n/credentials-search.avif) + +*Поиск 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`. Пустое поле — разрешить все | + + ![Форма Credentials для Пачки в n8n](/images/n8n/credentials-v2.avif) + +*Форма Pachca API Credentials* + + + ### Шаг 2. Где получить токен + +В Пачке доступны два типа токенов: + + - **Персональный токен** — доступен в разделе **Автоматизации** > **Интеграции** > **API** + - **Токен бота** — доступен в настройках бота на вкладке **API** + + Доступные операции зависят от [скоупов](/api/authorization#skoupy) токена, а не от его типа. Подробнее — в разделе [Авторизация](/api/authorization). + + После заполнения полей нажмите **Test** — n8n проверит подключение вызовом [Информация о токене](GET /oauth/token/info). При успехе вы увидите подтверждение. + + ![Успешная проверка Credentials](/images/n8n/credentials-test.avif) + +*Connection tested successfully* + + + > Если тест не проходит — проверьте правильность токена и доступность API. Подробнее — в разделе [Устранение ошибок](/guides/n8n/troubleshooting). + + +## Первый workflow + + + ### Шаг 1. Создание workflow + +Workflow — визуальный редактор, в котором выстраиваются цепочки триггеров и действий. Создайте новый workflow и добавьте триггер **Manual Trigger** для ручного запуска. + + Нажмите **+** на выходе триггера и найдите **Pachca** в списке узлов. + + ![Поиск Pachca в списке узлов n8n](/images/n8n/workflow-add-node.avif) + +*Поиск узла Pachca* + + + После добавления узла на канвасе появится цепочка: Manual Trigger → Pachca. + + ![Узел Pachca на канвасе workflow](/images/n8n/workflow-pachca-node.avif) + +*Pachca на канвасе* + + + ### Шаг 2. Настройка и запуск + +Дважды кликните на узел **Pachca** и настройте: + + - **Credential:** выберите созданный Pachca API + - **Resource:** Message + - **Operation:** Create + - **Entity ID:** ID чата (число) + - **Content:** текст сообщения + + ![Настройка отправки сообщения в узле Pachca](/images/n8n/node-message-create.avif) + +*Настройка Message → Create* + + + > **Внимание:** Перед отправкой сообщения [добавьте бота в чат](/guides/bots#dostupy-bota-k-chatam-i-soobscheniyam). + + + Закройте панель настроек и нажмите **Execute Workflow**. При успехе узлы подсветятся зелёным и покажут количество обработанных элементов. + + ![Успешное выполнение workflow](/images/n8n/workflow-execute-success.avif) + +*Workflow выполнен* + + + Откройте узел Pachca, чтобы увидеть ответ API с данными созданного сообщения. + + ![Данные ответа API после выполнения](/images/n8n/workflow-output-data.avif) + +*Ответ 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 + +| Событие | Значение | Описание | +|---------|----------|----------| +| Все события | `*` | Получать все типы событий | + +![Типы событий Pachca Trigger](/images/n8n/trigger-events.avif) + +*16 типов событий в Pachca Trigger* + + +> **Внимание:** Бот получает события только из чатов, в которых он состоит. Убедитесь, что бот добавлен в нужные чаты. + + +## Настройка + +Добавьте узел **Pachca Trigger** в workflow — найдите его через поиск в панели узлов. + +![Поиск Pachca Trigger в панели узлов](/images/n8n/trigger-add.avif) + +*Поиск Pachca Trigger* + + +### Автоматический режим (рекомендуется) + +![Конфигурация Pachca Trigger с выбором события](/images/n8n/trigger-node.avif) + +*Настройка 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 Trigger](/images/n8n/workflow-trigger-example.avif) + +*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 — неверный токен + +![Ошибка 401 при неверном токене](/images/n8n/error-invalid-token.avif) + +*Ошибка авторизации* + + +**Причина:** указан некорректный или просроченный 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** из расширения. + +## Приветствие нового сотрудника + +Автоматическое приветственное сообщение при добавлении сотрудника в канал. + +![Workflow приветствия нового сотрудника](/images/n8n/workflow-welcome.avif) + +*Приветствие нового сотрудника* + + +**Как работает:** + +1. **Pachca Trigger** — событие `New Message` +2. **IF** — проверка: сообщение системное (тип `user_joined`) +3. **Pachca** (Message > Create) — отправка приветствия в тот же чат + +**Что можно добавить:** отправка личного сообщения с полезными ссылками, добавление сотрудника в рабочие каналы. + + + Готовый workflow для импорта в n8n + + +> После импорта замените Credentials на свои во всех узлах Pachca. + + +--- + +## Пересылка сообщений между каналами + +Автоматическая пересылка сообщений из одного чата в другой. + +![Workflow пересылки сообщений](/images/n8n/workflow-forward.avif) + +*Пересылка сообщений между каналами* + + +**Как работает:** + +1. **Pachca Trigger** — событие `New Message` (в исходном канале) +2. **IF** — фильтрация: пропускать сервисные сообщения, пересылать только пользовательские +3. **Pachca** (Message > Create) — отправка в целевой канал с указанием автора + +**Когда полезно:** дублирование важных новостей в общие каналы, агрегация обсуждений. + + + Готовый workflow для импорта в n8n + + +> После импорта замените Credentials, `CHAT_ID_ИСТОЧНИКА` и `CHAT_ID_ЦЕЛЕВОГО` на свои значения. + + +--- + +## Напоминание о задачах + +Ежедневная проверка просроченных задач с уведомлением в чат. + +![Workflow напоминания о задачах](/images/n8n/workflow-reminder.avif) + +*Напоминание о просроченных задачах* + + +**Как работает:** + +1. **Schedule Trigger** — ежедневный запуск (например, в 10:00) +2. **Pachca** (Task > Get Many) — получение списка задач +3. **IF** — фильтр: только просроченные (дедлайн < сегодня) +4. **Pachca** (Message > Create) — уведомление в чат со списком задач + +**Что можно добавить:** личные уведомления ответственным, группировка по проектам. + + + Готовый workflow для импорта в n8n + + +> После импорта замените Credentials и `CHAT_ID` во всех узлах Pachca. + + +--- + +## Согласование с кнопками + +Запрос на согласование через сообщение с кнопками и обработка ответа. + +![Workflow согласования с кнопками](/images/n8n/workflow-approval.avif) + +*Согласование с 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. + + +--- + +## Мониторинг и алерты + +Периодическая проверка состояния и отправка алерта при аномалиях. + +![Workflow мониторинга с алертами](/images/n8n/workflow-monitoring.avif) + +*Мониторинг с отправкой алертов в Пачку* + + +**Как работает:** + +1. **Schedule Trigger** — проверка каждые 5 минут +2. **HTTP Request** — запрос к внешнему API или сервису +3. **IF** — проверка: статус не 200 или метрика выше порога +4. **Pachca** (Message > Create) — отправка алерта в канал мониторинга + +**Что можно добавить:** проверка нескольких сервисов, графики через разворачивание ссылок, эскалация в личные сообщения. + + + Готовый workflow для импорта в n8n + + +> После импорта замените Credentials, `CHAT_ID_МОНИТОРИНГА` и URL сервиса во всех узлах Pachca. + + +--- + +## Заявка на отпуск + +Полноценный сценарий с кнопками, формой и согласованием в треде — бот принимает заявку через модальную форму и отправляет на согласование руководителю. + +![Workflow заявки на отпуск с кнопками, формой и согласованием](/images/n8n/workflow-vacation.avif) + +*Заявка на отпуск: триггер → кнопка → форма → тред → согласование* + + +**Как работает (два 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 Tool](/images/n8n/ai-agent-tool.avif) + +*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 с визуальным редактором workflow](/images/n8n/n8n-interface.avif) *Визуальный редактор 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), указав почту, имя и пароль. ![Настройка аккаунта владельца n8n](/images/n8n/owner-account.avif) *Настройка Owner Account* + После входа вы увидите главный экран n8n с пустым списком workflow. + + ![Главная страница n8n после авторизации](/images/n8n/main-dashboard.avif) + +*Главная страница 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](/images/n8n/community-nodes.avif) -*Установка через Community nodes* +## Настройка Credentials - ### Шаг 3. Создание Credentials + ### Шаг 1. Создание Credentials -Credentials — данные для авторизации. +Credentials — данные для авторизации. Перейдите в **Credentials** и нажмите **Add Credential**. - Нажмите **«Add Credential»**, найдите **Pachca API** в списке и заполните поля: + ![Список Credentials в n8n](/images/n8n/credentials-list.avif) - - **Base URL:** `https://api.pachca.com/api/shared/v1` - - **Access Token:** Токен доступа к API. В Пачке доступны два типа токенов: - - Персональный токен — доступен в разделе **Автоматизации** > **Интеграции** > **API** - - Токен бота — доступен в настройках бота на вкладке **API** +*Список Credentials* - Подробнее о токенах — в разделе [Авторизация](/api/authorization). Credentials можно создать несколько — для разных операций и токенов. - ![Создание Credentials для Пачки в n8n](/images/n8n/credentials.avif) + Найдите **Pachca API** в списке и заполните поля: -*Настройка Pachca API Credentials* + ![Поиск Pachca API в списке Credentials](/images/n8n/credentials-search.avif) +*Поиск 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 в n8n](/images/n8n/workflow-editor.avif) + ![Форма Credentials для Пачки в n8n](/images/n8n/credentials-v2.avif) -*Редактор workflow* +*Форма Pachca API Credentials* - Пример: отправка сообщения от лица бота. Триггер — нажатие кнопки **«Execute Workflow»**, действие — **Send a message** в Пачке: + ### Шаг 2. Где получить токен - - **Credential** — от чьего лица будет отправлено сообщение - - **Entity ID** — ID чата - - **Content** — содержание сообщения +В Пачке доступны два типа токенов: - Не забудьте добавить бота в чат. + - **Персональный токен** — доступен в разделе **Автоматизации** > **Интеграции** > **API** + - **Токен бота** — доступен в настройках бота на вкладке **API** - ![Пример workflow с отправкой сообщения](/images/n8n/workflow-example.avif) + Доступные операции зависят от [скоупов](/api/authorization#skoupy) токена, а не от его типа. Подробнее — в разделе [Авторизация](/api/authorization). -*Пример отправки сообщения* + После заполнения полей нажмите **Test** — n8n проверит подключение вызовом [Информация о токене](GET /oauth/token/info). При успехе вы увидите подтверждение. + ![Успешная проверка Credentials](/images/n8n/credentials-test.avif) - В платформу встроено более 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 в списке узлов n8n](/images/n8n/workflow-add-node.avif) -### Действия с чатом +*Поиск узла 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 на канвасе workflow](/images/n8n/workflow-pachca-node.avif) -### Действия с тегами пользователей +*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** + ![Настройка отправки сообщения в узле Pachca](/images/n8n/node-message-create.avif) -### Другие действия +*Настройка 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 + ![Успешное выполнение workflow](/images/n8n/workflow-execute-success.avif) -Albato — платформа для интеграции различных сервисов (CRM, системы аналитики, мессенджеры и т.д.). Через Albato к Пачке можно подключить более 250 сервисов и получать уведомления прямо в чаты и каналы Пачки. +*Workflow выполнен* -Уведомления приходят в виде сообщения от того, кто подключил интеграцию. Количество интеграций в одном чате не ограничено. -> Пачка пока способна обрабатывать только входящие события. Отправить информацию из Пачки другим сервисам через Albato нельзя. + Откройте узел Pachca, чтобы увидеть ответ API с данными созданного сообщения. + ![Данные ответа API после выполнения](/images/n8n/workflow-output-data.avif) -> **Внимание:** 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](/images/n8n/resource-dropdown.avif) +*Выбор ресурса в узле Pachca* - ### Шаг 1. Зарегистрируйтесь в Albato -![Форма регистрации на сайте Albato](/images/albato/registration.webp) +Для каждого ресурса доступен свой набор операций. -*Форма регистрации на сайте Albato* +![Выпадающий список операций для ресурса Message](/images/n8n/operation-dropdown.avif) +*Операции для ресурса 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 | Журнал безопасности | да | -![Раздел подключений в личном кабинете Albato](/images/albato/connections.webp) +> **Внимание:** Для некоторых операций требуются скоупы, которые доступны только определённым ролям (администратор, владелец). При создании персонального токена отображаются только скоупы, доступные вашей роли. Подробнее — в разделе [Авторизация](/api/authorization). -*Раздел подключений в личном кабинете Albato* +--- - ![Выбор Пачки в списке сервисов Albato](/images/albato/select-pachca.webp) +## 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 с Entity ID и Return All](/images/n8n/message-get-many.avif) -![Добавление нового подключения к сервису](/images/albato/add-connection.webp) +*Настройка Message → Get Many* -*Добавление нового подключения к сервису* +--- - ![Настройка подключения внешнего сервиса](/images/albato/setup-connection.webp) +## 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. Добавьте триггер +Управление участниками чата: добавление, удаление, изменение ролей, управление тегами. -![Создание триггера для связки в Albato](/images/albato/create-trigger.webp) +> В 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) | -![Добавление действия после триггера](/images/albato/add-action.webp) +--- -*Добавление действия после триггера* +## Group Tag +Теги (группы) сотрудников: создание, обновление, удаление, список пользователей. - ![Настройка отправки сообщения в Пачку](/images/albato/send-message.webp) +| Операция | 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` — сообщение при режиме «Нет на месте», которое отображается в профиле пользователя и при отправке ему личного сообщения или упоминании в чате. +![Переключатель Return All и поле Limit в узле Pachca](/images/n8n/return-all.avif) -В модель сообщения добавлены поля `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 в узле Pachca](/images/n8n/searchable-dropdown.avif) - +*Поиск чата по имени в поле 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 + +| Событие | Значение | Описание | +|---------|----------|----------| +| Все события | `*` | Получать все типы событий | + +![Типы событий Pachca Trigger](/images/n8n/trigger-events.avif) + +*16 типов событий в Pachca Trigger* + + +> **Внимание:** Бот получает события только из чатов, в которых он состоит. Убедитесь, что бот добавлен в нужные чаты. + + +## Настройка + +Добавьте узел **Pachca Trigger** в workflow — найдите его через поиск в панели узлов. + +![Поиск Pachca Trigger в панели узлов](/images/n8n/trigger-add.avif) + +*Поиск Pachca Trigger* + + +### Автоматический режим (рекомендуется) + +![Конфигурация Pachca Trigger с выбором события](/images/n8n/trigger-node.avif) + +*Настройка 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 Trigger](/images/n8n/workflow-trigger-example.avif) + +*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** из расширения. + +## Приветствие нового сотрудника + +Автоматическое приветственное сообщение при добавлении сотрудника в канал. + +![Workflow приветствия нового сотрудника](/images/n8n/workflow-welcome.avif) + +*Приветствие нового сотрудника* + + +**Как работает:** + +1. **Pachca Trigger** — событие `New Message` +2. **IF** — проверка: сообщение системное (тип `user_joined`) +3. **Pachca** (Message > Create) — отправка приветствия в тот же чат + +**Что можно добавить:** отправка личного сообщения с полезными ссылками, добавление сотрудника в рабочие каналы. + + + Готовый workflow для импорта в n8n + + +> После импорта замените Credentials на свои во всех узлах Pachca. + + +--- + +## Пересылка сообщений между каналами + +Автоматическая пересылка сообщений из одного чата в другой. + +![Workflow пересылки сообщений](/images/n8n/workflow-forward.avif) + +*Пересылка сообщений между каналами* + + +**Как работает:** + +1. **Pachca Trigger** — событие `New Message` (в исходном канале) +2. **IF** — фильтрация: пропускать сервисные сообщения, пересылать только пользовательские +3. **Pachca** (Message > Create) — отправка в целевой канал с указанием автора + +**Когда полезно:** дублирование важных новостей в общие каналы, агрегация обсуждений. + + + Готовый workflow для импорта в n8n + + +> После импорта замените Credentials, `CHAT_ID_ИСТОЧНИКА` и `CHAT_ID_ЦЕЛЕВОГО` на свои значения. + + +--- + +## Напоминание о задачах + +Ежедневная проверка просроченных задач с уведомлением в чат. + +![Workflow напоминания о задачах](/images/n8n/workflow-reminder.avif) + +*Напоминание о просроченных задачах* + + +**Как работает:** + +1. **Schedule Trigger** — ежедневный запуск (например, в 10:00) +2. **Pachca** (Task > Get Many) — получение списка задач +3. **IF** — фильтр: только просроченные (дедлайн < сегодня) +4. **Pachca** (Message > Create) — уведомление в чат со списком задач + +**Что можно добавить:** личные уведомления ответственным, группировка по проектам. + + + Готовый workflow для импорта в n8n + + +> После импорта замените Credentials и `CHAT_ID` во всех узлах Pachca. + + +--- + +## Согласование с кнопками + +Запрос на согласование через сообщение с кнопками и обработка ответа. + +![Workflow согласования с кнопками](/images/n8n/workflow-approval.avif) + +*Согласование с 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. + + +--- + +## Мониторинг и алерты + +Периодическая проверка состояния и отправка алерта при аномалиях. + +![Workflow мониторинга с алертами](/images/n8n/workflow-monitoring.avif) + +*Мониторинг с отправкой алертов в Пачку* + + +**Как работает:** + +1. **Schedule Trigger** — проверка каждые 5 минут +2. **HTTP Request** — запрос к внешнему API или сервису +3. **IF** — проверка: статус не 200 или метрика выше порога +4. **Pachca** (Message > Create) — отправка алерта в канал мониторинга + +**Что можно добавить:** проверка нескольких сервисов, графики через разворачивание ссылок, эскалация в личные сообщения. + + + Готовый workflow для импорта в n8n + + +> После импорта замените Credentials, `CHAT_ID_МОНИТОРИНГА` и URL сервиса во всех узлах Pachca. + + +--- + +## Заявка на отпуск + +Полноценный сценарий с кнопками, формой и согласованием в треде — бот принимает заявку через модальную форму и отправляет на согласование руководителю. + +![Workflow заявки на отпуск с кнопками, формой и согласованием](/images/n8n/workflow-vacation.avif) + +*Заявка на отпуск: триггер → кнопка → форма → тред → согласование* + + +**Как работает (два 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 Tool](/images/n8n/ai-agent-tool.avif) + +*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). + + +--- + + +# Продвинутые функции + +## Загрузка файлов + +![Настройка загрузки файла в узле Pachca](/images/n8n/file-upload.avif) + +*Ресурс 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). + +--- + +## Кнопки в сообщениях + +![Настройка кнопок в сообщении в узле Pachca](/images/n8n/message-buttons.avif) + +*Кнопки в параметрах сообщения* + + +При создании или обновлении сообщения можно добавить интерактивные кнопки через поле **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 как инструмент AI Agent в n8n](/images/n8n/ai-agent-tool.avif) + +*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 — неверный токен + +![Ошибка 401 при неверном токене](/images/n8n/error-invalid-token.avif) + +*Ошибка авторизации* + + +**Причина:** указан некорректный или просроченный 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](/images/albato/registration.webp) + +*Форма регистрации на сайте Albato* + + + 1. Откройте [сайт Albato](https://albato.ru/) и нажмите **«Регистрация»** в верхнем правом углу + 2. Введите свои данные и нажмите **«Зарегистрироваться»** + 3. Проверьте почту — вам должен прийти код подтверждения + 4. Введите код + + + ### Шаг 2. Подключите Пачку к Albato + +![Раздел подключений в личном кабинете Albato](/images/albato/connections.webp) + +*Раздел подключений в личном кабинете Albato* + + + ![Выбор Пачки в списке сервисов Albato](/images/albato/select-pachca.webp) + +*Выбор Пачки в списке сервисов Albato* + + + 1. Зайдите в свой аккаунт в Пачке в том браузере, в котором вы настраиваете интеграцию + 2. Вернитесь на сайт Albato и в личном кабинете откройте раздел **Подключения** + 3. Нажмите **«Добавить подключение»** в верхнем левом углу + 4. Выберите в списке сервисов Пачку + 5. Добавьте название подключения или оставьте базовое и нажмите **«Далее»** + 6. Нажмите **«Предоставить доступ Albato»** + 7. В открывшемся окне нажмите **«Продолжить»** + + + ### Шаг 3. Подключите внешний сервис + +![Добавление нового подключения к сервису](/images/albato/add-connection.webp) + +*Добавление нового подключения к сервису* + + + ![Настройка подключения внешнего сервиса](/images/albato/setup-connection.webp) + +*Настройка подключения внешнего сервиса* + + + 1. В личном кабинете откройте раздел **Подключения** и нажмите **«Добавить подключение»** + 2. Выберите в списке сервис, который хотите интегрировать с Пачкой, и нажмите **«Предоставить доступ»** + 3. Добавьте название подключения или оставьте базовое, введите необходимые данные и нажмите **«Далее»** + + Для подключения разных сервисов нужны разные данные. В базе знаний Albato есть [инструкции](https://blog.albato.ru/category/instrukczii/) для основных сервисов. + + + ### Шаг 4. Добавьте триггер + +![Создание триггера для связки в Albato](/images/albato/create-trigger.webp) + +*Создание триггера для связки в Albato* + + + 1. В личном кабинете перейдите в раздел **Мои связки** и нажмите **«Создать новую связку»** + 2. Нажмите **«Добавить триггер, который будет запускать связку»** + 3. Выберите сервис, из которого будут передаваться данные в Пачку + 4. Выберите событие, которое будет запускать передачу данных + 5. Выберите подключённый аккаунт + 6. Нажмите **«Добавить триггер»** + 7. При необходимости укажите дополнительные настройки (для каждого сервиса они свои) + + > В Albato можно добавлять условия для прерывания или продолжения работы связки через фильтр входящих данных внутри триггера. Подробнее — в [документации Albato](https://blog.albato.ru/instrument-filtr-vhodyashhih-dannyh/). + + + ### Шаг 5. Добавьте действие + +![Добавление действия после триггера](/images/albato/add-action.webp) + +*Добавление действия после триггера* + + + ![Настройка отправки сообщения в Пачку](/images/albato/send-message.webp) + +*Настройка отправки сообщения в Пачку* + + + 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; + expect(httpMock.mock.calls[0][1].url).toContain('entity_type=Task'); + }); + + it('should use User entity type for non-task resources', async () => { + const ctx = createLoadCtx({ + params: { resource: 'user' }, + httpResponses: [{ data: [{ id: 3, name: 'Department' }] }], + }); + + const result = await getCustomProperties.call(ctx); + expect(result).toEqual([{ name: 'Department', value: 3 }]); + + const httpMock = ctx.helpers.httpRequestWithAuthentication as ReturnType; + expect(httpMock.mock.calls[0][1].url).toContain('entity_type=User'); + }); + + it('should return empty array when no properties exist', async () => { + const ctx = createLoadCtx({ + params: { resource: 'user' }, + httpResponses: [{ data: [] }], + }); + + const result = await getCustomProperties.call(ctx); + expect(result).toEqual([]); + }); + + it('should handle missing data in response', async () => { + const ctx = createLoadCtx({ + params: { resource: 'user' }, + httpResponses: [{}], + }); + + const result = await getCustomProperties.call(ctx); + expect(result).toEqual([]); + }); +}); diff --git a/integrations/n8n/tests/router.test.ts b/integrations/n8n/tests/router.test.ts new file mode 100644 index 00000000..bf7d616b --- /dev/null +++ b/integrations/n8n/tests/router.test.ts @@ -0,0 +1,1428 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { router } from '../nodes/Pachca/SharedRouter'; +import * as GenericFunctions from '../nodes/Pachca/GenericFunctions'; + +// ============================================================================ +// Mock IExecuteFunctions context +// ============================================================================ + +interface MockCall { + method: string; + url: string; + body?: unknown; + qs?: unknown; +} + +function createMockContext(opts: { + resource: string; + operation: string; + nodeVersion?: number; + params?: Record; + inputData?: unknown[]; + continueOnFail?: boolean; +}) { + const version = opts.nodeVersion ?? 2; + const params = opts.params ?? {}; + const calls: MockCall[] = []; + + const ctx = { + getInputData: () => + (opts.inputData ?? [{}]).map((json) => ({ json: json as Record })), + getNodeParameter: vi.fn((name: string, _index: number, defaultVal?: unknown) => { + if (name === 'resource') return opts.resource; + if (name === 'operation') return opts.operation; + if (name in params) return params[name]; + if (defaultVal !== undefined) return defaultVal; + throw new Error(`Missing parameter: ${name}`); + }), + getNode: () => ({ + typeVersion: version, + name: 'Pachca', + type: 'n8n-nodes-pachca.pachca', + }), + getCredentials: vi.fn(async () => ({ + baseUrl: 'https://api.pachca.com/api/shared/v1', + accessToken: 'test-token', + })), + helpers: { + httpRequestWithAuthentication: vi.fn(async (_cred: string, options: { method: string; url: string; body?: unknown; qs?: unknown }) => { + calls.push({ + method: options.method, + url: options.url, + body: options.body, + qs: options.qs, + }); + // Default successful response + return { + statusCode: 200, + body: { data: { id: 1, name: 'test' } }, + }; + }), + httpRequest: vi.fn(async () => Buffer.from('file-content')), + assertBinaryData: vi.fn(() => ({ fileName: 'test.txt', mimeType: 'text/plain' })), + getBinaryDataBuffer: vi.fn(async () => Buffer.from('binary-data')), + }, + continueOnFail: () => opts.continueOnFail ?? false, + _calls: calls, + }; + + return ctx; +} + +// Helper to run router with mock context +async function runRouter(ctx: ReturnType) { + return router.call(ctx as any); +} + +// Helper to configure mock for paginated response +function mockPaginatedResponse(ctx: ReturnType, pages: { data: unknown[]; nextCursor?: string }[]) { + let pageIndex = 0; + ctx.helpers.httpRequestWithAuthentication.mockImplementation(async (_cred: string, options: any) => { + ctx._calls.push({ method: options.method, url: options.url, body: options.body, qs: options.qs }); + const page = pages[pageIndex++] ?? { data: [] }; + return { + statusCode: 200, + body: { + data: page.data, + meta: page.nextCursor ? { paginate: { next_page: page.nextCursor } } : {}, + }, + }; + }); +} + +// Helper to override mock response while preserving call tracking +function mockResponse(ctx: ReturnType, response: { statusCode: number; body: unknown }) { + ctx.helpers.httpRequestWithAuthentication.mockImplementation(async (_cred: string, options: any) => { + ctx._calls.push({ method: options.method, url: options.url, body: options.body, qs: options.qs }); + return response; + }); +} + +// ============================================================================ +// ROUTER DISPATCH TESTS +// ============================================================================ + +describe('Router dispatch', () => { + it('dispatches to correct resource/operation', async () => { + const ctx = createMockContext({ resource: 'profile', operation: 'get' }); + const result = await runRouter(ctx); + + expect(result).toHaveLength(1); // single output array + expect(result[0]).toHaveLength(1); // one item + expect(ctx._calls).toHaveLength(1); + expect(ctx._calls[0].method).toBe('GET'); + expect(ctx._calls[0].url).toContain('/profile'); + }); + + it('throws on unknown operation', async () => { + const ctx = createMockContext({ resource: 'profile', operation: 'nonexistent' }); + await expect(runRouter(ctx)).rejects.toThrow('Unknown operation'); + }); + + it('continueOnFail catches errors', async () => { + const ctx = createMockContext({ + resource: 'profile', + operation: 'nonexistent', + continueOnFail: true, + }); + const result = await runRouter(ctx); + expect(result[0]).toHaveLength(1); + expect(result[0][0].json).toHaveProperty('error'); + expect(result[0][0]).toHaveProperty('pairedItem', { item: 0 }); + }); + + it('adds pairedItem to every result', async () => { + const ctx = createMockContext({ resource: 'profile', operation: 'get' }); + const result = await runRouter(ctx); + expect(result[0][0]).toHaveProperty('pairedItem', { item: 0 }); + }); +}); + +// ============================================================================ +// PATH PARAMETER TESTS +// ============================================================================ + +describe('Path parameters', () => { + it('substitutes path params into URL', async () => { + const ctx = createMockContext({ + resource: 'task', + operation: 'get', + params: { id: 42 }, + }); + await runRouter(ctx); + expect(ctx._calls[0].url).toContain('/tasks/42'); + }); + + it('resolves resourceLocator params', async () => { + const ctx = createMockContext({ + resource: 'chat', + operation: 'get', + params: { id: { __rl: true, value: 123, mode: 'id' } }, + }); + await runRouter(ctx); + expect(ctx._calls[0].url).toContain('/chats/123'); + }); + + it('uses v1Fallback when main param missing', async () => { + const ctx = createMockContext({ + resource: 'message', + operation: 'get', + nodeVersion: 1, + params: { messageId: 999 }, + }); + // Override getNodeParameter to throw on 'id' but return messageId + ctx.getNodeParameter.mockImplementation((name: string, _index: number, defaultVal?: unknown) => { + if (name === 'resource') return 'message'; + if (name === 'operation') return 'get'; // v1 operation mapped via V1_OP_MAP + if (name === 'messageId') return 999; + if (name === 'id') throw new Error('Missing parameter: id'); + if (defaultVal !== undefined) return defaultVal; + throw new Error(`Missing parameter: ${name}`); + }); + await runRouter(ctx); + expect(ctx._calls[0].url).toContain('/messages/999'); + }); + + it('handles multiple path params', async () => { + const ctx = createMockContext({ + resource: 'chat', + operation: 'removeUser', + params: { chatId: 10, userId: 20 }, + }); + await runRouter(ctx); + expect(ctx._calls[0].url).toContain('/chats/10/members/20'); + }); +}); + +// ============================================================================ +// BODY WRAPPING TESTS +// ============================================================================ + +describe('Body wrapping', () => { + it('wraps body in wrapperKey', async () => { + const ctx = createMockContext({ + resource: 'chat', + operation: 'create', + params: { chatName: 'Test Chat', additionalFields: {} }, + }); + await runRouter(ctx); + const body = ctx._calls[0].body as Record; + expect(body).toHaveProperty('chat'); + expect((body.chat as Record).name).toBe('Test Chat'); + }); + + it('keeps sibling fields outside wrapper', async () => { + const ctx = createMockContext({ + resource: 'user', + operation: 'create', + params: { + email: 'test@test.com', + additionalFields: { skipEmailNotify: true, firstName: 'John' }, + }, + }); + await runRouter(ctx); + const body = ctx._calls[0].body as Record; + expect(body).toHaveProperty('user'); + expect(body).toHaveProperty('skip_email_notify', true); + expect((body.user as Record)).toHaveProperty('first_name', 'John'); + expect((body.user as Record)).not.toHaveProperty('skip_email_notify'); + }); + + it('sends body without wrapper when no wrapperKey', async () => { + const ctx = createMockContext({ + resource: 'reaction', + operation: 'create', + params: { reactionsMessageId: 100, reactionsReactionCode: '👍', additionalFields: {} }, + }); + await runRouter(ctx); + const body = ctx._calls[0].body as Record; + expect(body).toHaveProperty('code', '👍'); + expect(body).not.toHaveProperty('reaction'); + }); +}); + +// ============================================================================ +// QUERY PARAMETER TESTS +// ============================================================================ + +describe('Query parameters', () => { + it('sends query params from queryMap', async () => { + const ctx = createMockContext({ + resource: 'customProperty', + operation: 'get', + params: { entityType: 'User' }, + }); + await runRouter(ctx); + expect(ctx._calls[0].qs).toEqual({ entity_type: 'User' }); + }); + + it('sends optional query params from additionalFields', async () => { + const ctx = createMockContext({ + resource: 'security', + operation: 'getAll', + params: { + returnAll: true, + additionalFields: { eventKey: 'login', actorType: 'user' }, + }, + }); + mockPaginatedResponse(ctx, [{ data: [{ id: 1 }] }]); + await runRouter(ctx); + const qs = ctx._calls[0].qs as Record; + expect(qs).toHaveProperty('event_key', 'login'); + expect(qs).toHaveProperty('actor_type', 'user'); + }); + + it('skips empty/null query params', async () => { + const ctx = createMockContext({ + resource: 'customProperty', + operation: 'get', + params: { entityType: '' }, + }); + // entityType is '' so should be skipped + ctx.getNodeParameter.mockImplementation((name: string, _index: number, defaultVal?: unknown) => { + if (name === 'resource') return 'customProperty'; + if (name === 'operation') return 'get'; + if (name === 'entityType') return ''; + if (defaultVal !== undefined) return defaultVal; + throw new Error(`Missing: ${name}`); + }); + await runRouter(ctx); + expect(ctx._calls[0].qs).toBeUndefined(); + }); +}); + +// ============================================================================ +// COMMA-TO-ARRAY CONVERSION +// ============================================================================ + +describe('Comma-to-array conversion', () => { + it('splits comma-separated IDs into integer array', async () => { + const ctx = createMockContext({ + resource: 'chat', + operation: 'create', + params: { chatName: 'Test', additionalFields: { memberIds: '1,2,3' } }, + }); + await runRouter(ctx); + const body = ctx._calls[0].body as any; + expect(body.chat.member_ids).toEqual([1, 2, 3]); + }); + + it('validates integer IDs and rejects invalid values', async () => { + const ctx = createMockContext({ + resource: 'chat', + operation: 'create', + params: { chatName: 'Test', additionalFields: { memberIds: '1,abc,3' } }, + }); + await expect(runRouter(ctx)).rejects.toThrow(/must be numbers/); + }); +}); + +// ============================================================================ +// fixedCollection subKey HANDLING +// ============================================================================ + +describe('fixedCollection subKey', () => { + it('extracts inner array from fixedCollection object', async () => { + const ctx = createMockContext({ + resource: 'task', + operation: 'create', + params: { + taskKind: 'task', + additionalFields: { + customProperties: { property: [{ id: 1, value: 'val1' }] }, + }, + }, + }); + await runRouter(ctx); + const body = ctx._calls[0].body as any; + expect(body.task.custom_properties).toEqual([{ id: 1, value: 'val1' }]); + }); +}); + +// ============================================================================ +// DELETE OPERATIONS +// ============================================================================ + +describe('DELETE operations', () => { + it('returns { success: true } for delete', async () => { + const ctx = createMockContext({ + resource: 'task', + operation: 'delete', + params: { id: 42 }, + }); + mockResponse(ctx, { statusCode: 204, body: {} }); + const result = await runRouter(ctx); + expect(result[0][0].json).toEqual({ success: true }); + }); +}); + +// ============================================================================ +// noDataWrapper OPERATIONS +// ============================================================================ + +describe('noDataWrapper operations', () => { + it('returns full response for export.get (no .data unwrap)', async () => { + const ctx = createMockContext({ + resource: 'export', + operation: 'get', + params: { id: 1 }, + }); + const rawResponse = { id: 1, status: 'completed', url: 'https://...' }; + ctx.helpers.httpRequestWithAuthentication.mockResolvedValue({ + statusCode: 200, + body: rawResponse, + }); + const result = await runRouter(ctx); + expect(result[0][0].json).toEqual(rawResponse); + }); +}); + +// ============================================================================ +// SPECIAL HANDLERS +// ============================================================================ + +describe('Special: messageButtons', () => { + it('calls buildButtonRows and includes buttons in body', async () => { + const spy = vi.spyOn(GenericFunctions, 'buildButtonRows').mockReturnValue([[{ text: 'Click' }]]); + const fileSpy = vi.spyOn(GenericFunctions, 'cleanFileAttachments').mockReturnValue([]); + + const ctx = createMockContext({ + resource: 'message', + operation: 'create', + params: { + entityType: 'discussion', + entityId: 1, + content: 'Hello', + additionalFields: {}, + }, + }); + await runRouter(ctx); + const body = ctx._calls[0].body as any; + expect(body.message.buttons).toEqual([[{ text: 'Click' }]]); + + spy.mockRestore(); + fileSpy.mockRestore(); + }); + + it('calls cleanFileAttachments and includes files in body', async () => { + const spy = vi.spyOn(GenericFunctions, 'buildButtonRows').mockReturnValue([]); + const fileSpy = vi.spyOn(GenericFunctions, 'cleanFileAttachments').mockReturnValue([ + { key: 'uploads/test.pdf', file_type: 'file' }, + ]); + + const ctx = createMockContext({ + resource: 'message', + operation: 'create', + params: { + entityType: 'discussion', + entityId: 1, + content: 'With file', + additionalFields: {}, + }, + }); + await runRouter(ctx); + const body = ctx._calls[0].body as any; + expect(body.message.files).toEqual([{ key: 'uploads/test.pdf', file_type: 'file' }]); + + spy.mockRestore(); + fileSpy.mockRestore(); + }); +}); + +describe('Special: formBlocks', () => { + it('calls resolveFormBlocksFromParams and includes blocks in body', async () => { + const spy = vi.spyOn(GenericFunctions, 'resolveFormBlocksFromParams').mockReturnValue([ + { type: 'header', text: 'Test' }, + ]); + + const ctx = createMockContext({ + resource: 'form', + operation: 'create', + params: { + formTitle: 'My Form', + type: 'modal', + triggerId: 'abc123', + additionalFields: {}, + }, + }); + await runRouter(ctx); + const body = ctx._calls[0].body as any; + expect(body.view.blocks).toEqual([{ type: 'header', text: 'Test' }]); + + spy.mockRestore(); + }); +}); + +describe('Special: botWebhook', () => { + it('nests webhookUrl inside webhook object', async () => { + const ctx = createMockContext({ + resource: 'bot', + operation: 'update', + params: { botId: 5, webhookUrl: 'https://example.com/webhook', additionalFields: {} }, + }); + await runRouter(ctx); + const body = ctx._calls[0].body as any; + expect(body.bot.webhook).toEqual({ outgoing_url: 'https://example.com/webhook' }); + }); +}); + +describe('Special: avatarUpload', () => { + it('profile.updateAvatar calls uploadAvatar with /profile/avatar', async () => { + const spy = vi.spyOn(GenericFunctions, 'uploadAvatar').mockResolvedValue({ + id: 1, + image_url: 'https://example.com/avatar.jpg', + }); + + const ctx = createMockContext({ + resource: 'profile', + operation: 'updateAvatar', + params: { image: 'data' }, + }); + const result = await runRouter(ctx); + expect(result[0][0].json).toHaveProperty('image_url', 'https://example.com/avatar.jpg'); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy.mock.calls[0][2]).toBe('/profile/avatar'); + + spy.mockRestore(); + }); + + it('user.updateAvatar calls uploadAvatar with /users/{user_id}/avatar', async () => { + const spy = vi.spyOn(GenericFunctions, 'uploadAvatar').mockResolvedValue({ + id: 42, + image_url: 'https://example.com/user-avatar.jpg', + }); + + const ctx = createMockContext({ + resource: 'user', + operation: 'updateAvatar', + params: { image: 'data', userId: 42 }, + }); + const result = await runRouter(ctx); + expect(result[0][0].json).toHaveProperty('image_url', 'https://example.com/user-avatar.jpg'); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy.mock.calls[0][2]).toBe('/users/42/avatar'); + + spy.mockRestore(); + }); + + it('profile.deleteAvatar sends DELETE /profile/avatar', async () => { + const ctx = createMockContext({ + resource: 'profile', + operation: 'deleteAvatar', + }); + mockResponse(ctx, { statusCode: 204, body: {} }); + const result = await runRouter(ctx); + expect(ctx._calls[0].method).toBe('DELETE'); + expect(ctx._calls[0].url).toContain('/profile/avatar'); + expect(result[0][0].json).toEqual({ success: true }); + }); + + it('user.deleteAvatar sends DELETE /users/{user_id}/avatar', async () => { + const ctx = createMockContext({ + resource: 'user', + operation: 'deleteAvatar', + params: { userId: 42 }, + }); + mockResponse(ctx, { statusCode: 204, body: {} }); + const result = await runRouter(ctx); + expect(ctx._calls[0].method).toBe('DELETE'); + expect(ctx._calls[0].url).toContain('/users/42/avatar'); + expect(result[0][0].json).toEqual({ success: true }); + }); +}); + +describe('Special: fileUpload', () => { + it('calls uploadFileToS3', async () => { + const spy = vi.spyOn(GenericFunctions, 'uploadFileToS3').mockResolvedValue({ + key: 'uploads/test.txt', + file_name: 'test.txt', + content_type: 'text/plain', + size: 100, + }); + + const ctx = createMockContext({ + resource: 'file', + operation: 'create', + params: { fileSource: 'binary', binaryProperty: 'data' }, + }); + const result = await runRouter(ctx); + expect(result[0][0].json).toHaveProperty('key', 'uploads/test.txt'); + expect(spy).toHaveBeenCalledTimes(1); + + spy.mockRestore(); + }); +}); + +describe('Special: formProcessSubmission', () => { + it('passes through input data without API call', async () => { + const inputData = { type: 'view_submission', payload: { values: { feedback: 'great' } } }; + const ctx = createMockContext({ + resource: 'form', + operation: 'processSubmission', + inputData: [inputData], + }); + const result = await runRouter(ctx); + expect(result[0][0].json).toEqual(inputData); + expect(ctx._calls).toHaveLength(0); // no API call made + }); +}); + +// ============================================================================ +// SIMPLIFY MODE +// ============================================================================ + +describe('Simplify mode', () => { + it('v2 GET single returns simplified item when simplify=true', async () => { + const ctx = createMockContext({ + resource: 'message', + operation: 'get', + nodeVersion: 2, + params: { id: 1, simplify: true }, + }); + ctx.helpers.httpRequestWithAuthentication.mockResolvedValue({ + statusCode: 200, + body: { + data: { + id: 1, entity_id: 10, chat_id: 5, content: 'Hi', user_id: 3, created_at: '2026-01-01', + updated_at: '2026-01-02', message_chat_id: 5, files: [], buttons: [], + }, + }, + }); + const result = await runRouter(ctx); + const json = result[0][0].json; + expect(json).toHaveProperty('id'); + expect(json).toHaveProperty('content'); + expect(json).not.toHaveProperty('updated_at'); + expect(json).not.toHaveProperty('files'); + expect(json).not.toHaveProperty('buttons'); + }); + + it('v1 GET single does NOT simplify even with simplify=true', async () => { + const ctx = createMockContext({ + resource: 'message', + operation: 'get', + nodeVersion: 1, + params: { id: 1, simplify: true }, + }); + ctx.helpers.httpRequestWithAuthentication.mockResolvedValue({ + statusCode: 200, + body: { + data: { + id: 1, entity_id: 10, chat_id: 5, content: 'Hi', user_id: 3, created_at: '2026-01-01', + updated_at: '2026-01-02', + }, + }, + }); + const result = await runRouter(ctx); + const json = result[0][0].json; + expect(json).toHaveProperty('updated_at'); // not simplified + }); +}); + +// ============================================================================ +// V1 COMPATIBILITY: RESOURCE MAPPING +// ============================================================================ + +describe('V1 resource mapping', () => { + it('maps v1 "customFields" to "customProperty"', async () => { + const ctx = createMockContext({ + resource: 'customFields', + operation: 'getCustomProperties', // v1 op name + nodeVersion: 1, + params: { entityType: 'User' }, + }); + await runRouter(ctx); + expect(ctx._calls[0].url).toContain('/custom_properties'); + }); + + it('maps v1 "status" to "profile"', async () => { + const ctx = createMockContext({ + resource: 'status', + operation: 'getProfile', // v1 op name + nodeVersion: 1, + }); + await runRouter(ctx); + expect(ctx._calls[0].url).toContain('/profile'); + }); + + it('maps v1 "reactions" to "reaction"', async () => { + const ctx = createMockContext({ + resource: 'reactions', + operation: 'addReaction', // v1 op name + nodeVersion: 1, + params: { reactionsMessageId: 100, reactionsReactionCode: '👍', additionalFields: {} }, + }); + await runRouter(ctx); + expect(ctx._calls[0].url).toContain('/messages/100/reactions'); + }); +}); + +// ============================================================================ +// V1 COMPATIBILITY: OPERATION MAPPING +// ============================================================================ + +describe('V1 operation mapping', () => { + it('maps message "send" → "create"', async () => { + const ctx = createMockContext({ + resource: 'message', + operation: 'send', + nodeVersion: 1, + params: { + entityType: 'discussion', + entityId: 1, + content: 'Hello from v1', + additionalFields: {}, + }, + }); + vi.spyOn(GenericFunctions, 'buildButtonRows').mockReturnValue([]); + vi.spyOn(GenericFunctions, 'cleanFileAttachments').mockReturnValue([]); + await runRouter(ctx); + expect(ctx._calls[0].method).toBe('POST'); + expect(ctx._calls[0].url).toContain('/messages'); + vi.restoreAllMocks(); + }); + + it('maps message "getById" → "get"', async () => { + const ctx = createMockContext({ + resource: 'message', + operation: 'getById', + nodeVersion: 1, + params: { messageId: 42 }, + }); + ctx.getNodeParameter.mockImplementation((name: string, _index: number, defaultVal?: unknown) => { + if (name === 'resource') return 'message'; + if (name === 'operation') return 'getById'; + if (name === 'messageId') return 42; + if (name === 'id') throw new Error('Missing'); + if (defaultVal !== undefined) return defaultVal; + throw new Error(`Missing: ${name}`); + }); + await runRouter(ctx); + expect(ctx._calls[0].url).toContain('/messages/42'); + }); + + it('maps chat "getById" → "get"', async () => { + const ctx = createMockContext({ + resource: 'chat', + operation: 'getById', + nodeVersion: 1, + params: { chatId: 55 }, + }); + ctx.getNodeParameter.mockImplementation((name: string, _index: number, defaultVal?: unknown) => { + if (name === 'resource') return 'chat'; + if (name === 'operation') return 'getById'; + if (name === 'chatId') return 55; + if (name === 'id') throw new Error('Missing'); + if (defaultVal !== undefined) return defaultVal; + throw new Error(`Missing: ${name}`); + }); + await runRouter(ctx); + expect(ctx._calls[0].url).toContain('/chats/55'); + }); + + it('maps user "getById" → "get"', async () => { + const ctx = createMockContext({ + resource: 'user', + operation: 'getById', + nodeVersion: 1, + params: { userId: 77 }, + }); + ctx.getNodeParameter.mockImplementation((name: string, _index: number, defaultVal?: unknown) => { + if (name === 'resource') return 'user'; + if (name === 'operation') return 'getById'; + if (name === 'userId') return 77; + if (name === 'id') throw new Error('Missing'); + if (defaultVal !== undefined) return defaultVal; + throw new Error(`Missing: ${name}`); + }); + await runRouter(ctx); + expect(ctx._calls[0].url).toContain('/users/77'); + }); + + it('maps profile "updateStatus" → "updateStatus" (via status→profile resource map)', async () => { + const ctx = createMockContext({ + resource: 'status', + operation: 'updateStatus', + nodeVersion: 1, + params: { statusEmoji: '🏖️', statusTitle: 'On vacation', additionalFields: {} }, + }); + await runRouter(ctx); + expect(ctx._calls[0].method).toBe('PUT'); + expect(ctx._calls[0].url).toContain('/profile/status'); + const body = ctx._calls[0].body as any; + expect(body.status.emoji).toBe('🏖️'); + expect(body.status.title).toBe('On vacation'); + }); + + it('maps reaction "addReaction" → "create"', async () => { + const ctx = createMockContext({ + resource: 'reactions', + operation: 'addReaction', + nodeVersion: 1, + params: { reactionsMessageId: 100, reactionsReactionCode: '❤️', additionalFields: {} }, + }); + await runRouter(ctx); + expect(ctx._calls[0].method).toBe('POST'); + expect(ctx._calls[0].url).toContain('/messages/100/reactions'); + }); + + it('maps file "upload" → "create"', async () => { + const spy = vi.spyOn(GenericFunctions, 'uploadFileToS3').mockResolvedValue({ + key: 'uploads/test.txt', file_name: 'test.txt', content_type: 'text/plain', size: 42, + }); + const ctx = createMockContext({ + resource: 'file', + operation: 'upload', + nodeVersion: 1, + params: { fileSource: 'binary', binaryProperty: 'data' }, + }); + const result = await runRouter(ctx); + expect(result[0][0].json).toHaveProperty('key', 'uploads/test.txt'); + spy.mockRestore(); + }); + + it('maps form "createView" → "create"', async () => { + const spy = vi.spyOn(GenericFunctions, 'resolveFormBlocksFromParams').mockReturnValue([]); + const ctx = createMockContext({ + resource: 'form', + operation: 'createView', + nodeVersion: 1, + params: { + formTitle: 'Test', + type: 'modal', + triggerId: 'xyz', + additionalFields: {}, + }, + }); + await runRouter(ctx); + expect(ctx._calls[0].method).toBe('POST'); + expect(ctx._calls[0].url).toContain('/views/open'); + spy.mockRestore(); + }); + + it('maps thread "createThread" → "create"', async () => { + const ctx = createMockContext({ + resource: 'thread', + operation: 'createThread', + nodeVersion: 1, + params: { threadMessageId: 50 }, + }); + await runRouter(ctx); + expect(ctx._calls[0].url).toContain('/messages/50/thread'); + }); + + it('maps thread "getThread" → "get"', async () => { + const ctx = createMockContext({ + resource: 'thread', + operation: 'getThread', + nodeVersion: 1, + params: { threadThreadId: 60 }, + }); + await runRouter(ctx); + expect(ctx._calls[0].url).toContain('/threads/60'); + }); + + it('maps groupTag "getById" → "get"', async () => { + const ctx = createMockContext({ + resource: 'groupTag', + operation: 'getById', + nodeVersion: 1, + params: { groupTagId: 33 }, + }); + await runRouter(ctx); + expect(ctx._calls[0].url).toContain('/group_tags/33'); + }); +}); + +// ============================================================================ +// V1 ALIAS OPERATIONS +// ============================================================================ + +describe('V1 alias operations (cross-resource)', () => { + it('chat.getMembers hits GET /chats/{id}/members', async () => { + const ctx = createMockContext({ + resource: 'chat', + operation: 'getMembers', + nodeVersion: 1, + params: { chatId: 10, returnAll: true }, + }); + mockPaginatedResponse(ctx, [{ data: [{ id: 1 }, { id: 2 }] }]); + const result = await runRouter(ctx); + expect(ctx._calls[0].url).toContain('/chats/10/members'); + expect(result[0].length).toBe(2); + }); + + it('chat.addUsers hits POST /chats/{id}/members', async () => { + const ctx = createMockContext({ + resource: 'chat', + operation: 'addUsers', + nodeVersion: 1, + params: { chatId: 10, memberIds: '1,2,3', silent: false }, + }); + await runRouter(ctx); + expect(ctx._calls[0].method).toBe('POST'); + expect(ctx._calls[0].url).toContain('/chats/10/members'); + const body = ctx._calls[0].body as any; + expect(body.member_ids).toEqual([1, 2, 3]); + }); + + it('chat.removeUser hits DELETE /chats/{id}/members/{userId}', async () => { + const ctx = createMockContext({ + resource: 'chat', + operation: 'removeUser', + nodeVersion: 1, + params: { chatId: 10, userId: 5 }, + }); + mockResponse(ctx, { statusCode: 204, body: {} }); + await runRouter(ctx); + expect(ctx._calls[0].method).toBe('DELETE'); + expect(ctx._calls[0].url).toContain('/chats/10/members/5'); + }); + + it('chat.updateRole hits PUT /chats/{id}/members/{userId}', async () => { + const ctx = createMockContext({ + resource: 'chat', + operation: 'updateRole', + nodeVersion: 1, + params: { chatId: 10, userId: 5, newRole: 'admin' }, + }); + await runRouter(ctx); + expect(ctx._calls[0].method).toBe('PUT'); + expect(ctx._calls[0].url).toContain('/chats/10/members/5'); + }); + + it('chat.leaveChat hits DELETE /chats/{id}/leave', async () => { + const ctx = createMockContext({ + resource: 'chat', + operation: 'leaveChat', + nodeVersion: 1, + params: { chatId: 10 }, + }); + mockResponse(ctx, { statusCode: 204, body: {} }); + await runRouter(ctx); + expect(ctx._calls[0].method).toBe('DELETE'); + expect(ctx._calls[0].url).toContain('/chats/10/leave'); + }); + + it('message.getReadMembers hits GET /messages/{id}/read_member_ids', async () => { + const ctx = createMockContext({ + resource: 'message', + operation: 'getReadMembers', + nodeVersion: 1, + params: { messageId: 100, returnAll: true }, + }); + mockPaginatedResponse(ctx, [{ data: [{ id: 1 }] }]); + await runRouter(ctx); + expect(ctx._calls[0].url).toContain('/messages/100/read_member_ids'); + }); + + it('message.unfurl hits POST /messages/{id}/link_previews', async () => { + const ctx = createMockContext({ + resource: 'message', + operation: 'unfurl', + nodeVersion: 1, + params: { messageId: 100, linkPreviews: [{ url: 'https://example.com' }] }, + }); + await runRouter(ctx); + expect(ctx._calls[0].method).toBe('POST'); + expect(ctx._calls[0].url).toContain('/messages/100/link_previews'); + }); + + it('groupTag.addTags hits POST /chats/{id}/group_tags', async () => { + const ctx = createMockContext({ + resource: 'groupTag', + operation: 'addTags', + nodeVersion: 1, + params: { groupTagChatId: 10, groupTagIds: '1,2' }, + }); + await runRouter(ctx); + expect(ctx._calls[0].method).toBe('POST'); + expect(ctx._calls[0].url).toContain('/chats/10/group_tags'); + }); + + it('groupTag.removeTag hits DELETE /chats/{id}/group_tags/{tagId}', async () => { + const ctx = createMockContext({ + resource: 'groupTag', + operation: 'removeTag', + nodeVersion: 1, + params: { groupTagChatId: 10, tagId: 5 }, + }); + mockResponse(ctx, { statusCode: 204, body: {} }); + await runRouter(ctx); + expect(ctx._calls[0].method).toBe('DELETE'); + expect(ctx._calls[0].url).toContain('/chats/10/group_tags/5'); + }); +}); + +// ============================================================================ +// PAGINATION +// ============================================================================ + +describe('Pagination', () => { + it('fetches all pages when returnAll=true', async () => { + const ctx = createMockContext({ + resource: 'user', + operation: 'getAll', + params: { returnAll: true, query: 'test', additionalFields: {} }, + }); + mockPaginatedResponse(ctx, [ + { data: [{ id: 1 }, { id: 2 }], nextCursor: 'cursor1' }, + { data: [{ id: 3 }] }, + ]); + const result = await runRouter(ctx); + expect(result[0]).toHaveLength(3); + }); + + it('limits results when returnAll=false', async () => { + const ctx = createMockContext({ + resource: 'user', + operation: 'getAll', + params: { returnAll: false, limit: 2, query: 'test', additionalFields: {} }, + }); + mockPaginatedResponse(ctx, [ + { data: [{ id: 1 }, { id: 2 }, { id: 3 }] }, + ]); + const result = await runRouter(ctx); + expect(result[0]).toHaveLength(2); + }); +}); + +// ============================================================================ +// ROUTES TABLE VALIDATION +// ============================================================================ + +describe('ROUTES table completeness', () => { + // Verify all expected resources exist + const expectedResources = [ + 'security', 'bot', 'chat', 'member', 'groupTag', 'message', + 'linkPreview', 'reaction', 'readMember', 'thread', 'profile', + 'search', 'task', 'user', 'form', 'export', 'customProperty', 'file', + ]; + + for (const res of expectedResources) { + it(`should have resource "${res}" in ROUTES`, async () => { + // We test this indirectly by trying to route to a known operation + // If the resource exists, router won't throw "Unknown operation" for valid ops + // We just verify the resource name appears in Router.ts + const routerContent = await import('fs').then((fs) => + fs.readFileSync(require('path').resolve(__dirname, '../nodes/Pachca/SharedRouter.ts'), 'utf-8'), + ); + expect(routerContent).toContain(`\t${res}: {`); + }); + } +}); + +// ============================================================================ +// MULTI-ITEM PROCESSING +// ============================================================================ + +describe('Multi-item processing', () => { + it('processes multiple input items', async () => { + const ctx = createMockContext({ + resource: 'profile', + operation: 'get', + inputData: [{}, {}, {}], // 3 items + }); + const result = await runRouter(ctx); + expect(result[0]).toHaveLength(3); + expect(result[0][0]).toHaveProperty('pairedItem', { item: 0 }); + expect(result[0][1]).toHaveProperty('pairedItem', { item: 1 }); + expect(result[0][2]).toHaveProperty('pairedItem', { item: 2 }); + }); + + it('continues processing on error with continueOnFail', async () => { + let callCount = 0; + const ctx = createMockContext({ + resource: 'profile', + operation: 'get', + inputData: [{}, {}], + continueOnFail: true, + }); + ctx.helpers.httpRequestWithAuthentication.mockImplementation(async () => { + callCount++; + if (callCount === 1) { + return { statusCode: 500, body: { message: 'Internal error' } }; + } + return { statusCode: 200, body: { data: { id: 1 } } }; + }); + const result = await runRouter(ctx); + expect(result[0]).toHaveLength(2); + expect(result[0][0].json).toHaveProperty('error'); // first failed + expect(result[0][1].json).toHaveProperty('id', 1); // second succeeded + }); +}); + +// ============================================================================ +// RESOURCE-SPECIFIC OPERATION TESTS +// ============================================================================ + +describe('Resource operations', () => { + it('chat.create sends correct body structure', async () => { + const ctx = createMockContext({ + resource: 'chat', + operation: 'create', + params: { + chatName: 'Dev Team', + additionalFields: { channel: true, public: false, memberIds: '1,2' }, + }, + }); + await runRouter(ctx); + const body = ctx._calls[0].body as any; + expect(body.chat.name).toBe('Dev Team'); + expect(body.chat.channel).toBe(true); + expect(body.chat.public).toBe(false); + expect(body.chat.member_ids).toEqual([1, 2]); + }); + + it('chat.archive hits PUT /chats/{id}/archive', async () => { + const ctx = createMockContext({ + resource: 'chat', + operation: 'archive', + params: { id: { __rl: true, value: 10, mode: 'id' } }, + }); + await runRouter(ctx); + expect(ctx._calls[0].method).toBe('PUT'); + expect(ctx._calls[0].url).toContain('/chats/10/archive'); + }); + + it('chat.unarchive hits PUT /chats/{id}/unarchive', async () => { + const ctx = createMockContext({ + resource: 'chat', + operation: 'unarchive', + params: { id: { __rl: true, value: 10, mode: 'id' } }, + }); + await runRouter(ctx); + expect(ctx._calls[0].method).toBe('PUT'); + expect(ctx._calls[0].url).toContain('/chats/10/unarchive'); + }); + + it('message.pin hits POST /messages/{id}/pin', async () => { + const ctx = createMockContext({ + resource: 'message', + operation: 'pin', + params: { id: 42 }, + }); + await runRouter(ctx); + expect(ctx._calls[0].method).toBe('POST'); + expect(ctx._calls[0].url).toContain('/messages/42/pin'); + }); + + it('message.unpin hits DELETE /messages/{id}/pin', async () => { + const ctx = createMockContext({ + resource: 'message', + operation: 'unpin', + params: { id: 42 }, + }); + mockResponse(ctx, { statusCode: 204, body: {} }); + await runRouter(ctx); + expect(ctx._calls[0].method).toBe('DELETE'); + expect(ctx._calls[0].url).toContain('/messages/42/pin'); + }); + + it('profile.getInfo hits GET /oauth/token/info', async () => { + const ctx = createMockContext({ + resource: 'profile', + operation: 'getInfo', + }); + await runRouter(ctx); + expect(ctx._calls[0].url).toContain('/oauth/token/info'); + }); + + it('profile.getStatus hits GET /profile/status', async () => { + const ctx = createMockContext({ + resource: 'profile', + operation: 'getStatus', + }); + await runRouter(ctx); + expect(ctx._calls[0].url).toContain('/profile/status'); + }); + + it('profile.deleteStatus hits DELETE /profile/status', async () => { + const ctx = createMockContext({ + resource: 'profile', + operation: 'deleteStatus', + }); + mockResponse(ctx, { statusCode: 204, body: {} }); + await runRouter(ctx); + expect(ctx._calls[0].method).toBe('DELETE'); + expect(ctx._calls[0].url).toContain('/profile/status'); + }); + + it('export.create sends correct body without wrapper', async () => { + const ctx = createMockContext({ + resource: 'export', + operation: 'create', + params: { + startAt: '2026-01-01', + endAt: '2026-01-31', + webhookUrl: 'https://example.com', + additionalFields: {}, + }, + }); + await runRouter(ctx); + const body = ctx._calls[0].body as any; + expect(body.start_at).toBe('2026-01-01'); + expect(body.end_at).toBe('2026-01-31'); + expect(body.webhook_url).toBe('https://example.com'); + // No wrapper key for export + expect(body).not.toHaveProperty('export'); + }); + + it('linkPreview.create sends body to correct endpoint', async () => { + const ctx = createMockContext({ + resource: 'linkPreview', + operation: 'create', + params: { id: 42, linkPreviews: [{ url: 'https://example.com', title: 'Example' }] }, + }); + await runRouter(ctx); + expect(ctx._calls[0].method).toBe('POST'); + expect(ctx._calls[0].url).toContain('/messages/42/link_previews'); + }); + + it('groupTag.create wraps in group_tag key', async () => { + const ctx = createMockContext({ + resource: 'groupTag', + operation: 'create', + params: { groupTagName: 'Engineers' }, + }); + await runRouter(ctx); + const body = ctx._calls[0].body as any; + expect(body.group_tag.name).toBe('Engineers'); + }); + + it('user.getStatus hits correct endpoint', async () => { + const ctx = createMockContext({ + resource: 'user', + operation: 'getStatus', + params: { userId: 42 }, + }); + await runRouter(ctx); + expect(ctx._calls[0].url).toContain('/users/42/status'); + }); + + it('reaction.delete sends code as query param', async () => { + const ctx = createMockContext({ + resource: 'reaction', + operation: 'delete', + params: { reactionsMessageId: 100, reactionsReactionCode: '👍', name: '' }, + }); + mockResponse(ctx, { statusCode: 204, body: {} }); + await runRouter(ctx); + expect(ctx._calls[0].method).toBe('DELETE'); + expect(ctx._calls[0].url).toContain('/messages/100/reactions'); + expect(ctx._calls[0].qs).toHaveProperty('code', '👍'); + }); + + it('search.getAllChats sends query', async () => { + const ctx = createMockContext({ + resource: 'search', + operation: 'getAllChats', + params: { returnAll: false, limit: 10, query: 'dev', additionalFields: {} }, + }); + mockPaginatedResponse(ctx, [{ data: [{ id: 1, name: 'dev-team' }] }]); + await runRouter(ctx); + expect(ctx._calls[0].url).toContain('/search/chats'); + expect(ctx._calls[0].qs).toHaveProperty('query', 'dev'); + }); +}); + +// ============================================================================ +// BUG FIX REGRESSION TESTS +// ============================================================================ + +describe('BUG 1: v1 user.getAll reads query from additionalOptions', () => { + it('sends query param when stored in additionalOptions (v1 compat)', async () => { + const ctx = createMockContext({ + resource: 'user', + operation: 'getAll', + nodeVersion: 1, + params: { + returnAll: false, + limit: 10, + additionalOptions: { query: 'john' }, + }, + }); + mockPaginatedResponse(ctx, [{ data: [{ id: 1, first_name: 'John' }] }]); + await runRouter(ctx); + expect(ctx._calls[0].qs).toHaveProperty('query', 'john'); + }); + + it('sends query param from top-level (v2)', async () => { + const ctx = createMockContext({ + resource: 'user', + operation: 'getAll', + nodeVersion: 2, + params: { + returnAll: false, + limit: 10, + query: 'jane', + additionalFields: {}, + }, + }); + mockPaginatedResponse(ctx, [{ data: [{ id: 2, first_name: 'Jane' }] }]); + await runRouter(ctx); + expect(ctx._calls[0].qs).toHaveProperty('query', 'jane'); + }); +}); + +describe('BUG 2: v1 user.getAll applies filterOptions client-side', () => { + it('filters by role', async () => { + const ctx = createMockContext({ + resource: 'user', + operation: 'getAll', + nodeVersion: 1, + params: { + returnAll: true, + additionalOptions: {}, + filterOptions: { filterRole: ['admin'] }, + }, + }); + mockPaginatedResponse(ctx, [{ data: [ + { id: 1, role: 'admin', bot: false, suspended: false }, + { id: 2, role: 'user', bot: false, suspended: false }, + { id: 3, role: 'admin', bot: false, suspended: false }, + ] }]); + const result = await runRouter(ctx); + expect(result[0]).toHaveLength(2); + expect(result[0].map(r => r.json.id)).toEqual([1, 3]); + }); + + it('filters bots only', async () => { + const ctx = createMockContext({ + resource: 'user', + operation: 'getAll', + nodeVersion: 1, + params: { + returnAll: true, + additionalOptions: {}, + filterOptions: { filterBot: 'bots' }, + }, + }); + mockPaginatedResponse(ctx, [{ data: [ + { id: 1, bot: true, role: 'user', suspended: false }, + { id: 2, bot: false, role: 'user', suspended: false }, + ] }]); + const result = await runRouter(ctx); + expect(result[0]).toHaveLength(1); + expect(result[0][0].json.id).toBe(1); + }); + + it('filters users only (excludes bots)', async () => { + const ctx = createMockContext({ + resource: 'user', + operation: 'getAll', + nodeVersion: 1, + params: { + returnAll: true, + additionalOptions: {}, + filterOptions: { filterBot: 'users' }, + }, + }); + mockPaginatedResponse(ctx, [{ data: [ + { id: 1, bot: true, role: 'user', suspended: false }, + { id: 2, bot: false, role: 'user', suspended: false }, + ] }]); + const result = await runRouter(ctx); + expect(result[0]).toHaveLength(1); + expect(result[0][0].json.id).toBe(2); + }); + + it('filters suspended only', async () => { + const ctx = createMockContext({ + resource: 'user', + operation: 'getAll', + nodeVersion: 1, + params: { + returnAll: true, + additionalOptions: {}, + filterOptions: { filterSuspended: 'suspended' }, + }, + }); + mockPaginatedResponse(ctx, [{ data: [ + { id: 1, suspended: true, role: 'user', bot: false }, + { id: 2, suspended: false, role: 'user', bot: false }, + ] }]); + const result = await runRouter(ctx); + expect(result[0]).toHaveLength(1); + expect(result[0][0].json.id).toBe(1); + }); + + it('filters active only', async () => { + const ctx = createMockContext({ + resource: 'user', + operation: 'getAll', + nodeVersion: 1, + params: { + returnAll: true, + additionalOptions: {}, + filterOptions: { filterSuspended: 'active' }, + }, + }); + mockPaginatedResponse(ctx, [{ data: [ + { id: 1, suspended: true, role: 'user', bot: false }, + { id: 2, suspended: false, role: 'user', bot: false }, + ] }]); + const result = await runRouter(ctx); + expect(result[0]).toHaveLength(1); + expect(result[0][0].json.id).toBe(2); + }); + + it('filters by invite_status', async () => { + const ctx = createMockContext({ + resource: 'user', + operation: 'getAll', + nodeVersion: 1, + params: { + returnAll: true, + additionalOptions: {}, + filterOptions: { filterInviteStatus: ['confirmed'] }, + }, + }); + mockPaginatedResponse(ctx, [{ data: [ + { id: 1, invite_status: 'confirmed', role: 'user', bot: false, suspended: false }, + { id: 2, invite_status: 'sent', role: 'user', bot: false, suspended: false }, + ] }]); + const result = await runRouter(ctx); + expect(result[0]).toHaveLength(1); + expect(result[0][0].json.id).toBe(1); + }); + + it('does NOT apply filters in v2 mode', async () => { + const ctx = createMockContext({ + resource: 'user', + operation: 'getAll', + nodeVersion: 2, + params: { + returnAll: true, + additionalFields: {}, + }, + }); + mockPaginatedResponse(ctx, [{ data: [ + { id: 1, role: 'admin' }, + { id: 2, role: 'user' }, + ] }]); + const result = await runRouter(ctx); + expect(result[0]).toHaveLength(2); // no filtering + }); + + it('combines multiple filters', async () => { + const ctx = createMockContext({ + resource: 'user', + operation: 'getAll', + nodeVersion: 1, + params: { + returnAll: true, + additionalOptions: {}, + filterOptions: { filterRole: ['admin'], filterSuspended: 'active' }, + }, + }); + mockPaginatedResponse(ctx, [{ data: [ + { id: 1, role: 'admin', suspended: false, bot: false }, + { id: 2, role: 'admin', suspended: true, bot: false }, + { id: 3, role: 'user', suspended: false, bot: false }, + ] }]); + const result = await runRouter(ctx); + expect(result[0]).toHaveLength(1); + expect(result[0][0].json.id).toBe(1); + }); +}); diff --git a/integrations/n8n/tests/routes-spec.test.ts b/integrations/n8n/tests/routes-spec.test.ts new file mode 100644 index 00000000..a56be8b8 --- /dev/null +++ b/integrations/n8n/tests/routes-spec.test.ts @@ -0,0 +1,506 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import * as fs from 'fs'; +import * as path from 'path'; +import { parseOpenAPI, resolveAllOf } from '@pachca/openapi-parser'; +import type { Endpoint, Schema } from '@pachca/openapi-parser'; +import { getWrapperKey } from '../scripts/utils'; + +// ============================================================================ +// SETUP: Paths +// ============================================================================ + +const ROOT = path.resolve(__dirname, '../../..'); +const SPEC_PATH = path.join(ROOT, 'packages/spec/openapi.yaml'); +const ROUTER_PATH = path.resolve(__dirname, '../nodes/Pachca/SharedRouter.ts'); + +// ============================================================================ +// Mapping tables (mirrored from generate-n8n.ts / contract.test.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'; +} + +/** Endpoints intentionally not covered by the n8n node */ +const INTENTIONALLY_SKIPPED = new Set([ + 'POST /direct_url', // low-level S3 upload, handled internally +]); + +// V1 alias operations that exist in ROUTES but NOT in the spec (they are +// duplicates of v2 sub-resource operations kept for backward compatibility) +const V1_ALIAS_OPS: Record = { + message: ['getReadMembers', 'unfurl'], + chat: ['getMembers', 'addUsers', 'removeUser', 'updateRole', 'leaveChat'], + groupTag: ['addTags', 'removeTag'], +}; + +// Special operations that don't map directly to an API endpoint +const SPECIAL_NON_API_OPS = new Set([ + 'form.processSubmission', +]); + +// ============================================================================ +// HELPERS: Parse OpenAPI spec +// ============================================================================ + +interface SpecOperation { + resource: string; + v2Op: 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' + : tag === 'Export' ? 'export' + : 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 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, endpoint: ep, hasPagination, wrapperKey }); + } + } + + return operations; +} + +// ============================================================================ +// HELPERS: Parse ROUTES table from Router.ts +// ============================================================================ + +interface ParsedRoute { + resource: string; + operation: string; + method: string; + path: string; + paginated: boolean; + wrapperKey: string | null; + special: string | null; +} + +function parseRoutesFromFile(): ParsedRoute[] { + const content = fs.readFileSync(ROUTER_PATH, 'utf-8'); + + // Extract the ROUTES block: starts with `const ROUTES: Record<...> = {` + // and ends before `// ===...` section (Router Entry Point) + const routesStart = content.indexOf('const ROUTES: Record<'); + if (routesStart === -1) throw new Error('Could not find ROUTES table in Router.ts'); + + const routesSection = content.slice(routesStart); + // Find the closing `};` of the ROUTES object — it's followed by the Router section + const routerSectionIdx = routesSection.indexOf('\n// ============================================================================\n// Router Entry Point'); + const routesBlock = routerSectionIdx !== -1 + ? routesSection.slice(0, routerSectionIdx) + : routesSection; + + const routes: ParsedRoute[] = []; + + // Parse resource blocks: top-level keys like `security: {`, `bot: {`, etc. + // Match: : { ... at top level (one tab indent) + const resourceRegex = /^\t(\w+): \{$/gm; + let resourceMatch: RegExpExecArray | null; + + while ((resourceMatch = resourceRegex.exec(routesBlock)) !== null) { + const resource = resourceMatch[1]; + const resourceStart = resourceMatch.index + resourceMatch[0].length; + + // Find the end of this resource block by tracking brace depth + let depth = 1; + let pos = resourceStart; + while (pos < routesBlock.length && depth > 0) { + if (routesBlock[pos] === '{') depth++; + if (routesBlock[pos] === '}') depth--; + pos++; + } + const resourceBody = routesBlock.slice(resourceStart, pos - 1); + + // Parse operation blocks within the resource: `: {` + const opRegex = /^\t\t(\w+): \{$/gm; + let opMatch: RegExpExecArray | null; + + while ((opMatch = opRegex.exec(resourceBody)) !== null) { + const operation = opMatch[1]; + const opStart = opMatch.index + opMatch[0].length; + + // Find the end of this operation block + let opDepth = 1; + let opPos = opStart; + while (opPos < resourceBody.length && opDepth > 0) { + if (resourceBody[opPos] === '{') opDepth++; + if (resourceBody[opPos] === '}') opDepth--; + opPos++; + } + const opBody = resourceBody.slice(opStart, opPos - 1); + + // Extract method + const methodMatch = opBody.match(/method: '([A-Z]+)'/); + const method = methodMatch?.[1] ?? ''; + + // Extract path + const pathMatch = opBody.match(/path: '([^']+)'/); + const routePath = pathMatch?.[1] ?? ''; + + // Extract paginated + const paginated = /paginated: true/.test(opBody); + + // Extract wrapperKey + const wrapperKeyMatch = opBody.match(/wrapperKey: '([^']+)'/); + const wrapperKey = wrapperKeyMatch?.[1] ?? null; + + // Extract special + const specialMatch = opBody.match(/special: '([^']+)'/); + const special = specialMatch?.[1] ?? null; + + routes.push({ resource, operation, method, path: routePath, paginated, wrapperKey, special }); + } + } + + return routes; +} + +// ============================================================================ +// Build a lookup from parsed routes +// ============================================================================ + +function buildRoutesMap(routes: ParsedRoute[]): Map { + const map = new Map(); + for (const route of routes) { + map.set(`${route.resource}.${route.operation}`, route); + } + return map; +} + +// ============================================================================ +// Normalize OpenAPI path to match ROUTES path format +// ============================================================================ + +/** + * OpenAPI paths use `{param_name}` notation, ROUTES may use the same or + * slightly different param names. We normalize both to compare the URL + * structure by replacing all `{...}` segments with a placeholder. + */ +function normalizePath(p: string): string { + return p.replace(/\{[^}]+\}/g, '{*}'); +} + +// ============================================================================ +// TESTS +// ============================================================================ + +let specOps: SpecOperation[]; +let parsedRoutes: ParsedRoute[]; +let routesMap: Map; + +beforeAll(() => { + specOps = getSpecOperations(); + parsedRoutes = parseRoutesFromFile(); + routesMap = buildRoutesMap(parsedRoutes); +}); + +describe('ROUTES table vs OpenAPI spec', () => { + describe('every spec endpoint has a ROUTES entry', () => { + it('all OpenAPI endpoints should exist in ROUTES', () => { + const missing: string[] = []; + + for (const spec of specOps) { + const key = `${spec.resource}.${spec.v2Op}`; + if (!routesMap.has(key)) { + missing.push(`${key} (${spec.endpoint.method} ${spec.endpoint.path})`); + } + } + + expect(missing, `Missing ROUTES entries:\n${missing.join('\n')}`).toEqual([]); + }); + }); + + describe('HTTP methods match', () => { + it('each ROUTES entry should have the correct HTTP method from spec', () => { + const mismatches: string[] = []; + + for (const spec of specOps) { + const key = `${spec.resource}.${spec.v2Op}`; + const route = routesMap.get(key); + if (!route) continue; + + // Skip special-only operations whose method/path are placeholders + if (SPECIAL_NON_API_OPS.has(key)) continue; + if (route.special === 'fileUpload') continue; + + const expectedMethod = spec.endpoint.method.toUpperCase(); + if (route.method !== expectedMethod) { + mismatches.push( + `${key}: ROUTES has ${route.method}, spec has ${expectedMethod}`, + ); + } + } + + expect(mismatches, `Method mismatches:\n${mismatches.join('\n')}`).toEqual([]); + }); + }); + + describe('URL paths match', () => { + it('each ROUTES entry should have the correct URL path structure from spec', () => { + const mismatches: string[] = []; + + for (const spec of specOps) { + const key = `${spec.resource}.${spec.v2Op}`; + const route = routesMap.get(key); + if (!route) continue; + + // Skip special-only operations whose path is a placeholder + if (SPECIAL_NON_API_OPS.has(key)) continue; + if (route.special === 'fileUpload') continue; + + const normalizedRoute = normalizePath(route.path); + const normalizedSpec = normalizePath(spec.endpoint.path); + + if (normalizedRoute !== normalizedSpec) { + mismatches.push( + `${key}: ROUTES has "${route.path}" (normalized: "${normalizedRoute}"), spec has "${spec.endpoint.path}" (normalized: "${normalizedSpec}")`, + ); + } + } + + expect(mismatches, `Path mismatches:\n${mismatches.join('\n')}`).toEqual([]); + }); + }); + + describe('pagination flags match', () => { + it('paginated spec endpoints should have paginated: true in ROUTES', () => { + const mismatches: string[] = []; + + for (const spec of specOps) { + const key = `${spec.resource}.${spec.v2Op}`; + const route = routesMap.get(key); + if (!route) continue; + + if (SPECIAL_NON_API_OPS.has(key)) continue; + if (route.special === 'fileUpload') continue; + + if (spec.hasPagination && !route.paginated) { + mismatches.push(`${key}: spec is paginated but ROUTES missing paginated: true`); + } + if (!spec.hasPagination && route.paginated) { + mismatches.push(`${key}: ROUTES has paginated: true but spec has no cursor param`); + } + } + + expect(mismatches, `Pagination mismatches:\n${mismatches.join('\n')}`).toEqual([]); + }); + }); + + describe('wrapperKey matches', () => { + it('body-wrapped spec endpoints should have the correct wrapperKey in ROUTES', () => { + const mismatches: string[] = []; + + for (const spec of specOps) { + const key = `${spec.resource}.${spec.v2Op}`; + const route = routesMap.get(key); + if (!route) continue; + + if (SPECIAL_NON_API_OPS.has(key)) continue; + if (route.special === 'fileUpload') continue; + + // noDataWrapper routes intentionally skip the wrapper + if (route.wrapperKey === null && spec.wrapperKey !== null) { + // Check if noDataWrapper is set — if so, the route intentionally omits the wrapper + // We need to re-check from the parsed route; noDataWrapper means body is sent flat + const fullRoute = parsedRoutes.find(r => r.resource === spec.resource && r.operation === spec.v2Op); + if (fullRoute) { + // Read Router.ts to check noDataWrapper — but we already parsed the fields we need + // For simplicity, skip noDataWrapper routes (export resource) + if (spec.resource === 'export') continue; + } + mismatches.push( + `${key}: spec has wrapperKey "${spec.wrapperKey}" but ROUTES has none`, + ); + } + if (route.wrapperKey !== null && spec.wrapperKey === null) { + mismatches.push( + `${key}: ROUTES has wrapperKey "${route.wrapperKey}" but spec has no wrapper`, + ); + } + if (route.wrapperKey !== null && spec.wrapperKey !== null && route.wrapperKey !== spec.wrapperKey) { + mismatches.push( + `${key}: ROUTES has wrapperKey "${route.wrapperKey}", spec has "${spec.wrapperKey}"`, + ); + } + } + + expect(mismatches, `WrapperKey mismatches:\n${mismatches.join('\n')}`).toEqual([]); + }); + }); + + describe('no orphan routes', () => { + it('every ROUTES entry should correspond to a spec endpoint, a v1 alias, or a special handler', () => { + const specKeys = new Set(specOps.map(s => `${s.resource}.${s.v2Op}`)); + + // Build set of all known v1 alias keys + const v1AliasKeys = new Set(); + for (const [resource, ops] of Object.entries(V1_ALIAS_OPS)) { + for (const op of ops) { + v1AliasKeys.add(`${resource}.${op}`); + } + } + + const orphans: string[] = []; + + for (const route of parsedRoutes) { + const key = `${route.resource}.${route.operation}`; + + // Skip if it matches a spec endpoint + if (specKeys.has(key)) continue; + + // Skip if it's a known v1 alias operation + if (v1AliasKeys.has(key)) continue; + + // Skip if it's a special non-API operation + if (SPECIAL_NON_API_OPS.has(key)) continue; + + orphans.push(`${key} (${route.method} ${route.path})`); + } + + expect(orphans, `Orphan ROUTES entries:\n${orphans.join('\n')}`).toEqual([]); + }); + }); +}); diff --git a/integrations/n8n/tests/trigger.test.ts b/integrations/n8n/tests/trigger.test.ts new file mode 100644 index 00000000..50856d4f --- /dev/null +++ b/integrations/n8n/tests/trigger.test.ts @@ -0,0 +1,507 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import type { IDataObject, IWebhookFunctions } from 'n8n-workflow'; +import * as crypto from 'crypto'; +import { PachcaTrigger } from '../nodes/Pachca/PachcaTrigger.node'; + +// ============================================================================ +// Helpers +// ============================================================================ + +function hmacSha256(body: string, secret: string): string { + const hmac = crypto.createHmac('sha256', secret); + hmac.update(body); + return hmac.digest('hex'); +} + +function freshTimestamp(): number { + return Math.floor(Date.now() / 1000); +} + +interface WebhookCtxOptions { + body?: IDataObject; + headers?: Record; + event?: string; + signingSecret?: string; + webhookAllowedIps?: string; + clientIp?: string; + xForwardedFor?: string; + rawBody?: Buffer | null; + remoteAddress?: string; +} + +function createWebhookCtx(opts: WebhookCtxOptions = {}): IWebhookFunctions { + const body = opts.body ?? { type: 'message', event: 'new', content: 'hello' }; + const headerData = opts.headers ?? {}; + const event = opts.event ?? '*'; + const signingSecret = opts.signingSecret ?? ''; + const webhookAllowedIps = opts.webhookAllowedIps ?? ''; + const rawBody = opts.rawBody !== undefined ? opts.rawBody : null; + const remoteAddress = opts.remoteAddress ?? '127.0.0.1'; + const xForwardedFor = opts.xForwardedFor; + + const requestHeaders: Record = {}; + if (xForwardedFor) requestHeaders['x-forwarded-for'] = xForwardedFor; + + return { + getBodyData: vi.fn(() => body), + getHeaderData: vi.fn(() => headerData), + getCredentials: vi.fn(async () => ({ + baseUrl: 'https://api.pachca.com/api/shared/v1', + accessToken: 'test-token', + signingSecret, + webhookAllowedIps, + })), + getNodeParameter: vi.fn((_name: string) => event), + getRequestObject: vi.fn(() => ({ + headers: requestHeaders, + rawBody, + socket: { remoteAddress }, + })), + helpers: { + returnJsonArray: vi.fn((data: IDataObject) => [{ json: data }]), + }, + } as unknown as IWebhookFunctions; +} + +// ============================================================================ +// Event Filtering +// ============================================================================ + +describe('PachcaTrigger — event filtering', () => { + const trigger = new PachcaTrigger(); + + it('should pass all events when event = "*"', async () => { + const ctx = createWebhookCtx({ + body: { type: 'message', event: 'new', content: 'hi' }, + event: '*', + }); + const result = await trigger.webhook.call(ctx); + expect(result.workflowData).toBeDefined(); + }); + + it('should pass matching event (new_message)', async () => { + const ctx = createWebhookCtx({ + body: { type: 'message', event: 'new', content: 'hi' }, + event: 'new_message', + }); + const result = await trigger.webhook.call(ctx); + expect(result.workflowData).toBeDefined(); + }); + + it('should filter non-matching event', async () => { + const ctx = createWebhookCtx({ + body: { type: 'message', event: 'new', content: 'hi' }, + event: 'message_deleted', // expects type: message, event: delete + }); + const result = await trigger.webhook.call(ctx); + expect(result.webhookResponse).toBe('Event filtered'); + expect(result.workflowData).toBeUndefined(); + }); + + it('should pass button_pressed event', async () => { + const ctx = createWebhookCtx({ + body: { type: 'button', event: 'click', data: 'btn1' }, + event: 'button_pressed', + }); + const result = await trigger.webhook.call(ctx); + expect(result.workflowData).toBeDefined(); + }); + + it('should pass form_submitted event', async () => { + const ctx = createWebhookCtx({ + body: { type: 'view', event: 'submit', callback_id: 'form1' }, + event: 'form_submitted', + }); + const result = await trigger.webhook.call(ctx); + expect(result.workflowData).toBeDefined(); + }); + + it('should filter when body type mismatches', async () => { + const ctx = createWebhookCtx({ + body: { type: 'reaction', event: 'new' }, + event: 'new_message', // expects type: message + }); + const result = await trigger.webhook.call(ctx); + expect(result.webhookResponse).toBe('Event filtered'); + }); + + it('should pass company_member events', async () => { + const ctx = createWebhookCtx({ + body: { type: 'company_member', event: 'invite', user_id: 1 }, + event: 'company_member_invite', + }); + const result = await trigger.webhook.call(ctx); + expect(result.workflowData).toBeDefined(); + }); +}); + +// ============================================================================ +// Signature Verification +// ============================================================================ + +describe('PachcaTrigger — signature verification', () => { + const trigger = new PachcaTrigger(); + const secret = 'my-webhook-secret'; + + it('should accept valid signature (rawBody)', async () => { + const bodyObj = { type: 'message', event: 'new', content: 'hi', webhook_timestamp: freshTimestamp() }; + const rawBodyStr = JSON.stringify(bodyObj); + const sig = hmacSha256(rawBodyStr, secret); + + const ctx = createWebhookCtx({ + body: bodyObj, + headers: { 'pachca-signature': sig }, + signingSecret: secret, + rawBody: Buffer.from(rawBodyStr), + }); + const result = await trigger.webhook.call(ctx); + expect(result.workflowData).toBeDefined(); + }); + + it('should reject invalid signature', async () => { + const bodyObj = { type: 'message', event: 'new', webhook_timestamp: freshTimestamp() }; + const ctx = createWebhookCtx({ + body: bodyObj, + headers: { 'pachca-signature': 'deadbeef'.repeat(8) }, + signingSecret: secret, + rawBody: Buffer.from(JSON.stringify(bodyObj)), + }); + const result = await trigger.webhook.call(ctx); + expect(result.webhookResponse).toBe('Rejected'); + }); + + it('should reject missing signature when secret is configured', async () => { + const ctx = createWebhookCtx({ + body: { type: 'message', event: 'new', webhook_timestamp: freshTimestamp() }, + headers: {}, // no signature header + signingSecret: secret, + }); + const result = await trigger.webhook.call(ctx); + expect(result.webhookResponse).toBe('Rejected'); + }); + + it('should skip verification when no signing secret', async () => { + const ctx = createWebhookCtx({ + body: { type: 'message', event: 'new' }, + headers: {}, // no signature + signingSecret: '', + }); + const result = await trigger.webhook.call(ctx); + expect(result.workflowData).toBeDefined(); + }); + + it('should use JSON.stringify fallback when rawBody is missing', async () => { + const bodyObj = { type: 'message', event: 'new', webhook_timestamp: freshTimestamp() }; + const sig = hmacSha256(JSON.stringify(bodyObj), secret); + + const ctx = createWebhookCtx({ + body: bodyObj, + headers: { 'pachca-signature': sig }, + signingSecret: secret, + rawBody: null, + }); + const result = await trigger.webhook.call(ctx); + expect(result.workflowData).toBeDefined(); + }); +}); + +// ============================================================================ +// IP Allowlist +// ============================================================================ + +describe('PachcaTrigger — IP allowlist', () => { + const trigger = new PachcaTrigger(); + + it('should allow request from allowed IP', async () => { + const ctx = createWebhookCtx({ + webhookAllowedIps: '37.200.70.177', + remoteAddress: '37.200.70.177', + }); + const result = await trigger.webhook.call(ctx); + expect(result.workflowData).toBeDefined(); + }); + + it('should reject request from disallowed IP', async () => { + const ctx = createWebhookCtx({ + webhookAllowedIps: '37.200.70.177', + remoteAddress: '1.2.3.4', + }); + const result = await trigger.webhook.call(ctx); + expect(result.webhookResponse).toBe('Forbidden'); + }); + + it('should normalize IPv6-mapped IPv4 (::ffff:)', async () => { + const ctx = createWebhookCtx({ + webhookAllowedIps: '37.200.70.177', + remoteAddress: '::ffff:37.200.70.177', + }); + const result = await trigger.webhook.call(ctx); + expect(result.workflowData).toBeDefined(); + }); + + it('should use x-forwarded-for when present', async () => { + const ctx = createWebhookCtx({ + webhookAllowedIps: '37.200.70.177', + xForwardedFor: '37.200.70.177, 10.0.0.1', + remoteAddress: '10.0.0.1', + }); + const result = await trigger.webhook.call(ctx); + expect(result.workflowData).toBeDefined(); + }); + + it('should allow all when no IPs configured', async () => { + const ctx = createWebhookCtx({ + webhookAllowedIps: '', + remoteAddress: '99.99.99.99', + }); + const result = await trigger.webhook.call(ctx); + expect(result.workflowData).toBeDefined(); + }); + + it('should support multiple allowed IPs', async () => { + const ctx = createWebhookCtx({ + webhookAllowedIps: '10.0.0.1, 37.200.70.177, 192.168.1.1', + remoteAddress: '192.168.1.1', + }); + const result = await trigger.webhook.call(ctx); + expect(result.workflowData).toBeDefined(); + }); +}); + +// ============================================================================ +// Replay Protection +// ============================================================================ + +describe('PachcaTrigger — replay protection', () => { + const trigger = new PachcaTrigger(); + + it('should accept fresh timestamp', async () => { + const ctx = createWebhookCtx({ + body: { type: 'message', event: 'new', webhook_timestamp: freshTimestamp() }, + }); + const result = await trigger.webhook.call(ctx); + expect(result.workflowData).toBeDefined(); + }); + + it('should reject old timestamp (> 5 minutes)', async () => { + const oldTs = Math.floor(Date.now() / 1000) - 6 * 60; // 6 minutes ago + const ctx = createWebhookCtx({ + body: { type: 'message', event: 'new', webhook_timestamp: oldTs }, + }); + const result = await trigger.webhook.call(ctx); + expect(result.webhookResponse).toBe('Rejected'); + }); + + it('should reject far-future timestamp (> 1 minute ahead)', async () => { + const futureTs = Math.floor(Date.now() / 1000) + 2 * 60; // 2 minutes in the future + const ctx = createWebhookCtx({ + body: { type: 'message', event: 'new', webhook_timestamp: futureTs }, + }); + const result = await trigger.webhook.call(ctx); + expect(result.webhookResponse).toBe('Rejected'); + }); + + it('should accept slightly-future timestamp (within 1 minute)', async () => { + const nearFutureTs = Math.floor(Date.now() / 1000) + 30; // 30 seconds ahead + const ctx = createWebhookCtx({ + body: { type: 'message', event: 'new', webhook_timestamp: nearFutureTs }, + }); + const result = await trigger.webhook.call(ctx); + expect(result.workflowData).toBeDefined(); + }); + + it('should skip check when no timestamp in body', async () => { + const ctx = createWebhookCtx({ + body: { type: 'message', event: 'new' }, // no webhook_timestamp + }); + const result = await trigger.webhook.call(ctx); + expect(result.workflowData).toBeDefined(); + }); + + it('should accept timestamp exactly at 5-minute boundary', async () => { + // 4 minutes 59 seconds ago — should pass + const ts = Math.floor(Date.now() / 1000) - 4 * 60 - 59; + const ctx = createWebhookCtx({ + body: { type: 'message', event: 'new', webhook_timestamp: ts }, + }); + const result = await trigger.webhook.call(ctx); + expect(result.workflowData).toBeDefined(); + }); +}); + +// ============================================================================ +// Combined scenarios +// ============================================================================ + +describe('PachcaTrigger — combined checks', () => { + const trigger = new PachcaTrigger(); + const secret = 'test-secret'; + + it('IP check runs before signature check', async () => { + // Blocked IP — should get Forbidden even without signature + const ctx = createWebhookCtx({ + webhookAllowedIps: '10.0.0.1', + remoteAddress: '99.99.99.99', + signingSecret: secret, + headers: {}, // no signature + }); + const result = await trigger.webhook.call(ctx); + expect(result.webhookResponse).toBe('Forbidden'); + }); + + it('signature check runs before replay check', async () => { + const oldTs = Math.floor(Date.now() / 1000) - 10 * 60; + const bodyObj = { type: 'message', event: 'new', webhook_timestamp: oldTs }; + const ctx = createWebhookCtx({ + body: bodyObj, + headers: { 'pachca-signature': 'invalid' }, + signingSecret: secret, + rawBody: Buffer.from(JSON.stringify(bodyObj)), + }); + const result = await trigger.webhook.call(ctx); + // Should be Rejected (sig fails), not Rejected from replay + expect(result.webhookResponse).toBe('Rejected'); + }); + + it('full valid request: IP + signature + fresh timestamp + matching event', async () => { + const bodyObj = { type: 'message', event: 'new', content: 'hi', webhook_timestamp: freshTimestamp() }; + const rawBodyStr = JSON.stringify(bodyObj); + const sig = hmacSha256(rawBodyStr, secret); + + const ctx = createWebhookCtx({ + body: bodyObj, + headers: { 'pachca-signature': sig }, + event: 'new_message', + signingSecret: secret, + webhookAllowedIps: '37.200.70.177', + remoteAddress: '37.200.70.177', + rawBody: Buffer.from(rawBodyStr), + }); + const result = await trigger.webhook.call(ctx); + expect(result.workflowData).toBeDefined(); + }); +}); + +// ============================================================================ +// resolveResourceLocator null/undefined guard +// ============================================================================ + +describe('resolveResourceLocator — null/undefined guard', () => { + // Imported separately to test the fix + let resolveResourceLocator: typeof import('../nodes/Pachca/GenericFunctions').resolveResourceLocator; + + beforeEach(async () => { + const mod = await import('../nodes/Pachca/GenericFunctions'); + resolveResourceLocator = mod.resolveResourceLocator; + }); + + function createCtx(params: Record) { + return { + getNodeParameter: vi.fn((name: string) => { + if (name in params) return params[name]; + throw new Error(`Missing parameter: ${name}`); + }), + getNode: vi.fn(() => ({ id: 'test', name: 'Test', type: 'test', typeVersion: 2, position: [0, 0], parameters: {} })), + } as unknown as import('n8n-workflow').IExecuteFunctions; + } + + it('should throw on null value', () => { + const ctx = createCtx({ id: null }); + expect(() => resolveResourceLocator(ctx, 'id', 0)).toThrow('empty'); + }); + + it('should throw on undefined value', () => { + const ctx = createCtx({ id: undefined }); + expect(() => resolveResourceLocator(ctx, 'id', 0)).toThrow('empty'); + }); + + it('should throw on empty string value', () => { + const ctx = createCtx({ id: '' }); + expect(() => resolveResourceLocator(ctx, 'id', 0)).toThrow('empty'); + }); + + it('should throw on empty ResourceLocator value', () => { + const ctx = createCtx({ id: { __rl: true, value: '', mode: 'id' } }); + expect(() => resolveResourceLocator(ctx, 'id', 0)).toThrow('empty'); + }); + + it('should pass valid number', () => { + const ctx = createCtx({ id: 42 }); + expect(resolveResourceLocator(ctx, 'id', 0)).toBe(42); + }); + + it('should pass valid ResourceLocator', () => { + const ctx = createCtx({ id: { __rl: true, value: 123, mode: 'id' } }); + expect(resolveResourceLocator(ctx, 'id', 0)).toBe(123); + }); +}); + +// ============================================================================ +// splitAndValidateCommaList — float rejection +// ============================================================================ + +describe('splitAndValidateCommaList — float rejection', () => { + let splitAndValidateCommaList: typeof import('../nodes/Pachca/GenericFunctions').splitAndValidateCommaList; + + beforeEach(async () => { + const mod = await import('../nodes/Pachca/GenericFunctions'); + splitAndValidateCommaList = mod.splitAndValidateCommaList; + }); + + function createCtx() { + return { + getNode: vi.fn(() => ({ id: 'test', name: 'Test', type: 'test', typeVersion: 2, position: [0, 0], parameters: {} })), + } as unknown as import('n8n-workflow').IExecuteFunctions; + } + + it('should reject floats in int mode', () => { + const ctx = createCtx(); + expect(() => splitAndValidateCommaList(ctx, '1, 3.14, 5', 'IDs', 'int', 0)).toThrow('Invalid'); + }); + + it('should accept valid integers', () => { + const ctx = createCtx(); + expect(splitAndValidateCommaList(ctx, '1, 2, 3', 'IDs', 'int', 0)).toEqual([1, 2, 3]); + }); + + it('should reject mixed valid/invalid', () => { + const ctx = createCtx(); + expect(() => splitAndValidateCommaList(ctx, '1, abc, 3', 'IDs', 'int', 0)).toThrow('abc'); + }); +}); + +// ============================================================================ +// buildMultipartBody — filename sanitization +// ============================================================================ + +describe('buildMultipartBody — filename sanitization', () => { + let buildMultipartBody: typeof import('../nodes/Pachca/GenericFunctions').buildMultipartBody; + + beforeEach(async () => { + const mod = await import('../nodes/Pachca/GenericFunctions'); + buildMultipartBody = mod.buildMultipartBody; + }); + + it('should strip null bytes from filename', () => { + const result = buildMultipartBody({}, Buffer.from('test'), 'file\x00name.txt', 'text/plain'); + const bodyStr = result.body.toString(); + expect(bodyStr).not.toContain('\x00'); + expect(bodyStr).toContain('file_name.txt'); + }); + + it('should strip control characters from filename', () => { + const result = buildMultipartBody({}, Buffer.from('test'), 'file\x07\x1fname.txt', 'text/plain'); + const bodyStr = result.body.toString(); + expect(bodyStr).toContain('file__name.txt'); + }); + + it('should truncate long filenames to 255 chars', () => { + const longName = 'a'.repeat(300) + '.txt'; + const result = buildMultipartBody({}, Buffer.from('test'), longName, 'text/plain'); + const bodyStr = result.body.toString(); + // The filename in the body should not exceed 255 chars + const match = bodyStr.match(/filename="([^"]+)"/); + expect(match).toBeTruthy(); + expect(match![1].length).toBeLessThanOrEqual(255); + }); +}); diff --git a/integrations/n8n/tests/webhook-lifecycle.test.ts b/integrations/n8n/tests/webhook-lifecycle.test.ts new file mode 100644 index 00000000..73266f4c --- /dev/null +++ b/integrations/n8n/tests/webhook-lifecycle.test.ts @@ -0,0 +1,254 @@ +import { describe, it, expect, vi } from 'vitest'; +import type { IDataObject, IHookFunctions } from 'n8n-workflow'; +import { PachcaTrigger } from '../nodes/Pachca/PachcaTrigger.node'; + +// ============================================================================ +// Helpers +// ============================================================================ + +const BASE_URL = 'https://api.pachca.com/api/shared/v1'; +const WEBHOOK_URL = 'https://n8n.example.com/webhook/abc123'; + +function createHookCtx(overrides: { + botId?: number; + httpResponses?: unknown[]; + resolveBotResult?: number; + resolveBotError?: Error; +} = {}): IHookFunctions { + let callIndex = 0; + const responses = overrides.httpResponses ?? [{}]; + + return { + getCredentials: vi.fn(async () => ({ + baseUrl: BASE_URL, + accessToken: 'test-token', + botId: overrides.botId ?? 0, + })), + getNodeWebhookUrl: vi.fn(() => WEBHOOK_URL), + helpers: { + httpRequestWithAuthentication: vi.fn(async () => { + const idx = callIndex++; + const resp = responses[idx % responses.length]; + if (resp instanceof Error) throw resp; + return resp; + }), + }, + logger: { + warn: vi.fn(), + info: vi.fn(), + error: vi.fn(), + }, + } as unknown as IHookFunctions; +} + +// ============================================================================ +// checkExists +// ============================================================================ + +describe('PachcaTrigger.webhookMethods.checkExists', () => { + const trigger = new PachcaTrigger(); + + it('should return true when webhook URL matches', async () => { + const ctx = createHookCtx({ + botId: 42, + httpResponses: [ + { data: { webhook: { outgoing_url: WEBHOOK_URL } } }, + ], + }); + + const result = await trigger.webhookMethods.default.checkExists.call(ctx); + expect(result).toBe(true); + }); + + it('should return false when webhook URL does not match', async () => { + const ctx = createHookCtx({ + botId: 42, + httpResponses: [ + { data: { webhook: { outgoing_url: 'https://other.example.com/webhook' } } }, + ], + }); + + const result = await trigger.webhookMethods.default.checkExists.call(ctx); + expect(result).toBe(false); + }); + + it('should return false when no webhook is configured', async () => { + const ctx = createHookCtx({ + botId: 42, + httpResponses: [{ data: {} }], + }); + + const result = await trigger.webhookMethods.default.checkExists.call(ctx); + expect(result).toBe(false); + }); + + it('should return false when bot ID is 0 (personal token)', async () => { + // botId=0 + token/info returns personal token + const ctx = createHookCtx({ + httpResponses: [ + { data: { name: 'My Token', user_id: 99 } }, // resolveBotId → 0 + ], + }); + + const result = await trigger.webhookMethods.default.checkExists.call(ctx); + expect(result).toBe(false); + // Should not attempt GET /bots/... + const httpMock = ctx.helpers.httpRequestWithAuthentication as ReturnType; + expect(httpMock.mock.calls).toHaveLength(1); // only token/info + }); + + it('should return false on network error during resolveBotId', async () => { + const ctx = createHookCtx({ + httpResponses: [new Error('Network timeout')], + }); + + const result = await trigger.webhookMethods.default.checkExists.call(ctx); + expect(result).toBe(false); + }); + + it('should return false on network error during bot GET', async () => { + const ctx = createHookCtx({ + botId: 42, + httpResponses: [new Error('Connection refused')], + }); + + const result = await trigger.webhookMethods.default.checkExists.call(ctx); + expect(result).toBe(false); + }); + + it('should call correct URL with bot ID', async () => { + const ctx = createHookCtx({ + botId: 77, + httpResponses: [ + { data: { webhook: { outgoing_url: WEBHOOK_URL } } }, + ], + }); + + await trigger.webhookMethods.default.checkExists.call(ctx); + const httpMock = ctx.helpers.httpRequestWithAuthentication as ReturnType; + const callArgs = httpMock.mock.calls[0]; + expect(callArgs[1].url).toBe(`${BASE_URL}/bots/77`); + expect(callArgs[1].method).toBe('GET'); + }); +}); + +// ============================================================================ +// create +// ============================================================================ + +describe('PachcaTrigger.webhookMethods.create', () => { + const trigger = new PachcaTrigger(); + + it('should register webhook via PUT /bots/:id', async () => { + const ctx = createHookCtx({ + botId: 42, + httpResponses: [{ data: {} }], + }); + + const result = await trigger.webhookMethods.default.create.call(ctx); + expect(result).toBe(true); + + const httpMock = ctx.helpers.httpRequestWithAuthentication as ReturnType; + expect(httpMock).toHaveBeenCalledWith( + 'pachcaApi', + expect.objectContaining({ + method: 'PUT', + url: `${BASE_URL}/bots/42`, + body: { bot: { webhook: { outgoing_url: WEBHOOK_URL } } }, + }), + ); + }); + + it('should warn and return true for personal token (no bot ID)', async () => { + const ctx = createHookCtx({ + // botId=0, token/info returns personal token + httpResponses: [ + { data: { name: 'My Token', user_id: 99 } }, // resolveBotId → 0 + ], + }); + + const result = await trigger.webhookMethods.default.create.call(ctx); + expect(result).toBe(true); + expect((ctx.logger as { warn: ReturnType }).warn).toHaveBeenCalledWith( + expect.stringContaining('not a bot token'), + ); + }); + + it('should auto-detect bot ID from token/info', async () => { + const ctx = createHookCtx({ + // botId=0, token/info returns bot token + httpResponses: [ + { data: { name: null, user_id: 55 } }, // resolveBotId → 55 + { data: {} }, // PUT /bots/55 + ], + }); + + const result = await trigger.webhookMethods.default.create.call(ctx); + expect(result).toBe(true); + + const httpMock = ctx.helpers.httpRequestWithAuthentication as ReturnType; + // Second call should be PUT /bots/55 + expect(httpMock.mock.calls[1][1].url).toBe(`${BASE_URL}/bots/55`); + }); +}); + +// ============================================================================ +// delete +// ============================================================================ + +describe('PachcaTrigger.webhookMethods.delete', () => { + const trigger = new PachcaTrigger(); + + it('should clear webhook via PUT /bots/:id with empty URL', async () => { + const ctx = createHookCtx({ + botId: 42, + httpResponses: [{ data: {} }], + }); + + const result = await trigger.webhookMethods.default.delete.call(ctx); + expect(result).toBe(true); + + const httpMock = ctx.helpers.httpRequestWithAuthentication as ReturnType; + expect(httpMock).toHaveBeenCalledWith( + 'pachcaApi', + expect.objectContaining({ + method: 'PUT', + url: `${BASE_URL}/bots/42`, + body: { bot: { webhook: { outgoing_url: '' } } }, + }), + ); + }); + + it('should return true when resolveBotId throws', async () => { + const ctx = createHookCtx({ + httpResponses: [new Error('Network timeout')], + }); + + const result = await trigger.webhookMethods.default.delete.call(ctx); + expect(result).toBe(true); + }); + + it('should return true when bot ID is 0', async () => { + const ctx = createHookCtx({ + httpResponses: [ + { data: { name: 'Personal Token', user_id: 1 } }, // → 0 + ], + }); + + const result = await trigger.webhookMethods.default.delete.call(ctx); + expect(result).toBe(true); + // Should not attempt PUT + const httpMock = ctx.helpers.httpRequestWithAuthentication as ReturnType; + expect(httpMock.mock.calls).toHaveLength(1); // only token/info + }); + + it('should ignore PUT errors on cleanup', async () => { + const ctx = createHookCtx({ + botId: 42, + httpResponses: [new Error('Server error')], + }); + + const result = await trigger.webhookMethods.default.delete.call(ctx); + expect(result).toBe(true); + }); +}); diff --git a/integrations/n8n/tsconfig.json b/integrations/n8n/tsconfig.json new file mode 100644 index 00000000..0a04b270 --- /dev/null +++ b/integrations/n8n/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "es2019", + "module": "commonjs", + "moduleResolution": "node", + "strict": true, + "strictNullChecks": true, + "noImplicitAny": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "outDir": "./dist/", + "declaration": true, + "sourceMap": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true + }, + "include": [ + "credentials/**/*", + "nodes/**/*", + "nodes/**/*.json" + ], + "exclude": [ + "node_modules", + "dist", + "scripts", + "tests", + "e2e" + ] +} diff --git a/package.json b/package.json index 7e37c22d..4bd69f87 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,17 @@ "apps/*", "apps/*/*", "packages/*", - "sdk/*" + "sdk/*", + "integrations/*" ], "devDependencies": { - "turbo": "^2.8.1" - } + "turbo": "^2.8.1", + "@playwright/test": "^1.50.0" + }, + "trustedDependencies": [ + "esbuild", + "sharp", + "unrs-resolver", + "vue-demi" + ] } diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 3273cb23..0ba5e06c 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 2026.4.0 (7 апреля 2026) + +- **Добавлено** (profile update-avatar): Загрузка аватара профиля +- **Добавлено** (profile delete-avatar): Удаление аватара профиля +- **Добавлено** (users update-avatar): Загрузка аватара сотрудника +- **Добавлено** (users remove-avatar): Удаление аватара сотрудника +- **Изменено** (chats list, messages list): Исправлена сортировка: --order без --sort теперь корректно работает (sort по умолчанию id, order по умолчанию desc) +- **Изменено** (messages list): chat_id можно передать первым аргументом: pachca messages list 12345 + ## 2026.3.10 (21 марта 2026) - **Изменено** (messages create): Поле link_preview перемещено на верхний уровень тела запроса (раньше было внутри message) diff --git a/packages/cli/README.md b/packages/cli/README.md index a1a03378..2ac5d674 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -171,6 +171,8 @@ pachca guide # список всех сценариев |---------|---------| | `pachca profile get-info` | Информация о токене | | `pachca profile get` | Информация о профиле | +| `pachca profile update-avatar` | Загрузка аватара | +| `pachca profile delete-avatar` | Удаление аватара | | `pachca profile get-status` | Текущий статус | | `pachca profile update-status` | Новый статус | | `pachca profile delete-status` | Удаление статуса | @@ -202,6 +204,8 @@ pachca guide # список всех сценариев | `pachca users get` | Информация о сотруднике | | `pachca users update` | Редактирование сотрудника | | `pachca users delete` | Удаление сотрудника | +| `pachca users update-avatar` | Загрузка аватара сотрудника | +| `pachca users remove-avatar` | Удаление аватара сотрудника | | `pachca users get-status` | Статус сотрудника | | `pachca users update-status` | Новый статус сотрудника | | `pachca users remove-status` | Удаление статуса сотрудника | diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index 9e8a35de..3e302f3d 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -1 +1 @@ -{"commands":{"api":{"aliases":[],"args":{"method":{"description":"HTTP method (GET, POST, PUT, DELETE)","name":"method","options":["GET","POST","PUT","DELETE","PATCH"],"required":true},"path":{"description":"API path (e.g., /messages)","name":"path","required":true}},"description":"Произвольный запрос к API","examples":["<%= config.bin %> api GET /messages --query chat_id=123","<%= config.bin %> api POST /messages -F message[chat_id]=12345 -f message[content]=\"Привет\"","<%= config.bin %> api POST /messages --input payload.json","<%= config.bin %> api GET /profile -o yaml"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"raw-field":{"char":"f","description":"String field (key=value)","name":"raw-field","hasDynamicHelp":false,"multiple":true,"type":"option"},"field":{"char":"F","description":"Typed field (numbers/bools auto-converted, @file reads file)","name":"field","hasDynamicHelp":false,"multiple":true,"type":"option"},"input":{"description":"JSON file to send as body (- for stdin)","name":"input","hasDynamicHelp":false,"multiple":false,"type":"option"},"query":{"description":"Query parameter (key=value)","name":"query","hasDynamicHelp":false,"multiple":true,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"api","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":false,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","api.js"]},"auth:list":{"aliases":[],"args":{},"description":"Список сохранённых профилей","examples":["<%= config.bin %> auth list","<%= config.bin %> auth list -o json"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"auth:list","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","auth","list.js"]},"auth:login":{"aliases":[],"args":{},"description":"Авторизация и сохранение токена","examples":["<%= config.bin %> auth login","<%= config.bin %> auth login --profile personal","<%= config.bin %> auth login --profile ci --token $PACHCA_TOKEN"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile name (default: \"default\")","name":"profile","default":"default","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Token to save (skips prompt)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"auth:login","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","auth","login.js"]},"auth:logout":{"aliases":[],"args":{"profile":{"description":"Profile name to remove","name":"profile","required":false}},"description":"Удаление сохранённого профиля","examples":["<%= config.bin %> auth logout bot-notify","<%= config.bin %> auth logout"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"auth:logout","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","auth","logout.js"]},"auth:status":{"aliases":[],"args":{},"description":"Статус текущего профиля","examples":["<%= config.bin %> auth status","<%= config.bin %> auth status -o json"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"auth:status","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","auth","status.js"]},"auth:switch":{"aliases":[],"args":{"profile":{"description":"Profile name to switch to","name":"profile","required":true}},"description":"Переключение активного профиля","examples":["<%= config.bin %> auth switch bot-support"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"auth:switch","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","auth","switch.js"]},"bots:list-events":{"aliases":[],"args":{},"description":"История событий","examples":["Обработка событий через историю (polling):\n $ pachca bots list-events"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"bots:list-events","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"webhooks:events:read","apiMethod":"GET","apiPath":"/webhooks/events","defaultColumns":["id","created_at","event_type","payload"],"isESM":true,"relativePath":["dist","commands","bots","list-events.js"]},"bots:remove-event":{"aliases":[],"args":{"id":{"description":"Идентификатор события (pachca bots list)","name":"id","required":true}},"description":"Удаление события","examples":["Обработка событий через историю (polling):\n $ pachca bots remove-event"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"bots:remove-event","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"webhooks:events:delete","apiMethod":"DELETE","apiPath":"/webhooks/events/{id}","isESM":true,"relativePath":["dist","commands","bots","remove-event.js"]},"bots:update":{"aliases":[],"args":{"id":{"description":"Идентификатор бота (pachca bots list)","name":"id","required":true}},"description":"Редактирование бота","examples":["Обновить Webhook URL бота:\n $ pachca bots update"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"webhook":{"description":"Объект параметров вебхука","name":"webhook","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"bots:update","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"bots:write","apiMethod":"PUT","apiPath":"/bots/{id}","defaultColumns":["id"],"requiredFlags":["webhook"],"isESM":true,"relativePath":["dist","commands","bots","update.js"]},"changelog":{"aliases":[],"args":{},"description":"История изменений CLI","examples":["<%= config.bin %> changelog","<%= config.bin %> changelog -o json"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"changelog","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","changelog.js"]},"chats:archive":{"aliases":[],"args":{"id":{"description":"Идентификатор чата (pachca chats list)","name":"id","required":true}},"description":"Архивация чата","examples":["Архивация и управление чатом:\n $ pachca chats archive","Найти и заархивировать неактивные чаты:\n $ pachca chats archive"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"chats:archive","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chats:archive","apiMethod":"PUT","apiPath":"/chats/{id}/archive","isESM":true,"relativePath":["dist","commands","chats","archive.js"]},"chats:create":{"aliases":[],"args":{},"description":"Новый чат","examples":["Создать канал и пригласить участников:\n $ pachca chats create","Создать проектную беседу из шаблона:\n $ pachca chats create","Найти активные чаты за период:\n $ pachca chats list"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"name":{"description":"Название","name":"name","hasDynamicHelp":false,"multiple":false,"type":"option"},"member-ids":{"description":"Массив идентификаторов пользователей, которые станут участниками","name":"member-ids","hasDynamicHelp":false,"multiple":false,"type":"option"},"group-tag-ids":{"description":"Массив идентификаторов тегов, которые станут участниками","name":"group-tag-ids","hasDynamicHelp":false,"multiple":false,"type":"option"},"channel":{"description":"Является каналом","name":"channel","allowNo":true,"type":"boolean"},"public":{"description":"Открытый доступ","name":"public","allowNo":true,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"chats:create","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chats:create","apiMethod":"POST","apiPath":"/chats","defaultColumns":["id","name","created_at","owner_id","channel"],"requiredFlags":["name"],"isESM":true,"relativePath":["dist","commands","chats","create.js"]},"chats:get":{"aliases":[],"args":{"id":{"description":"Идентификатор чата (pachca chats list)","name":"id","required":true}},"description":"Информация о чате","examples":["Переименовать или обновить чат:\n $ pachca chats update"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"chats:get","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chats:read","apiMethod":"GET","apiPath":"/chats/{id}","defaultColumns":["id","name","created_at","owner_id","channel"],"isESM":true,"relativePath":["dist","commands","chats","get.js"]},"chats:list":{"aliases":[],"args":{},"description":"Список чатов","examples":["Создать канал и пригласить участников:\n $ pachca chats create","Создать проектную беседу из шаблона:\n $ pachca chats create","Найти активные чаты за период:\n $ pachca chats list"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"sort":{"description":"Поле сортировки (id — идентификатор чата, last-message-at — дата и время создания последнего сообщения)","name":"sort","hasDynamicHelp":false,"multiple":false,"options":["id","last-message-at"],"type":"option"},"order":{"description":"Порядок сортировки","name":"order","hasDynamicHelp":false,"multiple":false,"options":["asc","desc"],"type":"option"},"availability":{"description":"Параметр, который отвечает за доступность и выборку чатов для пользователя","name":"availability","hasDynamicHelp":false,"multiple":false,"type":"option"},"last-message-at-after":{"description":"Фильтрация по времени создания последнего сообщения. Будут возвращены те чаты, время последнего созданного сообщения в которых не раньше чем указанное (в формате YYYY-MM-DDThh:mm:ss.sssZ).","name":"last-message-at-after","hasDynamicHelp":false,"multiple":false,"type":"option"},"last-message-at-before":{"description":"Фильтрация по времени создания последнего сообщения. Будут возвращены те чаты, время последнего созданного сообщения в которых не позже чем указанное (в формате YYYY-MM-DDThh:mm:ss.sssZ).","name":"last-message-at-before","hasDynamicHelp":false,"multiple":false,"type":"option"},"personal":{"description":"Фильтрация по личным и групповым чатам. Если параметр не указан, возвращаются любые чаты.","name":"personal","allowNo":true,"type":"boolean"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"chats:list","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chats:read","apiMethod":"GET","apiPath":"/chats","defaultColumns":["id","name","created_at","owner_id","channel"],"isESM":true,"relativePath":["dist","commands","chats","list.js"]},"chats:unarchive":{"aliases":[],"args":{"id":{"description":"Идентификатор чата (pachca chats list)","name":"id","required":true}},"description":"Разархивация чата","examples":["Архивация и управление чатом:\n $ pachca chats unarchive"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"chats:unarchive","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chats:archive","apiMethod":"PUT","apiPath":"/chats/{id}/unarchive","isESM":true,"relativePath":["dist","commands","chats","unarchive.js"]},"chats:update":{"aliases":[],"args":{"id":{"description":"Идентификатор чата (pachca chats list)","name":"id","required":true}},"description":"Обновление чата","examples":["Переименовать или обновить чат:\n $ pachca chats update"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"name":{"description":"Название","name":"name","hasDynamicHelp":false,"multiple":false,"type":"option"},"public":{"description":"Открытый доступ","name":"public","allowNo":true,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"chats:update","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chats:update","apiMethod":"PUT","apiPath":"/chats/{id}","defaultColumns":["id","name","created_at","owner_id","channel"],"isESM":true,"relativePath":["dist","commands","chats","update.js"]},"commands":{"aliases":[],"args":{},"description":"Список всех команд","examples":["<%= config.bin %> commands","<%= config.bin %> commands --available","<%= config.bin %> commands --available -o json"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"available":{"description":"Show only commands available to current token","name":"available","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"commands","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","commands.js"]},"common:custom-properties":{"aliases":[],"args":{},"description":"Список дополнительных полей","examples":["Получить кастомные поля профиля:\n $ pachca common custom-properties"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"entity-type":{"description":"Тип сущности","name":"entity-type","hasDynamicHelp":false,"multiple":false,"options":["User","Task"],"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"common:custom-properties","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"custom_properties:read","apiMethod":"GET","apiPath":"/custom_properties","defaultColumns":["id","name","data_type"],"requiredFlags":["entity-type"],"isESM":true,"relativePath":["dist","commands","common","custom-properties.js"]},"common:direct-url":{"aliases":[],"args":{},"description":"Загрузка файла","flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"direct-url":{"description":"URL для отправки запроса (получается из ответа POST /uploads)","name":"direct-url","required":true,"hasDynamicHelp":false,"multiple":false,"type":"option"},"content-disposition":{"description":"Параметр Content-Disposition, полученный в ответе на запрос [Получение подписи, ключа и других параметров](POST /uploads)","name":"content-disposition","hasDynamicHelp":false,"multiple":false,"type":"option"},"acl":{"description":"Параметр acl, полученный в ответе на запрос [Получение подписи, ключа и других параметров](POST /uploads)","name":"acl","hasDynamicHelp":false,"multiple":false,"type":"option"},"policy":{"description":"Параметр policy, полученный в ответе на запрос [Получение подписи, ключа и других параметров](POST /uploads)","name":"policy","hasDynamicHelp":false,"multiple":false,"type":"option"},"x-amz-credential":{"description":"Параметр x-amz-credential, полученный в ответе на запрос [Получение подписи, ключа и других параметров](POST /uploads)","name":"x-amz-credential","hasDynamicHelp":false,"multiple":false,"type":"option"},"x-amz-algorithm":{"description":"Параметр x-amz-algorithm, полученный в ответе на запрос [Получение подписи, ключа и других параметров](POST /uploads)","name":"x-amz-algorithm","hasDynamicHelp":false,"multiple":false,"type":"option"},"x-amz-date":{"description":"Параметр x-amz-date, полученный в ответе на запрос [Получение подписи, ключа и других параметров](POST /uploads)","name":"x-amz-date","hasDynamicHelp":false,"multiple":false,"type":"option"},"x-amz-signature":{"description":"Параметр x-amz-signature, полученный в ответе на запрос [Получение подписи, ключа и других параметров](POST /uploads)","name":"x-amz-signature","hasDynamicHelp":false,"multiple":false,"type":"option"},"key":{"description":"Параметр key, полученный в ответе на запрос [Получение подписи, ключа и других параметров](POST /uploads)","name":"key","hasDynamicHelp":false,"multiple":false,"type":"option"},"file":{"description":"Файл для загрузки","name":"file","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"common:direct-url","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"apiMethod":"POST","apiPath":"/direct_url","requiredFlags":["content-disposition","acl","policy","x-amz-credential","x-amz-algorithm","x-amz-date","x-amz-signature","key"],"isESM":true,"relativePath":["dist","commands","common","direct-url.js"]},"common:get-exports":{"aliases":[],"args":{"id":{"description":"Идентификатор экспорта","name":"id","required":true}},"description":"Скачать архив экспорта","examples":["Экспорт истории чата:\n $ pachca common get-exports"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"save":{"description":"Путь для сохранения файла","name":"save","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"common:get-exports","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chat_exports:read","plan":"corporation","apiMethod":"GET","apiPath":"/chats/exports/{id}","isESM":true,"relativePath":["dist","commands","common","get-exports.js"]},"common:request-export":{"aliases":[],"args":{},"description":"Экспорт сообщений","examples":["Экспорт истории чата:\n $ pachca common request-export"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"start-at":{"description":"Дата начала для экспорта (ISO-8601, UTC+0) в формате YYYY-MM-DD","name":"start-at","hasDynamicHelp":false,"multiple":false,"type":"option"},"end-at":{"description":"Дата окончания для экспорта (ISO-8601, UTC+0) в формате YYYY-MM-DD","name":"end-at","hasDynamicHelp":false,"multiple":false,"type":"option"},"webhook-url":{"description":"Адрес, на который будет отправлен вебхук по завершению экспорта","name":"webhook-url","hasDynamicHelp":false,"multiple":false,"type":"option"},"chat-ids":{"description":"Массив идентификаторов чатов. Указывается, если нужно получить сообщения только некоторых чатов.","name":"chat-ids","hasDynamicHelp":false,"multiple":false,"type":"option"},"skip-chats-file":{"description":"Пропуск формирования файла со списком чатов (chats.json)","name":"skip-chats-file","allowNo":true,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"common:request-export","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chat_exports:write","plan":"corporation","apiMethod":"POST","apiPath":"/chats/exports","requiredFlags":["start-at","end-at","webhook-url"],"isESM":true,"relativePath":["dist","commands","common","request-export.js"]},"common:uploads":{"aliases":[],"args":{},"description":"Получение подписи, ключа и других параметров","examples":["Изменить вложения сообщения:\n $ pachca common uploads"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"common:uploads","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"uploads:write","apiMethod":"POST","apiPath":"/uploads","defaultColumns":["Content-Disposition","acl","policy","x-amz-credential","x-amz-algorithm"],"isESM":true,"relativePath":["dist","commands","common","uploads.js"]},"config:get":{"aliases":[],"args":{"key":{"description":"Configuration key","name":"key","required":true}},"description":"Получение значения конфигурации","examples":["<%= config.bin %> config get defaults.output"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"config:get","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","config","get.js"]},"config:list":{"aliases":[],"args":{},"description":"Список всех настроек","examples":["<%= config.bin %> config list","<%= config.bin %> config list -o json"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"config:list","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","config","list.js"]},"config:set":{"aliases":[],"args":{"key":{"description":"Configuration key","name":"key","required":true},"value":{"description":"Configuration value","name":"value","required":true}},"description":"Установка значения конфигурации","examples":["<%= config.bin %> config set defaults.output json","<%= config.bin %> config set defaults.timeout 60"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"config:set","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","config","set.js"]},"doctor":{"aliases":[],"args":{},"description":"Диагностика окружения: Node.js, сеть, токен, конфигурация","examples":["<%= config.bin %> doctor","<%= config.bin %> doctor -o json"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"doctor","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","doctor.js"]},"group-tags:create":{"aliases":[],"args":{},"description":"Новый тег","examples":["Массовое создание сотрудников с тегами:\n $ pachca group-tags create","Получить всех сотрудников тега/департамента:\n $ pachca group-tags list"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"name":{"description":"Название тега","name":"name","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"group-tags:create","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"group_tags:write","apiMethod":"POST","apiPath":"/group_tags","defaultColumns":["id","name","users_count"],"requiredFlags":["name"],"isESM":true,"relativePath":["dist","commands","group-tags","create.js"]},"group-tags:delete":{"aliases":[],"args":{"id":{"description":"Идентификатор тега","name":"id","required":true}},"description":"Удаление тега","flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"group-tags:delete","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"group_tags:write","apiMethod":"DELETE","apiPath":"/group_tags/{id}","isESM":true,"relativePath":["dist","commands","group-tags","delete.js"]},"group-tags:get":{"aliases":[],"args":{"id":{"description":"Идентификатор тега","name":"id","required":true}},"description":"Информация о теге","flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"group-tags:get","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"group_tags:read","apiMethod":"GET","apiPath":"/group_tags/{id}","defaultColumns":["id","name","users_count"],"isESM":true,"relativePath":["dist","commands","group-tags","get.js"]},"group-tags:list":{"aliases":[],"args":{},"description":"Список тегов сотрудников","examples":["Массовое создание сотрудников с тегами:\n $ pachca group-tags create","Получить всех сотрудников тега/департамента:\n $ pachca group-tags list"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"names":{"description":"Массив названий тегов, по которым вы хотите отфильтровать список (через запятую)","name":"names","hasDynamicHelp":false,"multiple":false,"type":"option"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"group-tags:list","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"group_tags:read","apiMethod":"GET","apiPath":"/group_tags","defaultColumns":["id","name","users_count"],"isESM":true,"relativePath":["dist","commands","group-tags","list.js"]},"group-tags:list-users":{"aliases":[],"args":{"id":{"description":"Идентификатор тега","name":"id","required":true}},"description":"Список сотрудников тега","examples":["Получить всех сотрудников тега/департамента:\n $ pachca group-tags list-users"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"group-tags:list-users","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"group_tags:read","apiMethod":"GET","apiPath":"/group_tags/{id}/users","defaultColumns":["id","title","first_name","last_name","email"],"isESM":true,"relativePath":["dist","commands","group-tags","list-users.js"]},"group-tags:update":{"aliases":[],"args":{"id":{"description":"Идентификатор тега","name":"id","required":true}},"description":"Редактирование тега","flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"name":{"description":"Название тега","name":"name","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"group-tags:update","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"group_tags:write","apiMethod":"PUT","apiPath":"/group_tags/{id}","defaultColumns":["id","name","users_count"],"requiredFlags":["name"],"isESM":true,"relativePath":["dist","commands","group-tags","update.js"]},"guide":{"aliases":[],"args":{"query":{"description":"Search query","name":"query","required":false}},"description":"Поиск сценариев использования","examples":["<%= config.bin %> guide \"отправить файл\"","<%= config.bin %> guide \"создать бота\"","<%= config.bin %> guide"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"guide","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","guide.js"]},"introspect":{"aliases":[],"args":{"command":{"description":"Command name (e.g., \"messages create\")","name":"command","required":false}},"description":"Метаданные команды в машиночитаемом формате","examples":["<%= config.bin %> introspect messages create","<%= config.bin %> introspect"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"introspect","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":false,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","introspect.js"]},"link-previews:add":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения","name":"id","required":true}},"description":"Unfurl (разворачивание ссылок)","examples":["Разворачивание ссылок (unfurling):\n $ pachca link-previews add"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"link-previews":{"description":"`JSON` карта предпросмотров ссылок, где каждый ключ — `URL`, который был получен в исходящем вебхуке о новом сообщении.","name":"link-previews","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"link-previews:add","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"link_previews:write","apiMethod":"POST","apiPath":"/messages/{id}/link_previews","requiredFlags":["link-previews"],"isESM":true,"relativePath":["dist","commands","link-previews","add.js"]},"members:add":{"aliases":[],"args":{"id":{"description":"Идентификатор чата (беседа, канал или чат треда)","name":"id","required":true}},"description":"Добавление пользователей","examples":["Подписаться на тред сообщения:\n $ pachca members add","Упомянуть пользователя по имени:\n $ pachca members list","Создать канал и пригласить участников:\n $ pachca members add"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"member-ids":{"description":"Массив идентификаторов пользователей, которые станут участниками","name":"member-ids","hasDynamicHelp":false,"multiple":false,"type":"option"},"silent":{"description":"Не создавать в чате системное сообщение о добавлении участника","name":"silent","allowNo":true,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"members:add","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chat_members:write","apiMethod":"POST","apiPath":"/chats/{id}/members","requiredFlags":["member-ids"],"isESM":true,"relativePath":["dist","commands","members","add.js"]},"members:add-group-tags":{"aliases":[],"args":{"id":{"description":"Идентификатор чата","name":"id","required":true}},"description":"Добавление тегов","flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"group-tag-ids":{"description":"Массив идентификаторов тегов, которые станут участниками","name":"group-tag-ids","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"members:add-group-tags","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chat_members:write","apiMethod":"POST","apiPath":"/chats/{id}/group_tags","requiredFlags":["group-tag-ids"],"isESM":true,"relativePath":["dist","commands","members","add-group-tags.js"]},"members:leave":{"aliases":[],"args":{"id":{"description":"Идентификатор чата","name":"id","required":true}},"description":"Выход из беседы или канала","examples":["Архивация и управление чатом:\n $ pachca members leave"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"members:leave","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chats:leave","apiMethod":"DELETE","apiPath":"/chats/{id}/leave","isESM":true,"relativePath":["dist","commands","members","leave.js"]},"members:list":{"aliases":[],"args":{"id":{"description":"Идентификатор чата","name":"id","required":true}},"description":"Список участников чата","examples":["Подписаться на тред сообщения:\n $ pachca members add","Упомянуть пользователя по имени:\n $ pachca members list","Создать канал и пригласить участников:\n $ pachca members add"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"role":{"description":"Роль в чате","name":"role","hasDynamicHelp":false,"multiple":false,"type":"option"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"members:list","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chat_members:read","apiMethod":"GET","apiPath":"/chats/{id}/members","defaultColumns":["id","title","first_name","last_name","email"],"isESM":true,"relativePath":["dist","commands","members","list.js"]},"members:remove":{"aliases":[],"args":{"id":{"description":"Идентификатор чата","name":"id","required":true},"user_id":{"description":"Идентификатор пользователя (pachca users list)","name":"user_id","required":true}},"description":"Исключение пользователя","examples":["Архивация и управление чатом:\n $ pachca members update","Архивация и управление чатом:\n $ pachca members remove"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"members:remove","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chat_members:write","apiMethod":"DELETE","apiPath":"/chats/{id}/members/{user_id}","isESM":true,"relativePath":["dist","commands","members","remove.js"]},"members:remove-group-tag":{"aliases":[],"args":{"id":{"description":"Идентификатор чата","name":"id","required":true},"tag_id":{"description":"Идентификатор тега (pachca tags list)","name":"tag_id","required":true}},"description":"Исключение тега","flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"members:remove-group-tag","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chat_members:write","apiMethod":"DELETE","apiPath":"/chats/{id}/group_tags/{tag_id}","isESM":true,"relativePath":["dist","commands","members","remove-group-tag.js"]},"members:update":{"aliases":[],"args":{"id":{"description":"Идентификатор чата","name":"id","required":true},"user_id":{"description":"Идентификатор пользователя (pachca users list)","name":"user_id","required":true}},"description":"Редактирование роли","examples":["Архивация и управление чатом:\n $ pachca members update","Архивация и управление чатом:\n $ pachca members remove"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"role":{"description":"Роль","name":"role","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"members:update","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chat_members:write","apiMethod":"PUT","apiPath":"/chats/{id}/members/{user_id}","requiredFlags":["role"],"isESM":true,"relativePath":["dist","commands","members","update.js"]},"messages:create":{"aliases":[],"args":{},"description":"Новое сообщение","examples":["Найти чат по имени и отправить сообщение:\n $ pachca messages create","Отправить сообщение в канал или беседу (если chat_id известен):\n $ pachca messages create","Отправить личное сообщение пользователю:\n $ pachca messages create"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"entity-type":{"description":"Тип сущности","name":"entity-type","hasDynamicHelp":false,"multiple":false,"type":"option"},"entity-id":{"description":"Идентификатор сущности (pachca chats list | pachca users list)","name":"entity-id","hasDynamicHelp":false,"multiple":false,"type":"option"},"content":{"description":"Текст сообщения","name":"content","hasDynamicHelp":false,"multiple":false,"type":"option"},"files":{"description":"Прикрепляемые файлы","name":"files","hasDynamicHelp":false,"multiple":false,"type":"option"},"buttons":{"description":"Массив строк, каждая из которых представлена массивом кнопок. Максимум 100 кнопок у сообщения, до 8 кнопок в строке.","name":"buttons","hasDynamicHelp":false,"multiple":false,"type":"option"},"parent-message-id":{"description":"Идентификатор сообщения. Указывается в случае, если вы отправляете ответ на другое сообщение.","name":"parent-message-id","hasDynamicHelp":false,"multiple":false,"type":"option"},"display-avatar-url":{"description":"Ссылка на специальную аватарку отправителя для этого сообщения. Использование этого поля возможно только с access_token бота. (макс. 255 символов)","name":"display-avatar-url","hasDynamicHelp":false,"multiple":false,"type":"option"},"display-name":{"description":"Полное специальное имя отправителя для этого сообщения. Использование этого поля возможно только с access_token бота. (макс. 255 символов)","name":"display-name","hasDynamicHelp":false,"multiple":false,"type":"option"},"skip-invite-mentions":{"description":"Пропуск добавления упоминаемых пользователей в тред. Работает только при отправке сообщения в тред.","name":"skip-invite-mentions","allowNo":true,"type":"boolean"},"link-preview":{"description":"Отображение предпросмотра первой найденной ссылки в тексте сообщения","name":"link-preview","allowNo":true,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"messages:create","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"messages:create","apiMethod":"POST","apiPath":"/messages","defaultColumns":["id","content","created_at","entity_type","entity_id"],"requiredFlags":["entity-id","content"],"isESM":true,"relativePath":["dist","commands","messages","create.js"]},"messages:delete":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения (pachca messages list)","name":"id","required":true}},"description":"Удаление сообщения","examples":["Получить вложения из сообщения:\n $ pachca messages get","Отредактировать сообщение:\n $ pachca messages update","Изменить вложения сообщения:\n $ pachca messages get"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"messages:delete","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"messages:delete","apiMethod":"DELETE","apiPath":"/messages/{id}","isESM":true,"relativePath":["dist","commands","messages","delete.js"]},"messages:get":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения (pachca messages list)","name":"id","required":true}},"description":"Информация о сообщении","examples":["Получить вложения из сообщения:\n $ pachca messages get","Отредактировать сообщение:\n $ pachca messages update","Изменить вложения сообщения:\n $ pachca messages get"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"messages:get","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"messages:read","apiMethod":"GET","apiPath":"/messages/{id}","defaultColumns":["id","content","created_at","entity_type","entity_id"],"isESM":true,"relativePath":["dist","commands","messages","get.js"]},"messages:list":{"aliases":[],"args":{},"description":"Список сообщений чата","examples":["Найти чат по имени и отправить сообщение:\n $ pachca messages create","Отправить сообщение в канал или беседу (если chat_id известен):\n $ pachca messages create","Отправить личное сообщение пользователю:\n $ pachca messages create"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"chat-id":{"description":"Идентификатор чата (беседа, канал, диалог или чат треда)","name":"chat-id","hasDynamicHelp":false,"multiple":false,"type":"option"},"sort":{"description":"Поле сортировки (id — идентификатор сообщения)","name":"sort","hasDynamicHelp":false,"multiple":false,"options":["id"],"type":"option"},"order":{"description":"Порядок сортировки","name":"order","hasDynamicHelp":false,"multiple":false,"options":["asc","desc"],"type":"option"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"messages:list","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"messages:read","apiMethod":"GET","apiPath":"/messages","defaultColumns":["id","content","created_at","entity_type","entity_id"],"requiredFlags":["chat-id"],"isESM":true,"relativePath":["dist","commands","messages","list.js"]},"messages:pin":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения (pachca messages list)","name":"id","required":true}},"description":"Закрепление сообщения","examples":["Закрепить/открепить сообщение:\n $ pachca messages pin","Закрепить/открепить сообщение:\n $ pachca messages unpin"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"messages:pin","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"pins:write","apiMethod":"POST","apiPath":"/messages/{id}/pin","isESM":true,"relativePath":["dist","commands","messages","pin.js"]},"messages:unpin":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения (pachca messages list)","name":"id","required":true}},"description":"Открепление сообщения","examples":["Закрепить/открепить сообщение:\n $ pachca messages pin","Закрепить/открепить сообщение:\n $ pachca messages unpin"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"messages:unpin","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"pins:write","apiMethod":"DELETE","apiPath":"/messages/{id}/pin","isESM":true,"relativePath":["dist","commands","messages","unpin.js"]},"messages:update":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения (pachca messages list)","name":"id","required":true}},"description":"Редактирование сообщения","examples":["Получить вложения из сообщения:\n $ pachca messages get","Отредактировать сообщение:\n $ pachca messages update","Изменить вложения сообщения:\n $ pachca messages get"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"content":{"description":"Текст сообщения","name":"content","hasDynamicHelp":false,"multiple":false,"type":"option"},"files":{"description":"Прикрепляемые файлы","name":"files","hasDynamicHelp":false,"multiple":false,"type":"option"},"buttons":{"description":"Массив строк, каждая из которых представлена массивом кнопок. Максимум 100 кнопок у сообщения, до 8 кнопок в строке. Для удаления кнопок пришлите пустой массив.","name":"buttons","hasDynamicHelp":false,"multiple":false,"type":"option"},"display-avatar-url":{"description":"Ссылка на специальную аватарку отправителя для этого сообщения. Использование этого поля возможно только с access_token бота.","name":"display-avatar-url","hasDynamicHelp":false,"multiple":false,"type":"option"},"display-name":{"description":"Полное специальное имя отправителя для этого сообщения. Использование этого поля возможно только с access_token бота.","name":"display-name","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"messages:update","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"messages:update","apiMethod":"PUT","apiPath":"/messages/{id}","defaultColumns":["id","content","created_at","entity_type","entity_id"],"isESM":true,"relativePath":["dist","commands","messages","update.js"]},"profile:delete-status":{"aliases":[],"args":{},"description":"Удаление статуса","examples":["Установить статус:\n $ pachca profile update-status","Сбросить статус:\n $ pachca profile delete-status"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"profile:delete-status","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"profile_status:write","apiMethod":"DELETE","apiPath":"/profile/status","isESM":true,"relativePath":["dist","commands","profile","delete-status.js"]},"profile:get":{"aliases":[],"args":{},"description":"Информация о профиле","examples":["Получить свой профиль:\n $ pachca profile get","Получить кастомные поля профиля:\n $ pachca profile get"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"profile:get","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"profile:read","apiMethod":"GET","apiPath":"/profile","defaultColumns":["id","title","first_name","last_name","email"],"isESM":true,"relativePath":["dist","commands","profile","get.js"]},"profile:get-info":{"aliases":[],"args":{},"description":"Информация о токене","examples":["Проверить свой токен:\n $ pachca profile get-info"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"profile:get-info","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"apiMethod":"GET","apiPath":"/oauth/token/info","defaultColumns":["id","name","created_at","token","user_id"],"isESM":true,"relativePath":["dist","commands","profile","get-info.js"]},"profile:get-status":{"aliases":[],"args":{},"description":"Текущий статус","examples":["Установить статус:\n $ pachca profile update-status","Сбросить статус:\n $ pachca profile delete-status"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"profile:get-status","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"profile_status:read","apiMethod":"GET","apiPath":"/profile/status","defaultColumns":["title","emoji","expires_at","is_away"],"isESM":true,"relativePath":["dist","commands","profile","get-status.js"]},"profile:update-status":{"aliases":[],"args":{},"description":"Новый статус","examples":["Установить статус:\n $ pachca profile update-status","Сбросить статус:\n $ pachca profile delete-status"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"emoji":{"description":"Emoji символ статуса","name":"emoji","hasDynamicHelp":false,"multiple":false,"type":"option"},"title":{"description":"Текст статуса","name":"title","hasDynamicHelp":false,"multiple":false,"type":"option"},"expires-at":{"description":"Срок жизни статуса (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ","name":"expires-at","hasDynamicHelp":false,"multiple":false,"type":"option"},"is-away":{"description":"Режим «Нет на месте»","name":"is-away","allowNo":true,"type":"boolean"},"away-message":{"description":"Текст сообщения при режиме «Нет на месте». Отображается в профиле и при личных сообщениях/упоминаниях. (макс. 1024 символов)","name":"away-message","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"profile:update-status","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"profile_status:write","apiMethod":"PUT","apiPath":"/profile/status","defaultColumns":["title","emoji","expires_at","is_away"],"requiredFlags":["emoji","title"],"isESM":true,"relativePath":["dist","commands","profile","update-status.js"]},"reactions:add":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения","name":"id","required":true}},"description":"Добавление реакции","examples":["Добавить реакцию на сообщение:\n $ pachca reactions add","Добавить реакцию на сообщение:\n $ pachca reactions remove"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"code":{"description":"Emoji символ реакции","name":"code","hasDynamicHelp":false,"multiple":false,"type":"option"},"name":{"description":"Текстовое имя эмодзи (используется для кастомных эмодзи)","name":"name","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"reactions:add","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"reactions:write","apiMethod":"POST","apiPath":"/messages/{id}/reactions","defaultColumns":["name","created_at","user_id","code"],"requiredFlags":["code"],"isESM":true,"relativePath":["dist","commands","reactions","add.js"]},"reactions:list":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения","name":"id","required":true}},"description":"Список реакций","examples":["Добавить реакцию на сообщение:\n $ pachca reactions add","Добавить реакцию на сообщение:\n $ pachca reactions remove"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"reactions:list","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"reactions:read","apiMethod":"GET","apiPath":"/messages/{id}/reactions","defaultColumns":["name","created_at","user_id","code"],"isESM":true,"relativePath":["dist","commands","reactions","list.js"]},"reactions:remove":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения","name":"id","required":true}},"description":"Удаление реакции","examples":["Добавить реакцию на сообщение:\n $ pachca reactions add","Добавить реакцию на сообщение:\n $ pachca reactions remove"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"code":{"description":"Emoji символ реакции","name":"code","hasDynamicHelp":false,"multiple":false,"type":"option"},"name":{"description":"Текстовое имя эмодзи (используется для кастомных эмодзи)","name":"name","hasDynamicHelp":false,"multiple":false,"type":"option"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"reactions:remove","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"reactions:write","apiMethod":"DELETE","apiPath":"/messages/{id}/reactions","requiredFlags":["code"],"isESM":true,"relativePath":["dist","commands","reactions","remove.js"]},"read-member:list-readers":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения","name":"id","required":true}},"description":"Список прочитавших сообщение","examples":["Проверить, кто прочитал сообщение:\n $ pachca read-member list-readers"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"read-member:list-readers","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"messages:read","apiMethod":"GET","apiPath":"/messages/{id}/read_member_ids","isESM":true,"relativePath":["dist","commands","read-member","list-readers.js"]},"search:list-chats":{"aliases":[],"args":{},"description":"Поиск чатов","examples":["Найти чат по имени и отправить сообщение:\n $ pachca search list-chats","Найти чат по названию:\n $ pachca search list-chats"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"query":{"description":"Текст поискового запроса","name":"query","hasDynamicHelp":false,"multiple":false,"type":"option"},"order":{"description":"Направление сортировки","name":"order","hasDynamicHelp":false,"multiple":false,"options":["asc","desc"],"type":"option"},"created-from":{"description":"Фильтр по дате создания (от)","name":"created-from","hasDynamicHelp":false,"multiple":false,"type":"option"},"created-to":{"description":"Фильтр по дате создания (до)","name":"created-to","hasDynamicHelp":false,"multiple":false,"type":"option"},"active":{"description":"Фильтр по активности чата","name":"active","allowNo":true,"type":"boolean"},"chat-subtype":{"description":"Фильтр по типу чата","name":"chat-subtype","hasDynamicHelp":false,"multiple":false,"options":["discussion","thread"],"type":"option"},"personal":{"description":"Фильтр по личным чатам","name":"personal","allowNo":true,"type":"boolean"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"search:list-chats","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"search:chats","apiMethod":"GET","apiPath":"/search/chats","defaultColumns":["id","name","created_at","owner_id","channel"],"isESM":true,"relativePath":["dist","commands","search","list-chats.js"]},"search:list-messages":{"aliases":[],"args":{},"description":"Поиск сообщений","examples":["Найти сообщение по тексту:\n $ pachca search list-messages"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"query":{"description":"Текст поискового запроса","name":"query","hasDynamicHelp":false,"multiple":false,"type":"option"},"order":{"description":"Направление сортировки","name":"order","hasDynamicHelp":false,"multiple":false,"options":["asc","desc"],"type":"option"},"created-from":{"description":"Фильтр по дате создания (от)","name":"created-from","hasDynamicHelp":false,"multiple":false,"type":"option"},"created-to":{"description":"Фильтр по дате создания (до)","name":"created-to","hasDynamicHelp":false,"multiple":false,"type":"option"},"chat-ids":{"description":"Фильтр по ID чатов (через запятую)","name":"chat-ids","hasDynamicHelp":false,"multiple":false,"type":"option"},"user-ids":{"description":"Фильтр по ID авторов сообщений (через запятую)","name":"user-ids","hasDynamicHelp":false,"multiple":false,"type":"option"},"active":{"description":"Фильтр по активности чата","name":"active","allowNo":true,"type":"boolean"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"search:list-messages","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"search:messages","apiMethod":"GET","apiPath":"/search/messages","defaultColumns":["id","content","created_at","entity_type","entity_id"],"isESM":true,"relativePath":["dist","commands","search","list-messages.js"]},"search:list-users":{"aliases":[],"args":{},"description":"Поиск сотрудников","examples":["Отправить личное сообщение пользователю:\n $ pachca search list-users","Упомянуть пользователя по имени:\n $ pachca search list-users","Найти сотрудника по имени:\n $ pachca search list-users"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"query":{"description":"Текст поискового запроса","name":"query","hasDynamicHelp":false,"multiple":false,"type":"option"},"sort":{"description":"Сортировка результатов","name":"sort","hasDynamicHelp":false,"multiple":false,"options":["by_score","alphabetical"],"type":"option"},"order":{"description":"Направление сортировки","name":"order","hasDynamicHelp":false,"multiple":false,"options":["asc","desc"],"type":"option"},"created-from":{"description":"Фильтр по дате создания (от)","name":"created-from","hasDynamicHelp":false,"multiple":false,"type":"option"},"created-to":{"description":"Фильтр по дате создания (до)","name":"created-to","hasDynamicHelp":false,"multiple":false,"type":"option"},"company-roles":{"description":"Фильтр по ролям сотрудников (через запятую)","name":"company-roles","hasDynamicHelp":false,"multiple":false,"type":"option"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"search:list-users","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"search:users","apiMethod":"GET","apiPath":"/search/users","defaultColumns":["id","title","first_name","last_name","email"],"isESM":true,"relativePath":["dist","commands","search","list-users.js"]},"security:list":{"aliases":[],"args":{},"description":"Журнал аудита событий","examples":["Получить журнал аудита событий:\n $ pachca security list","Мониторинг подозрительных входов:\n $ pachca security list","Экспорт логов за период:\n $ pachca security list"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"start-time":{"description":"Начальная метка времени (включительно)","name":"start-time","hasDynamicHelp":false,"multiple":false,"type":"option"},"end-time":{"description":"Конечная метка времени (исключительно)","name":"end-time","hasDynamicHelp":false,"multiple":false,"type":"option"},"event-key":{"description":"Фильтр по конкретному типу события","name":"event-key","hasDynamicHelp":false,"multiple":false,"options":["user_login","user_logout","user_2fa_fail","user_2fa_success","user_created","user_deleted","user_role_changed","user_updated","tag_created","tag_deleted","user_added_to_tag","user_removed_from_tag","chat_created","chat_renamed","chat_permission_changed","user_chat_join","user_chat_leave","tag_added_to_chat","tag_removed_from_chat","message_updated","message_deleted","message_created","reaction_created","reaction_deleted","thread_created","access_token_created","access_token_updated","access_token_destroy","kms_encrypt","kms_decrypt","audit_events_accessed","dlp_violation_detected","search_users_api","search_chats_api","search_messages_api"],"type":"option"},"actor-id":{"description":"Идентификатор пользователя, выполнившего действие","name":"actor-id","hasDynamicHelp":false,"multiple":false,"type":"option"},"actor-type":{"description":"Тип актора","name":"actor-type","hasDynamicHelp":false,"multiple":false,"type":"option"},"entity-id":{"description":"Идентификатор затронутой сущности","name":"entity-id","hasDynamicHelp":false,"multiple":false,"type":"option"},"entity-type":{"description":"Тип сущности","name":"entity-type","hasDynamicHelp":false,"multiple":false,"type":"option"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"security:list","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"audit_events:read","plan":"corporation","apiMethod":"GET","apiPath":"/audit_events","defaultColumns":["id","created_at","event_key","entity_id","entity_type"],"isESM":true,"relativePath":["dist","commands","security","list.js"]},"tasks:create":{"aliases":[],"args":{},"description":"Новое напоминание","examples":["Форма заявки/запроса:\n $ pachca tasks create","Создать напоминание:\n $ pachca tasks create","Получить список предстоящих задач:\n $ pachca tasks list"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"kind":{"description":"Тип","name":"kind","hasDynamicHelp":false,"multiple":false,"type":"option"},"content":{"description":"Описание (по умолчанию — название типа)","name":"content","hasDynamicHelp":false,"multiple":false,"type":"option"},"due-at":{"description":"Срок выполнения напоминания (ISO-8601) в формате YYYY-MM-DDThh:mm:ss.sssTZD. Если указано время 23:59:59.000, то напоминание будет создано на весь день (без указания времени).","name":"due-at","hasDynamicHelp":false,"multiple":false,"type":"option"},"priority":{"description":"Приоритет: 1, 2 (важно) или 3 (очень важно).","name":"priority","hasDynamicHelp":false,"multiple":false,"type":"option"},"performer-ids":{"description":"Массив идентификаторов пользователей, привязываемых к напоминанию как «ответственные» (по умолчанию ответственным назначается вы)","name":"performer-ids","hasDynamicHelp":false,"multiple":false,"type":"option"},"chat-id":{"description":"Идентификатор чата, к которому привязывается напоминание (pachca chats list)","name":"chat-id","hasDynamicHelp":false,"multiple":false,"type":"option"},"all-day":{"description":"Напоминание на весь день (без указания времени)","name":"all-day","allowNo":true,"type":"boolean"},"custom-properties":{"description":"Задаваемые дополнительные поля","name":"custom-properties","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"tasks:create","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"tasks:create","apiMethod":"POST","apiPath":"/tasks","defaultColumns":["id","content","created_at","kind","due_at"],"requiredFlags":["kind"],"isESM":true,"relativePath":["dist","commands","tasks","create.js"]},"tasks:delete":{"aliases":[],"args":{"id":{"description":"Идентификатор напоминания (pachca tasks list)","name":"id","required":true}},"description":"Удаление напоминания","examples":["Получить задачу по ID:\n $ pachca tasks get","Отметить задачу выполненной:\n $ pachca tasks update","Обновить задачу (перенести срок, сменить ответственных):\n $ pachca tasks update"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"tasks:delete","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"tasks:delete","apiMethod":"DELETE","apiPath":"/tasks/{id}","isESM":true,"relativePath":["dist","commands","tasks","delete.js"]},"tasks:get":{"aliases":[],"args":{"id":{"description":"Идентификатор напоминания (pachca tasks list)","name":"id","required":true}},"description":"Информация о напоминании","examples":["Получить задачу по ID:\n $ pachca tasks get","Отметить задачу выполненной:\n $ pachca tasks update","Обновить задачу (перенести срок, сменить ответственных):\n $ pachca tasks update"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"tasks:get","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"tasks:read","apiMethod":"GET","apiPath":"/tasks/{id}","defaultColumns":["id","content","created_at","kind","due_at"],"isESM":true,"relativePath":["dist","commands","tasks","get.js"]},"tasks:list":{"aliases":[],"args":{},"description":"Список напоминаний","examples":["Форма заявки/запроса:\n $ pachca tasks create","Создать напоминание:\n $ pachca tasks create","Получить список предстоящих задач:\n $ pachca tasks list"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"tasks:list","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"tasks:read","apiMethod":"GET","apiPath":"/tasks","defaultColumns":["id","content","created_at","kind","due_at"],"isESM":true,"relativePath":["dist","commands","tasks","list.js"]},"tasks:update":{"aliases":[],"args":{"id":{"description":"Идентификатор напоминания (pachca tasks list)","name":"id","required":true}},"description":"Редактирование напоминания","examples":["Получить задачу по ID:\n $ pachca tasks get","Отметить задачу выполненной:\n $ pachca tasks update","Обновить задачу (перенести срок, сменить ответственных):\n $ pachca tasks update"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"kind":{"description":"Тип","name":"kind","hasDynamicHelp":false,"multiple":false,"type":"option"},"content":{"description":"Описание","name":"content","hasDynamicHelp":false,"multiple":false,"type":"option"},"due-at":{"description":"Срок выполнения напоминания (ISO-8601) в формате YYYY-MM-DDThh:mm:ss.sssTZD. Если указано время 23:59:59.000, то напоминание будет создано на весь день (без указания времени).","name":"due-at","hasDynamicHelp":false,"multiple":false,"type":"option"},"priority":{"description":"Приоритет: 1, 2 (важно) или 3 (очень важно).","name":"priority","hasDynamicHelp":false,"multiple":false,"type":"option"},"performer-ids":{"description":"Массив идентификаторов пользователей, привязываемых к напоминанию как «ответственные»","name":"performer-ids","hasDynamicHelp":false,"multiple":false,"type":"option"},"status":{"description":"Статус","name":"status","hasDynamicHelp":false,"multiple":false,"type":"option"},"all-day":{"description":"Напоминание на весь день (без указания времени)","name":"all-day","allowNo":true,"type":"boolean"},"done-at":{"description":"Дата и время выполнения напоминания (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ","name":"done-at","hasDynamicHelp":false,"multiple":false,"type":"option"},"custom-properties":{"description":"Задаваемые дополнительные поля","name":"custom-properties","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"tasks:update","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"tasks:update","apiMethod":"PUT","apiPath":"/tasks/{id}","defaultColumns":["id","content","created_at","kind","due_at"],"isESM":true,"relativePath":["dist","commands","tasks","update.js"]},"threads:add":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения","name":"id","required":true}},"description":"Новый тред","examples":["Ответить в тред (комментарий к сообщению):\n $ pachca thread add","Подписаться на тред сообщения:\n $ pachca thread add"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"threads:add","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"threads:create","apiMethod":"POST","apiPath":"/messages/{id}/thread","defaultColumns":["id","chat_id","message_id","message_chat_id","updated_at"],"isESM":true,"relativePath":["dist","commands","threads","add.js"]},"threads:get":{"aliases":[],"args":{"id":{"description":"Идентификатор треда","name":"id","required":true}},"description":"Информация о треде","flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"threads:get","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"threads:read","apiMethod":"GET","apiPath":"/threads/{id}","defaultColumns":["id","chat_id","message_id","message_chat_id","updated_at"],"isESM":true,"relativePath":["dist","commands","threads","get.js"]},"upgrade":{"aliases":[],"args":{},"description":"Обновить CLI до последней версии","examples":["<%= config.bin %> upgrade"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"upgrade","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","upgrade.js"]},"upload":{"aliases":[],"args":{"file":{"description":"Путь к файлу или - для stdin","name":"file","required":true}},"description":"Загрузить файл (получает подпись и загружает автоматически)","examples":["<%= config.bin %> upload photo.jpg","cat data.csv | <%= config.bin %> upload -"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"upload","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"uploads:write","isESM":true,"relativePath":["dist","commands","upload.js"]},"users:create":{"aliases":[],"args":{},"description":"Создать сотрудника","examples":["Проверить, кто прочитал сообщение:\n $ pachca users list","Разослать уведомление нескольким пользователям:\n $ pachca users list","Массовое создание сотрудников с тегами:\n $ pachca users create"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"first-name":{"description":"Имя","name":"first-name","hasDynamicHelp":false,"multiple":false,"type":"option"},"last-name":{"description":"Фамилия","name":"last-name","hasDynamicHelp":false,"multiple":false,"type":"option"},"email":{"description":"Электронная почта","name":"email","hasDynamicHelp":false,"multiple":false,"type":"option"},"phone-number":{"description":"Телефон","name":"phone-number","hasDynamicHelp":false,"multiple":false,"type":"option"},"nickname":{"description":"Имя пользователя","name":"nickname","hasDynamicHelp":false,"multiple":false,"type":"option"},"department":{"description":"Департамент","name":"department","hasDynamicHelp":false,"multiple":false,"type":"option"},"title":{"description":"Должность","name":"title","hasDynamicHelp":false,"multiple":false,"type":"option"},"role":{"description":"Уровень доступа","name":"role","hasDynamicHelp":false,"multiple":false,"type":"option"},"suspended":{"description":"Деактивация пользователя","name":"suspended","allowNo":true,"type":"boolean"},"list-tags":{"description":"Массив тегов, привязываемых к сотруднику","name":"list-tags","hasDynamicHelp":false,"multiple":false,"type":"option"},"custom-properties":{"description":"Задаваемые дополнительные поля","name":"custom-properties","hasDynamicHelp":false,"multiple":false,"type":"option"},"skip-email-notify":{"description":"Пропуск этапа отправки приглашения сотруднику. Сотруднику не будет отправлено письмо на электронную почту с приглашением создать аккаунт. Полезно при предварительном создании аккаунтов перед входом через SSO.","name":"skip-email-notify","allowNo":true,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"users:create","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"users:create","apiMethod":"POST","apiPath":"/users","defaultColumns":["id","title","first_name","last_name","email"],"requiredFlags":["email"],"isESM":true,"relativePath":["dist","commands","users","create.js"]},"users:delete":{"aliases":[],"args":{"id":{"description":"Идентификатор пользователя (pachca users list)","name":"id","required":true}},"description":"Удаление сотрудника","examples":["Получить сотрудника по ID:\n $ pachca users get","Массовое создание сотрудников с тегами:\n $ pachca users update","Offboarding сотрудника:\n $ pachca users update"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"users:delete","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"users:delete","apiMethod":"DELETE","apiPath":"/users/{id}","isESM":true,"relativePath":["dist","commands","users","delete.js"]},"users:get":{"aliases":[],"args":{"id":{"description":"Идентификатор пользователя (pachca users list)","name":"id","required":true}},"description":"Информация о сотруднике","examples":["Получить сотрудника по ID:\n $ pachca users get","Массовое создание сотрудников с тегами:\n $ pachca users update","Offboarding сотрудника:\n $ pachca users update"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"users:get","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"users:read","apiMethod":"GET","apiPath":"/users/{id}","defaultColumns":["id","title","first_name","last_name","email"],"isESM":true,"relativePath":["dist","commands","users","get.js"]},"users:get-status":{"aliases":[],"args":{"user_id":{"description":"Идентификатор пользователя (pachca users list)","name":"user_id","required":true}},"description":"Статус сотрудника","examples":["Управление статусом сотрудника:\n $ pachca users get-status","Управление статусом сотрудника:\n $ pachca users update-status","Управление статусом сотрудника:\n $ pachca users remove-status"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"users:get-status","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"user_status:read","apiMethod":"GET","apiPath":"/users/{user_id}/status","defaultColumns":["title","emoji","expires_at","is_away"],"isESM":true,"relativePath":["dist","commands","users","get-status.js"]},"users:list":{"aliases":[],"args":{},"description":"Список сотрудников","examples":["Проверить, кто прочитал сообщение:\n $ pachca users list","Разослать уведомление нескольким пользователям:\n $ pachca users list","Массовое создание сотрудников с тегами:\n $ pachca users create"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"query":{"description":"Поисковая фраза для фильтрации результатов. Поиск работает по полям: `first_name` (имя), `last_name` (фамилия), `email` (электронная почта), `phone_number` (телефон) и `nickname` (никнейм).","name":"query","hasDynamicHelp":false,"multiple":false,"type":"option"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"users:list","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"users:read","apiMethod":"GET","apiPath":"/users","defaultColumns":["id","title","first_name","last_name","email"],"isESM":true,"relativePath":["dist","commands","users","list.js"]},"users:remove-status":{"aliases":[],"args":{"user_id":{"description":"Идентификатор пользователя (pachca users list)","name":"user_id","required":true}},"description":"Удаление статуса сотрудника","examples":["Управление статусом сотрудника:\n $ pachca users get-status","Управление статусом сотрудника:\n $ pachca users update-status","Управление статусом сотрудника:\n $ pachca users remove-status"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"users:remove-status","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"user_status:write","apiMethod":"DELETE","apiPath":"/users/{user_id}/status","isESM":true,"relativePath":["dist","commands","users","remove-status.js"]},"users:update":{"aliases":[],"args":{"id":{"description":"Идентификатор пользователя (pachca users list)","name":"id","required":true}},"description":"Редактирование сотрудника","examples":["Получить сотрудника по ID:\n $ pachca users get","Массовое создание сотрудников с тегами:\n $ pachca users update","Offboarding сотрудника:\n $ pachca users update"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"first-name":{"description":"Имя","name":"first-name","hasDynamicHelp":false,"multiple":false,"type":"option"},"last-name":{"description":"Фамилия","name":"last-name","hasDynamicHelp":false,"multiple":false,"type":"option"},"email":{"description":"Электронная почта","name":"email","hasDynamicHelp":false,"multiple":false,"type":"option"},"phone-number":{"description":"Телефон","name":"phone-number","hasDynamicHelp":false,"multiple":false,"type":"option"},"nickname":{"description":"Имя пользователя","name":"nickname","hasDynamicHelp":false,"multiple":false,"type":"option"},"department":{"description":"Департамент","name":"department","hasDynamicHelp":false,"multiple":false,"type":"option"},"title":{"description":"Должность","name":"title","hasDynamicHelp":false,"multiple":false,"type":"option"},"role":{"description":"Уровень доступа","name":"role","hasDynamicHelp":false,"multiple":false,"type":"option"},"suspended":{"description":"Деактивация пользователя","name":"suspended","allowNo":true,"type":"boolean"},"list-tags":{"description":"Массив тегов, привязываемых к сотруднику","name":"list-tags","hasDynamicHelp":false,"multiple":false,"type":"option"},"custom-properties":{"description":"Задаваемые дополнительные поля","name":"custom-properties","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"users:update","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"users:update","apiMethod":"PUT","apiPath":"/users/{id}","defaultColumns":["id","title","first_name","last_name","email"],"isESM":true,"relativePath":["dist","commands","users","update.js"]},"users:update-status":{"aliases":[],"args":{"user_id":{"description":"Идентификатор пользователя (pachca users list)","name":"user_id","required":true}},"description":"Новый статус сотрудника","examples":["Управление статусом сотрудника:\n $ pachca users get-status","Управление статусом сотрудника:\n $ pachca users update-status","Управление статусом сотрудника:\n $ pachca users remove-status"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"emoji":{"description":"Emoji символ статуса","name":"emoji","hasDynamicHelp":false,"multiple":false,"type":"option"},"title":{"description":"Текст статуса","name":"title","hasDynamicHelp":false,"multiple":false,"type":"option"},"expires-at":{"description":"Срок жизни статуса (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ","name":"expires-at","hasDynamicHelp":false,"multiple":false,"type":"option"},"is-away":{"description":"Режим «Нет на месте»","name":"is-away","allowNo":true,"type":"boolean"},"away-message":{"description":"Текст сообщения при режиме «Нет на месте». Отображается в профиле и при личных сообщениях/упоминаниях. (макс. 1024 символов)","name":"away-message","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"users:update-status","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"user_status:write","apiMethod":"PUT","apiPath":"/users/{user_id}/status","defaultColumns":["title","emoji","expires_at","is_away"],"requiredFlags":["emoji","title"],"isESM":true,"relativePath":["dist","commands","users","update-status.js"]},"version":{"aliases":[],"args":{},"description":"Версия CLI","examples":["<%= config.bin %> version"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"version","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","version.js"]},"views:open":{"aliases":[],"args":{},"description":"Открытие представления","examples":["Показать интерактивную форму пользователю:\n $ pachca views open","Опрос сотрудников через форму:\n $ pachca views open","Форма заявки/запроса:\n $ pachca views open"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"title":{"description":"Заголовок представления (макс. 24 символов)","name":"title","hasDynamicHelp":false,"multiple":false,"type":"option"},"close-text":{"description":"Текст кнопки закрытия представления (макс. 24 символов)","name":"close-text","hasDynamicHelp":false,"multiple":false,"type":"option"},"submit-text":{"description":"Текст кнопки отправки формы (макс. 24 символов)","name":"submit-text","hasDynamicHelp":false,"multiple":false,"type":"option"},"blocks":{"description":"Массив блоков представления","name":"blocks","hasDynamicHelp":false,"multiple":false,"type":"option"},"type":{"description":"Способ открытия представления","name":"type","hasDynamicHelp":false,"multiple":false,"options":["modal"],"type":"option"},"trigger-id":{"description":"Уникальный идентификатор события (полученный, например, в исходящем вебхуке о нажатии кнопки)","name":"trigger-id","hasDynamicHelp":false,"multiple":false,"type":"option"},"private-metadata":{"description":"Необязательная строка, которая будет отправлена в ваше приложение при отправке пользователем заполненной формы. Используйте это поле, например, для передачи в формате `JSON` какой то дополнительной информации вместе с заполненной пользователем формой. (макс. 3000 символов)","name":"private-metadata","hasDynamicHelp":false,"multiple":false,"type":"option"},"callback-id":{"description":"Необязательный идентификатор для распознавания этого представления, который будет отправлен в ваше приложение при отправке пользователем заполненной формы. Используйте это поле, например, для понимания, какую форму должен был заполнить пользователь. (макс. 255 символов)","name":"callback-id","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"views:open","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"views:write","apiMethod":"POST","apiPath":"/views/open","requiredFlags":["title","blocks","type","trigger-id"],"isESM":true,"relativePath":["dist","commands","views","open.js"]}},"version":"0.0.0"} \ No newline at end of file +{"commands":{"api":{"aliases":[],"args":{"method":{"description":"HTTP method (GET, POST, PUT, DELETE)","name":"method","options":["GET","POST","PUT","DELETE","PATCH"],"required":true},"path":{"description":"API path (e.g., /messages)","name":"path","required":true}},"description":"Произвольный запрос к API","examples":["<%= config.bin %> api GET /messages --query chat_id=123","<%= config.bin %> api POST /messages -F message[chat_id]=12345 -f message[content]=\"Привет\"","<%= config.bin %> api POST /messages --input payload.json","<%= config.bin %> api GET /profile -o yaml"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"raw-field":{"char":"f","description":"String field (key=value)","name":"raw-field","hasDynamicHelp":false,"multiple":true,"type":"option"},"field":{"char":"F","description":"Typed field (numbers/bools auto-converted, @file reads file)","name":"field","hasDynamicHelp":false,"multiple":true,"type":"option"},"input":{"description":"JSON file to send as body (- for stdin)","name":"input","hasDynamicHelp":false,"multiple":false,"type":"option"},"query":{"description":"Query parameter (key=value)","name":"query","hasDynamicHelp":false,"multiple":true,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"api","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":false,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","api.js"]},"auth:list":{"aliases":[],"args":{},"description":"Список сохранённых профилей","examples":["<%= config.bin %> auth list","<%= config.bin %> auth list -o json"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"auth:list","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","auth","list.js"]},"auth:login":{"aliases":[],"args":{},"description":"Авторизация и сохранение токена","examples":["<%= config.bin %> auth login","<%= config.bin %> auth login --profile personal","<%= config.bin %> auth login --profile ci --token $PACHCA_TOKEN"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile name (default: \"default\")","name":"profile","default":"default","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Token to save (skips prompt)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"auth:login","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","auth","login.js"]},"auth:logout":{"aliases":[],"args":{"profile":{"description":"Profile name to remove","name":"profile","required":false}},"description":"Удаление сохранённого профиля","examples":["<%= config.bin %> auth logout bot-notify","<%= config.bin %> auth logout"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"auth:logout","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","auth","logout.js"]},"auth:status":{"aliases":[],"args":{},"description":"Статус текущего профиля","examples":["<%= config.bin %> auth status","<%= config.bin %> auth status -o json"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"auth:status","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","auth","status.js"]},"auth:switch":{"aliases":[],"args":{"profile":{"description":"Profile name to switch to","name":"profile","required":true}},"description":"Переключение активного профиля","examples":["<%= config.bin %> auth switch bot-support"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"auth:switch","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","auth","switch.js"]},"bots:list-events":{"aliases":[],"args":{},"description":"История событий","examples":["Обработка событий через историю (polling):\n $ pachca bots list-events"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"bots:list-events","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"webhooks:events:read","apiMethod":"GET","apiPath":"/webhooks/events","defaultColumns":["id","created_at","event_type","payload"],"isESM":true,"relativePath":["dist","commands","bots","list-events.js"]},"bots:remove-event":{"aliases":[],"args":{"id":{"description":"Идентификатор события (pachca bots list)","name":"id","required":true}},"description":"Удаление события","examples":["Обработка событий через историю (polling):\n $ pachca bots remove-event"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"bots:remove-event","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"webhooks:events:delete","apiMethod":"DELETE","apiPath":"/webhooks/events/{id}","isESM":true,"relativePath":["dist","commands","bots","remove-event.js"]},"bots:update":{"aliases":[],"args":{"id":{"description":"Идентификатор бота (pachca bots list)","name":"id","required":true}},"description":"Редактирование бота","examples":["Обновить Webhook URL бота:\n $ pachca bots update"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"webhook":{"description":"Объект параметров вебхука","name":"webhook","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"bots:update","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"bots:write","apiMethod":"PUT","apiPath":"/bots/{id}","defaultColumns":["id"],"requiredFlags":["webhook"],"isESM":true,"relativePath":["dist","commands","bots","update.js"]},"changelog":{"aliases":[],"args":{},"description":"История изменений CLI","examples":["<%= config.bin %> changelog","<%= config.bin %> changelog -o json"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"changelog","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","changelog.js"]},"chats:archive":{"aliases":[],"args":{"id":{"description":"Идентификатор чата (pachca chats list)","name":"id","required":true}},"description":"Архивация чата","examples":["Архивация и управление чатом:\n $ pachca chats archive","Найти и заархивировать неактивные чаты:\n $ pachca chats archive"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"chats:archive","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chats:archive","apiMethod":"PUT","apiPath":"/chats/{id}/archive","isESM":true,"relativePath":["dist","commands","chats","archive.js"]},"chats:create":{"aliases":[],"args":{},"description":"Новый чат","examples":["Создать канал и пригласить участников:\n $ pachca chats create","Создать проектную беседу из шаблона:\n $ pachca chats create","Найти активные чаты за период:\n $ pachca chats list"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"name":{"description":"Название","name":"name","hasDynamicHelp":false,"multiple":false,"type":"option"},"member-ids":{"description":"Массив идентификаторов пользователей, которые станут участниками","name":"member-ids","hasDynamicHelp":false,"multiple":false,"type":"option"},"group-tag-ids":{"description":"Массив идентификаторов тегов, которые станут участниками","name":"group-tag-ids","hasDynamicHelp":false,"multiple":false,"type":"option"},"channel":{"description":"Является каналом","name":"channel","allowNo":true,"type":"boolean"},"public":{"description":"Открытый доступ","name":"public","allowNo":true,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"chats:create","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chats:create","apiMethod":"POST","apiPath":"/chats","defaultColumns":["id","name","created_at","owner_id","channel"],"requiredFlags":["name"],"isESM":true,"relativePath":["dist","commands","chats","create.js"]},"chats:get":{"aliases":[],"args":{"id":{"description":"Идентификатор чата (pachca chats list)","name":"id","required":true}},"description":"Информация о чате","examples":["Переименовать или обновить чат:\n $ pachca chats update"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"chats:get","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chats:read","apiMethod":"GET","apiPath":"/chats/{id}","defaultColumns":["id","name","created_at","owner_id","channel"],"isESM":true,"relativePath":["dist","commands","chats","get.js"]},"chats:list":{"aliases":[],"args":{},"description":"Список чатов","examples":["Создать канал и пригласить участников:\n $ pachca chats create","Создать проектную беседу из шаблона:\n $ pachca chats create","Найти активные чаты за период:\n $ pachca chats list"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"sort":{"description":"Поле сортировки","name":"sort","default":"id","hasDynamicHelp":false,"multiple":false,"options":["id","last_message_at"],"type":"option"},"order":{"description":"Направление сортировки","name":"order","default":"desc","hasDynamicHelp":false,"multiple":false,"options":["asc","desc"],"type":"option"},"availability":{"description":"Параметр, который отвечает за доступность и выборку чатов для пользователя","name":"availability","default":"is_member","hasDynamicHelp":false,"multiple":false,"options":["is_member","public"],"type":"option"},"last-message-at-after":{"description":"Фильтрация по времени создания последнего сообщения. Будут возвращены те чаты, время последнего созданного сообщения в которых не раньше чем указанное (в формате YYYY-MM-DDThh:mm:ss.sssZ).","name":"last-message-at-after","hasDynamicHelp":false,"multiple":false,"type":"option"},"last-message-at-before":{"description":"Фильтрация по времени создания последнего сообщения. Будут возвращены те чаты, время последнего созданного сообщения в которых не позже чем указанное (в формате YYYY-MM-DDThh:mm:ss.sssZ).","name":"last-message-at-before","hasDynamicHelp":false,"multiple":false,"type":"option"},"personal":{"description":"Фильтрация по личным и групповым чатам. Если параметр не указан, возвращаются любые чаты.","name":"personal","allowNo":true,"type":"boolean"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"chats:list","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chats:read","apiMethod":"GET","apiPath":"/chats","defaultColumns":["id","name","created_at","owner_id","channel"],"isESM":true,"relativePath":["dist","commands","chats","list.js"]},"chats:unarchive":{"aliases":[],"args":{"id":{"description":"Идентификатор чата (pachca chats list)","name":"id","required":true}},"description":"Разархивация чата","examples":["Архивация и управление чатом:\n $ pachca chats unarchive"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"chats:unarchive","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chats:archive","apiMethod":"PUT","apiPath":"/chats/{id}/unarchive","isESM":true,"relativePath":["dist","commands","chats","unarchive.js"]},"chats:update":{"aliases":[],"args":{"id":{"description":"Идентификатор чата (pachca chats list)","name":"id","required":true}},"description":"Обновление чата","examples":["Переименовать или обновить чат:\n $ pachca chats update"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"name":{"description":"Название","name":"name","hasDynamicHelp":false,"multiple":false,"type":"option"},"public":{"description":"Открытый доступ","name":"public","allowNo":true,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"chats:update","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chats:update","apiMethod":"PUT","apiPath":"/chats/{id}","defaultColumns":["id","name","created_at","owner_id","channel"],"isESM":true,"relativePath":["dist","commands","chats","update.js"]},"commands":{"aliases":[],"args":{},"description":"Список всех команд","examples":["<%= config.bin %> commands","<%= config.bin %> commands --available","<%= config.bin %> commands --available -o json"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"available":{"description":"Show only commands available to current token","name":"available","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"commands","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","commands.js"]},"common:custom-properties":{"aliases":[],"args":{},"description":"Список дополнительных полей","examples":["Получить кастомные поля профиля:\n $ pachca common custom-properties"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"entity-type":{"description":"Тип сущности","name":"entity-type","hasDynamicHelp":false,"multiple":false,"options":["User","Task"],"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"common:custom-properties","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"custom_properties:read","apiMethod":"GET","apiPath":"/custom_properties","defaultColumns":["id","name","data_type"],"requiredFlags":["entity-type"],"isESM":true,"relativePath":["dist","commands","common","custom-properties.js"]},"common:direct-url":{"aliases":[],"args":{},"description":"Загрузка файла","flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"direct-url":{"description":"URL для отправки запроса (получается из ответа POST /uploads)","name":"direct-url","required":true,"hasDynamicHelp":false,"multiple":false,"type":"option"},"content-disposition":{"description":"Параметр Content-Disposition, полученный в ответе на запрос [Получение подписи, ключа и других параметров](POST /uploads)","name":"content-disposition","hasDynamicHelp":false,"multiple":false,"type":"option"},"acl":{"description":"Параметр acl, полученный в ответе на запрос [Получение подписи, ключа и других параметров](POST /uploads)","name":"acl","hasDynamicHelp":false,"multiple":false,"type":"option"},"policy":{"description":"Параметр policy, полученный в ответе на запрос [Получение подписи, ключа и других параметров](POST /uploads)","name":"policy","hasDynamicHelp":false,"multiple":false,"type":"option"},"x-amz-credential":{"description":"Параметр x-amz-credential, полученный в ответе на запрос [Получение подписи, ключа и других параметров](POST /uploads)","name":"x-amz-credential","hasDynamicHelp":false,"multiple":false,"type":"option"},"x-amz-algorithm":{"description":"Параметр x-amz-algorithm, полученный в ответе на запрос [Получение подписи, ключа и других параметров](POST /uploads)","name":"x-amz-algorithm","hasDynamicHelp":false,"multiple":false,"type":"option"},"x-amz-date":{"description":"Параметр x-amz-date, полученный в ответе на запрос [Получение подписи, ключа и других параметров](POST /uploads)","name":"x-amz-date","hasDynamicHelp":false,"multiple":false,"type":"option"},"x-amz-signature":{"description":"Параметр x-amz-signature, полученный в ответе на запрос [Получение подписи, ключа и других параметров](POST /uploads)","name":"x-amz-signature","hasDynamicHelp":false,"multiple":false,"type":"option"},"key":{"description":"Параметр key, полученный в ответе на запрос [Получение подписи, ключа и других параметров](POST /uploads)","name":"key","hasDynamicHelp":false,"multiple":false,"type":"option"},"file":{"description":"Файл для загрузки","name":"file","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"common:direct-url","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"apiMethod":"POST","apiPath":"/direct_url","requiredFlags":["content-disposition","acl","policy","x-amz-credential","x-amz-algorithm","x-amz-date","x-amz-signature","key"],"isESM":true,"relativePath":["dist","commands","common","direct-url.js"]},"common:get-exports":{"aliases":[],"args":{"id":{"description":"Идентификатор экспорта","name":"id","required":true}},"description":"Скачать архив экспорта","examples":["Экспорт истории чата:\n $ pachca common get-exports"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"save":{"description":"Путь для сохранения файла","name":"save","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"common:get-exports","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chat_exports:read","plan":"corporation","apiMethod":"GET","apiPath":"/chats/exports/{id}","isESM":true,"relativePath":["dist","commands","common","get-exports.js"]},"common:request-export":{"aliases":[],"args":{},"description":"Экспорт сообщений","examples":["Экспорт истории чата:\n $ pachca common request-export"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"start-at":{"description":"Дата начала для экспорта (ISO-8601, UTC+0) в формате YYYY-MM-DD","name":"start-at","hasDynamicHelp":false,"multiple":false,"type":"option"},"end-at":{"description":"Дата окончания для экспорта (ISO-8601, UTC+0) в формате YYYY-MM-DD","name":"end-at","hasDynamicHelp":false,"multiple":false,"type":"option"},"webhook-url":{"description":"Адрес, на который будет отправлен вебхук по завершению экспорта","name":"webhook-url","hasDynamicHelp":false,"multiple":false,"type":"option"},"chat-ids":{"description":"Массив идентификаторов чатов. Указывается, если нужно получить сообщения только некоторых чатов.","name":"chat-ids","hasDynamicHelp":false,"multiple":false,"type":"option"},"skip-chats-file":{"description":"Пропуск формирования файла со списком чатов (chats.json)","name":"skip-chats-file","allowNo":true,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"common:request-export","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chat_exports:write","plan":"corporation","apiMethod":"POST","apiPath":"/chats/exports","requiredFlags":["start-at","end-at","webhook-url"],"isESM":true,"relativePath":["dist","commands","common","request-export.js"]},"common:uploads":{"aliases":[],"args":{},"description":"Получение подписи, ключа и других параметров","examples":["Изменить вложения сообщения:\n $ pachca common uploads"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"common:uploads","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"uploads:write","apiMethod":"POST","apiPath":"/uploads","defaultColumns":["Content-Disposition","acl","policy","x-amz-credential","x-amz-algorithm"],"isESM":true,"relativePath":["dist","commands","common","uploads.js"]},"config:get":{"aliases":[],"args":{"key":{"description":"Configuration key","name":"key","required":true}},"description":"Получение значения конфигурации","examples":["<%= config.bin %> config get defaults.output"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"config:get","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","config","get.js"]},"config:list":{"aliases":[],"args":{},"description":"Список всех настроек","examples":["<%= config.bin %> config list","<%= config.bin %> config list -o json"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"config:list","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","config","list.js"]},"config:set":{"aliases":[],"args":{"key":{"description":"Configuration key","name":"key","required":true},"value":{"description":"Configuration value","name":"value","required":true}},"description":"Установка значения конфигурации","examples":["<%= config.bin %> config set defaults.output json","<%= config.bin %> config set defaults.timeout 60"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"config:set","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","config","set.js"]},"doctor":{"aliases":[],"args":{},"description":"Диагностика окружения: Node.js, сеть, токен, конфигурация","examples":["<%= config.bin %> doctor","<%= config.bin %> doctor -o json"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"doctor","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","doctor.js"]},"group-tags:create":{"aliases":[],"args":{},"description":"Новый тег","examples":["Массовое создание сотрудников с тегами:\n $ pachca group-tags create","Получить всех сотрудников тега/департамента:\n $ pachca group-tags list"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"name":{"description":"Название тега","name":"name","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"group-tags:create","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"group_tags:write","apiMethod":"POST","apiPath":"/group_tags","defaultColumns":["id","name","users_count"],"requiredFlags":["name"],"isESM":true,"relativePath":["dist","commands","group-tags","create.js"]},"group-tags:delete":{"aliases":[],"args":{"id":{"description":"Идентификатор тега","name":"id","required":true}},"description":"Удаление тега","flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"group-tags:delete","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"group_tags:write","apiMethod":"DELETE","apiPath":"/group_tags/{id}","isESM":true,"relativePath":["dist","commands","group-tags","delete.js"]},"group-tags:get":{"aliases":[],"args":{"id":{"description":"Идентификатор тега","name":"id","required":true}},"description":"Информация о теге","flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"group-tags:get","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"group_tags:read","apiMethod":"GET","apiPath":"/group_tags/{id}","defaultColumns":["id","name","users_count"],"isESM":true,"relativePath":["dist","commands","group-tags","get.js"]},"group-tags:list":{"aliases":[],"args":{},"description":"Список тегов сотрудников","examples":["Массовое создание сотрудников с тегами:\n $ pachca group-tags create","Получить всех сотрудников тега/департамента:\n $ pachca group-tags list"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"names":{"description":"Массив названий тегов, по которым вы хотите отфильтровать список (через запятую)","name":"names","hasDynamicHelp":false,"multiple":false,"type":"option"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"group-tags:list","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"group_tags:read","apiMethod":"GET","apiPath":"/group_tags","defaultColumns":["id","name","users_count"],"isESM":true,"relativePath":["dist","commands","group-tags","list.js"]},"group-tags:list-users":{"aliases":[],"args":{"id":{"description":"Идентификатор тега","name":"id","required":true}},"description":"Список сотрудников тега","examples":["Получить всех сотрудников тега/департамента:\n $ pachca group-tags list-users"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"group-tags:list-users","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"group_tags:read","apiMethod":"GET","apiPath":"/group_tags/{id}/users","defaultColumns":["id","title","first_name","last_name","email"],"isESM":true,"relativePath":["dist","commands","group-tags","list-users.js"]},"group-tags:update":{"aliases":[],"args":{"id":{"description":"Идентификатор тега","name":"id","required":true}},"description":"Редактирование тега","flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"name":{"description":"Название тега","name":"name","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"group-tags:update","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"group_tags:write","apiMethod":"PUT","apiPath":"/group_tags/{id}","defaultColumns":["id","name","users_count"],"requiredFlags":["name"],"isESM":true,"relativePath":["dist","commands","group-tags","update.js"]},"guide":{"aliases":[],"args":{"query":{"description":"Search query","name":"query","required":false}},"description":"Поиск сценариев использования","examples":["<%= config.bin %> guide \"отправить файл\"","<%= config.bin %> guide \"создать бота\"","<%= config.bin %> guide"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"guide","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","guide.js"]},"introspect":{"aliases":[],"args":{"command":{"description":"Command name (e.g., \"messages create\")","name":"command","required":false}},"description":"Метаданные команды в машиночитаемом формате","examples":["<%= config.bin %> introspect messages create","<%= config.bin %> introspect"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"introspect","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":false,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","introspect.js"]},"link-previews:add":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения","name":"id","required":true}},"description":"Unfurl (разворачивание ссылок)","examples":["Разворачивание ссылок (unfurling):\n $ pachca link-previews add"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"link-previews":{"description":"`JSON` карта предпросмотров ссылок, где каждый ключ — `URL`, который был получен в исходящем вебхуке о новом сообщении.","name":"link-previews","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"link-previews:add","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"link_previews:write","apiMethod":"POST","apiPath":"/messages/{id}/link_previews","requiredFlags":["link-previews"],"isESM":true,"relativePath":["dist","commands","link-previews","add.js"]},"members:add":{"aliases":[],"args":{"id":{"description":"Идентификатор чата (беседа, канал или чат треда)","name":"id","required":true}},"description":"Добавление пользователей","examples":["Подписаться на тред сообщения:\n $ pachca members add","Упомянуть пользователя по имени:\n $ pachca members list","Создать канал и пригласить участников:\n $ pachca members add"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"member-ids":{"description":"Массив идентификаторов пользователей, которые станут участниками","name":"member-ids","hasDynamicHelp":false,"multiple":false,"type":"option"},"silent":{"description":"Не создавать в чате системное сообщение о добавлении участника","name":"silent","allowNo":true,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"members:add","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chat_members:write","apiMethod":"POST","apiPath":"/chats/{id}/members","requiredFlags":["member-ids"],"isESM":true,"relativePath":["dist","commands","members","add.js"]},"members:add-group-tags":{"aliases":[],"args":{"id":{"description":"Идентификатор чата","name":"id","required":true}},"description":"Добавление тегов","flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"group-tag-ids":{"description":"Массив идентификаторов тегов, которые станут участниками","name":"group-tag-ids","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"members:add-group-tags","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chat_members:write","apiMethod":"POST","apiPath":"/chats/{id}/group_tags","requiredFlags":["group-tag-ids"],"isESM":true,"relativePath":["dist","commands","members","add-group-tags.js"]},"members:leave":{"aliases":[],"args":{"id":{"description":"Идентификатор чата","name":"id","required":true}},"description":"Выход из беседы или канала","examples":["Архивация и управление чатом:\n $ pachca members leave"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"members:leave","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chats:leave","apiMethod":"DELETE","apiPath":"/chats/{id}/leave","isESM":true,"relativePath":["dist","commands","members","leave.js"]},"members:list":{"aliases":[],"args":{"id":{"description":"Идентификатор чата","name":"id","required":true}},"description":"Список участников чата","examples":["Подписаться на тред сообщения:\n $ pachca members add","Упомянуть пользователя по имени:\n $ pachca members list","Создать канал и пригласить участников:\n $ pachca members add"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"role":{"description":"Роль в чате","name":"role","default":"all","hasDynamicHelp":false,"multiple":false,"options":["all","owner","admin","editor","member"],"type":"option"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"members:list","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chat_members:read","apiMethod":"GET","apiPath":"/chats/{id}/members","defaultColumns":["id","title","first_name","last_name","email"],"isESM":true,"relativePath":["dist","commands","members","list.js"]},"members:remove":{"aliases":[],"args":{"id":{"description":"Идентификатор чата","name":"id","required":true},"user_id":{"description":"Идентификатор пользователя (pachca users list)","name":"user_id","required":true}},"description":"Исключение пользователя","examples":["Архивация и управление чатом:\n $ pachca members update","Архивация и управление чатом:\n $ pachca members remove"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"members:remove","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chat_members:write","apiMethod":"DELETE","apiPath":"/chats/{id}/members/{user_id}","isESM":true,"relativePath":["dist","commands","members","remove.js"]},"members:remove-group-tag":{"aliases":[],"args":{"id":{"description":"Идентификатор чата","name":"id","required":true},"tag_id":{"description":"Идентификатор тега (pachca tags list)","name":"tag_id","required":true}},"description":"Исключение тега","flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"members:remove-group-tag","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chat_members:write","apiMethod":"DELETE","apiPath":"/chats/{id}/group_tags/{tag_id}","isESM":true,"relativePath":["dist","commands","members","remove-group-tag.js"]},"members:update":{"aliases":[],"args":{"id":{"description":"Идентификатор чата","name":"id","required":true},"user_id":{"description":"Идентификатор пользователя (pachca users list)","name":"user_id","required":true}},"description":"Редактирование роли","examples":["Архивация и управление чатом:\n $ pachca members update","Архивация и управление чатом:\n $ pachca members remove"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"role":{"description":"Роль","name":"role","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"members:update","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"chat_members:write","apiMethod":"PUT","apiPath":"/chats/{id}/members/{user_id}","requiredFlags":["role"],"isESM":true,"relativePath":["dist","commands","members","update.js"]},"messages:create":{"aliases":[],"args":{},"description":"Новое сообщение","examples":["Найти чат по имени и отправить сообщение:\n $ pachca messages create","Отправить сообщение в канал или беседу (если chat_id известен):\n $ pachca messages create","Отправить личное сообщение пользователю:\n $ pachca messages create"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"entity-type":{"description":"Тип сущности","name":"entity-type","hasDynamicHelp":false,"multiple":false,"type":"option"},"entity-id":{"description":"Идентификатор сущности (pachca chats list | pachca users list)","name":"entity-id","hasDynamicHelp":false,"multiple":false,"type":"option"},"content":{"description":"Текст сообщения","name":"content","hasDynamicHelp":false,"multiple":false,"type":"option"},"files":{"description":"Прикрепляемые файлы","name":"files","hasDynamicHelp":false,"multiple":false,"type":"option"},"buttons":{"description":"Массив строк, каждая из которых представлена массивом кнопок. Максимум 100 кнопок у сообщения, до 8 кнопок в строке.","name":"buttons","hasDynamicHelp":false,"multiple":false,"type":"option"},"parent-message-id":{"description":"Идентификатор сообщения. Указывается в случае, если вы отправляете ответ на другое сообщение.","name":"parent-message-id","hasDynamicHelp":false,"multiple":false,"type":"option"},"display-avatar-url":{"description":"Ссылка на специальную аватарку отправителя для этого сообщения. Использование этого поля возможно только с access_token бота. (макс. 255 символов)","name":"display-avatar-url","hasDynamicHelp":false,"multiple":false,"type":"option"},"display-name":{"description":"Полное специальное имя отправителя для этого сообщения. Использование этого поля возможно только с access_token бота. (макс. 255 символов)","name":"display-name","hasDynamicHelp":false,"multiple":false,"type":"option"},"skip-invite-mentions":{"description":"Пропуск добавления упоминаемых пользователей в тред. Работает только при отправке сообщения в тред.","name":"skip-invite-mentions","allowNo":true,"type":"boolean"},"link-preview":{"description":"Отображение предпросмотра первой найденной ссылки в тексте сообщения","name":"link-preview","allowNo":true,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"messages:create","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"messages:create","apiMethod":"POST","apiPath":"/messages","defaultColumns":["id","content","created_at","entity_type","entity_id"],"requiredFlags":["entity-id","content"],"isESM":true,"relativePath":["dist","commands","messages","create.js"]},"messages:delete":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения (pachca messages list)","name":"id","required":true}},"description":"Удаление сообщения","examples":["Получить вложения из сообщения:\n $ pachca messages get","Отредактировать сообщение:\n $ pachca messages update","Изменить вложения сообщения:\n $ pachca messages get"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"messages:delete","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"messages:delete","apiMethod":"DELETE","apiPath":"/messages/{id}","isESM":true,"relativePath":["dist","commands","messages","delete.js"]},"messages:get":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения (pachca messages list)","name":"id","required":true}},"description":"Информация о сообщении","examples":["Получить вложения из сообщения:\n $ pachca messages get","Отредактировать сообщение:\n $ pachca messages update","Изменить вложения сообщения:\n $ pachca messages get"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"messages:get","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"messages:read","apiMethod":"GET","apiPath":"/messages/{id}","defaultColumns":["id","content","created_at","entity_type","entity_id"],"isESM":true,"relativePath":["dist","commands","messages","get.js"]},"messages:list":{"aliases":[],"args":{"chat_id":{"description":"Идентификатор чата (беседа, канал, диалог или чат треда)","name":"chat_id","required":false}},"description":"Список сообщений чата","examples":["Найти чат по имени и отправить сообщение:\n $ pachca messages create","Отправить сообщение в канал или беседу (если chat_id известен):\n $ pachca messages create","Отправить личное сообщение пользователю:\n $ pachca messages create"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"chat-id":{"description":"Идентификатор чата (беседа, канал, диалог или чат треда)","name":"chat-id","hasDynamicHelp":false,"multiple":false,"type":"option"},"sort":{"description":"Поле сортировки","name":"sort","default":"id","hasDynamicHelp":false,"multiple":false,"options":["id"],"type":"option"},"order":{"description":"Направление сортировки","name":"order","default":"desc","hasDynamicHelp":false,"multiple":false,"options":["asc","desc"],"type":"option"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"messages:list","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"messages:read","apiMethod":"GET","apiPath":"/messages","defaultColumns":["id","content","created_at","entity_type","entity_id"],"requiredFlags":["chat-id"],"isESM":true,"relativePath":["dist","commands","messages","list.js"]},"messages:pin":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения (pachca messages list)","name":"id","required":true}},"description":"Закрепление сообщения","examples":["Закрепить/открепить сообщение:\n $ pachca messages pin","Закрепить/открепить сообщение:\n $ pachca messages unpin"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"messages:pin","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"pins:write","apiMethod":"POST","apiPath":"/messages/{id}/pin","isESM":true,"relativePath":["dist","commands","messages","pin.js"]},"messages:unpin":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения (pachca messages list)","name":"id","required":true}},"description":"Открепление сообщения","examples":["Закрепить/открепить сообщение:\n $ pachca messages pin","Закрепить/открепить сообщение:\n $ pachca messages unpin"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"messages:unpin","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"pins:write","apiMethod":"DELETE","apiPath":"/messages/{id}/pin","isESM":true,"relativePath":["dist","commands","messages","unpin.js"]},"messages:update":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения (pachca messages list)","name":"id","required":true}},"description":"Редактирование сообщения","examples":["Получить вложения из сообщения:\n $ pachca messages get","Отредактировать сообщение:\n $ pachca messages update","Изменить вложения сообщения:\n $ pachca messages get"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"content":{"description":"Текст сообщения","name":"content","hasDynamicHelp":false,"multiple":false,"type":"option"},"files":{"description":"Прикрепляемые файлы","name":"files","hasDynamicHelp":false,"multiple":false,"type":"option"},"buttons":{"description":"Массив строк, каждая из которых представлена массивом кнопок. Максимум 100 кнопок у сообщения, до 8 кнопок в строке. Для удаления кнопок пришлите пустой массив.","name":"buttons","hasDynamicHelp":false,"multiple":false,"type":"option"},"display-avatar-url":{"description":"Ссылка на специальную аватарку отправителя для этого сообщения. Использование этого поля возможно только с access_token бота.","name":"display-avatar-url","hasDynamicHelp":false,"multiple":false,"type":"option"},"display-name":{"description":"Полное специальное имя отправителя для этого сообщения. Использование этого поля возможно только с access_token бота.","name":"display-name","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"messages:update","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"messages:update","apiMethod":"PUT","apiPath":"/messages/{id}","defaultColumns":["id","content","created_at","entity_type","entity_id"],"isESM":true,"relativePath":["dist","commands","messages","update.js"]},"profile:delete-avatar":{"aliases":[],"args":{},"description":"Удаление аватара","examples":["Загрузить аватар профиля:\n $ pachca profile update-avatar","Удалить аватар профиля:\n $ pachca profile delete-avatar"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"profile:delete-avatar","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"profile_avatar:write","apiMethod":"DELETE","apiPath":"/profile/avatar","isESM":true,"relativePath":["dist","commands","profile","delete-avatar.js"]},"profile:delete-status":{"aliases":[],"args":{},"description":"Удаление статуса","examples":["Установить статус:\n $ pachca profile update-status","Сбросить статус:\n $ pachca profile delete-status"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"profile:delete-status","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"profile_status:write","apiMethod":"DELETE","apiPath":"/profile/status","isESM":true,"relativePath":["dist","commands","profile","delete-status.js"]},"profile:get":{"aliases":[],"args":{},"description":"Информация о профиле","examples":["Получить свой профиль:\n $ pachca profile get","Получить кастомные поля профиля:\n $ pachca profile get"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"profile:get","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"profile:read","apiMethod":"GET","apiPath":"/profile","defaultColumns":["id","title","first_name","last_name","email"],"isESM":true,"relativePath":["dist","commands","profile","get.js"]},"profile:get-info":{"aliases":[],"args":{},"description":"Информация о токене","examples":["Проверить свой токен:\n $ pachca profile get-info"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"profile:get-info","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"apiMethod":"GET","apiPath":"/oauth/token/info","defaultColumns":["id","name","created_at","token","user_id"],"isESM":true,"relativePath":["dist","commands","profile","get-info.js"]},"profile:get-status":{"aliases":[],"args":{},"description":"Текущий статус","examples":["Установить статус:\n $ pachca profile update-status","Сбросить статус:\n $ pachca profile delete-status"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"profile:get-status","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"profile_status:read","apiMethod":"GET","apiPath":"/profile/status","defaultColumns":["title","emoji","expires_at","is_away"],"isESM":true,"relativePath":["dist","commands","profile","get-status.js"]},"profile:update-avatar":{"aliases":[],"args":{},"description":"Загрузка аватара","examples":["Загрузить аватар профиля:\n $ pachca profile update-avatar","Удалить аватар профиля:\n $ pachca profile delete-avatar"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"file":{"description":"Файл изображения для аватара","name":"file","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"profile:update-avatar","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"profile_avatar:write","apiMethod":"PUT","apiPath":"/profile/avatar","defaultColumns":["image_url"],"isESM":true,"relativePath":["dist","commands","profile","update-avatar.js"]},"profile:update-status":{"aliases":[],"args":{},"description":"Новый статус","examples":["Установить статус:\n $ pachca profile update-status","Сбросить статус:\n $ pachca profile delete-status"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"emoji":{"description":"Emoji символ статуса","name":"emoji","hasDynamicHelp":false,"multiple":false,"type":"option"},"title":{"description":"Текст статуса","name":"title","hasDynamicHelp":false,"multiple":false,"type":"option"},"expires-at":{"description":"Срок жизни статуса (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ","name":"expires-at","hasDynamicHelp":false,"multiple":false,"type":"option"},"is-away":{"description":"Режим «Нет на месте»","name":"is-away","allowNo":true,"type":"boolean"},"away-message":{"description":"Текст сообщения при режиме «Нет на месте». Отображается в профиле и при личных сообщениях/упоминаниях. (макс. 1024 символов)","name":"away-message","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"profile:update-status","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"profile_status:write","apiMethod":"PUT","apiPath":"/profile/status","defaultColumns":["title","emoji","expires_at","is_away"],"requiredFlags":["emoji","title"],"isESM":true,"relativePath":["dist","commands","profile","update-status.js"]},"reactions:add":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения","name":"id","required":true}},"description":"Добавление реакции","examples":["Добавить реакцию на сообщение:\n $ pachca reactions add","Добавить реакцию на сообщение:\n $ pachca reactions remove"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"code":{"description":"Emoji символ реакции","name":"code","hasDynamicHelp":false,"multiple":false,"type":"option"},"name":{"description":"Текстовое имя эмодзи (используется для кастомных эмодзи)","name":"name","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"reactions:add","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"reactions:write","apiMethod":"POST","apiPath":"/messages/{id}/reactions","defaultColumns":["name","created_at","user_id","code"],"requiredFlags":["code"],"isESM":true,"relativePath":["dist","commands","reactions","add.js"]},"reactions:list":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения","name":"id","required":true}},"description":"Список реакций","examples":["Добавить реакцию на сообщение:\n $ pachca reactions add","Добавить реакцию на сообщение:\n $ pachca reactions remove"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"reactions:list","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"reactions:read","apiMethod":"GET","apiPath":"/messages/{id}/reactions","defaultColumns":["name","created_at","user_id","code"],"isESM":true,"relativePath":["dist","commands","reactions","list.js"]},"reactions:remove":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения","name":"id","required":true}},"description":"Удаление реакции","examples":["Добавить реакцию на сообщение:\n $ pachca reactions add","Добавить реакцию на сообщение:\n $ pachca reactions remove"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"code":{"description":"Emoji символ реакции","name":"code","hasDynamicHelp":false,"multiple":false,"type":"option"},"name":{"description":"Текстовое имя эмодзи (используется для кастомных эмодзи)","name":"name","hasDynamicHelp":false,"multiple":false,"type":"option"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"reactions:remove","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"reactions:write","apiMethod":"DELETE","apiPath":"/messages/{id}/reactions","requiredFlags":["code"],"isESM":true,"relativePath":["dist","commands","reactions","remove.js"]},"read-member:list-readers":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения","name":"id","required":true}},"description":"Список прочитавших сообщение","examples":["Проверить, кто прочитал сообщение:\n $ pachca read-member list-readers"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"read-member:list-readers","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"messages:read","apiMethod":"GET","apiPath":"/messages/{id}/read_member_ids","isESM":true,"relativePath":["dist","commands","read-member","list-readers.js"]},"search:list-chats":{"aliases":[],"args":{},"description":"Поиск чатов","examples":["Найти чат по имени и отправить сообщение:\n $ pachca search list-chats","Найти чат по названию:\n $ pachca search list-chats"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"query":{"description":"Текст поискового запроса","name":"query","hasDynamicHelp":false,"multiple":false,"type":"option"},"order":{"description":"Направление сортировки","name":"order","hasDynamicHelp":false,"multiple":false,"options":["asc","desc"],"type":"option"},"created-from":{"description":"Фильтр по дате создания (от)","name":"created-from","hasDynamicHelp":false,"multiple":false,"type":"option"},"created-to":{"description":"Фильтр по дате создания (до)","name":"created-to","hasDynamicHelp":false,"multiple":false,"type":"option"},"active":{"description":"Фильтр по активности чата","name":"active","allowNo":true,"type":"boolean"},"chat-subtype":{"description":"Фильтр по типу чата","name":"chat-subtype","hasDynamicHelp":false,"multiple":false,"options":["discussion","thread"],"type":"option"},"personal":{"description":"Фильтр по личным чатам","name":"personal","allowNo":true,"type":"boolean"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"search:list-chats","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"search:chats","apiMethod":"GET","apiPath":"/search/chats","defaultColumns":["id","name","created_at","owner_id","channel"],"isESM":true,"relativePath":["dist","commands","search","list-chats.js"]},"search:list-messages":{"aliases":[],"args":{},"description":"Поиск сообщений","examples":["Найти сообщение по тексту:\n $ pachca search list-messages"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"query":{"description":"Текст поискового запроса","name":"query","hasDynamicHelp":false,"multiple":false,"type":"option"},"order":{"description":"Направление сортировки","name":"order","hasDynamicHelp":false,"multiple":false,"options":["asc","desc"],"type":"option"},"created-from":{"description":"Фильтр по дате создания (от)","name":"created-from","hasDynamicHelp":false,"multiple":false,"type":"option"},"created-to":{"description":"Фильтр по дате создания (до)","name":"created-to","hasDynamicHelp":false,"multiple":false,"type":"option"},"chat-ids":{"description":"Фильтр по ID чатов (через запятую)","name":"chat-ids","hasDynamicHelp":false,"multiple":false,"type":"option"},"user-ids":{"description":"Фильтр по ID авторов сообщений (через запятую)","name":"user-ids","hasDynamicHelp":false,"multiple":false,"type":"option"},"active":{"description":"Фильтр по активности чата","name":"active","allowNo":true,"type":"boolean"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"search:list-messages","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"search:messages","apiMethod":"GET","apiPath":"/search/messages","defaultColumns":["id","content","created_at","entity_type","entity_id"],"isESM":true,"relativePath":["dist","commands","search","list-messages.js"]},"search:list-users":{"aliases":[],"args":{},"description":"Поиск сотрудников","examples":["Отправить личное сообщение пользователю:\n $ pachca search list-users","Упомянуть пользователя по имени:\n $ pachca search list-users","Найти сотрудника по имени:\n $ pachca search list-users"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"query":{"description":"Текст поискового запроса","name":"query","hasDynamicHelp":false,"multiple":false,"type":"option"},"sort":{"description":"Сортировка результатов","name":"sort","hasDynamicHelp":false,"multiple":false,"options":["by_score","alphabetical"],"type":"option"},"order":{"description":"Направление сортировки","name":"order","hasDynamicHelp":false,"multiple":false,"options":["asc","desc"],"type":"option"},"created-from":{"description":"Фильтр по дате создания (от)","name":"created-from","hasDynamicHelp":false,"multiple":false,"type":"option"},"created-to":{"description":"Фильтр по дате создания (до)","name":"created-to","hasDynamicHelp":false,"multiple":false,"type":"option"},"company-roles":{"description":"Фильтр по ролям сотрудников (через запятую)","name":"company-roles","hasDynamicHelp":false,"multiple":false,"type":"option"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"search:list-users","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"search:users","apiMethod":"GET","apiPath":"/search/users","defaultColumns":["id","title","first_name","last_name","email"],"isESM":true,"relativePath":["dist","commands","search","list-users.js"]},"security:list":{"aliases":[],"args":{},"description":"Журнал аудита событий","examples":["Получить журнал аудита событий:\n $ pachca security list","Мониторинг подозрительных входов:\n $ pachca security list","Экспорт логов за период:\n $ pachca security list"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"start-time":{"description":"Начальная метка времени (включительно)","name":"start-time","hasDynamicHelp":false,"multiple":false,"type":"option"},"end-time":{"description":"Конечная метка времени (исключительно)","name":"end-time","hasDynamicHelp":false,"multiple":false,"type":"option"},"event-key":{"description":"Фильтр по конкретному типу события","name":"event-key","hasDynamicHelp":false,"multiple":false,"options":["user_login","user_logout","user_2fa_fail","user_2fa_success","user_created","user_deleted","user_role_changed","user_updated","tag_created","tag_deleted","user_added_to_tag","user_removed_from_tag","chat_created","chat_renamed","chat_permission_changed","user_chat_join","user_chat_leave","tag_added_to_chat","tag_removed_from_chat","message_updated","message_deleted","message_created","reaction_created","reaction_deleted","thread_created","access_token_created","access_token_updated","access_token_destroy","kms_encrypt","kms_decrypt","audit_events_accessed","dlp_violation_detected","search_users_api","search_chats_api","search_messages_api"],"type":"option"},"actor-id":{"description":"Идентификатор пользователя, выполнившего действие","name":"actor-id","hasDynamicHelp":false,"multiple":false,"type":"option"},"actor-type":{"description":"Тип актора","name":"actor-type","hasDynamicHelp":false,"multiple":false,"type":"option"},"entity-id":{"description":"Идентификатор затронутой сущности","name":"entity-id","hasDynamicHelp":false,"multiple":false,"type":"option"},"entity-type":{"description":"Тип сущности","name":"entity-type","hasDynamicHelp":false,"multiple":false,"type":"option"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"security:list","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"audit_events:read","plan":"corporation","apiMethod":"GET","apiPath":"/audit_events","defaultColumns":["id","created_at","event_key","entity_id","entity_type"],"isESM":true,"relativePath":["dist","commands","security","list.js"]},"tasks:create":{"aliases":[],"args":{},"description":"Новое напоминание","examples":["Форма заявки/запроса:\n $ pachca tasks create","Создать напоминание:\n $ pachca tasks create","Получить список предстоящих задач:\n $ pachca tasks list"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"kind":{"description":"Тип","name":"kind","hasDynamicHelp":false,"multiple":false,"type":"option"},"content":{"description":"Описание (по умолчанию — название типа)","name":"content","hasDynamicHelp":false,"multiple":false,"type":"option"},"due-at":{"description":"Срок выполнения напоминания (ISO-8601) в формате YYYY-MM-DDThh:mm:ss.sssTZD. Если указано время 23:59:59.000, то напоминание будет создано на весь день (без указания времени).","name":"due-at","hasDynamicHelp":false,"multiple":false,"type":"option"},"priority":{"description":"Приоритет: 1, 2 (важно) или 3 (очень важно).","name":"priority","hasDynamicHelp":false,"multiple":false,"type":"option"},"performer-ids":{"description":"Массив идентификаторов пользователей, привязываемых к напоминанию как «ответственные» (по умолчанию ответственным назначается вы)","name":"performer-ids","hasDynamicHelp":false,"multiple":false,"type":"option"},"chat-id":{"description":"Идентификатор чата, к которому привязывается напоминание (pachca chats list)","name":"chat-id","hasDynamicHelp":false,"multiple":false,"type":"option"},"all-day":{"description":"Напоминание на весь день (без указания времени)","name":"all-day","allowNo":true,"type":"boolean"},"custom-properties":{"description":"Задаваемые дополнительные поля","name":"custom-properties","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"tasks:create","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"tasks:create","apiMethod":"POST","apiPath":"/tasks","defaultColumns":["id","content","created_at","kind","due_at"],"requiredFlags":["kind"],"isESM":true,"relativePath":["dist","commands","tasks","create.js"]},"tasks:delete":{"aliases":[],"args":{"id":{"description":"Идентификатор напоминания (pachca tasks list)","name":"id","required":true}},"description":"Удаление напоминания","examples":["Получить задачу по ID:\n $ pachca tasks get","Отметить задачу выполненной:\n $ pachca tasks update","Обновить задачу (перенести срок, сменить ответственных):\n $ pachca tasks update"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"tasks:delete","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"tasks:delete","apiMethod":"DELETE","apiPath":"/tasks/{id}","isESM":true,"relativePath":["dist","commands","tasks","delete.js"]},"tasks:get":{"aliases":[],"args":{"id":{"description":"Идентификатор напоминания (pachca tasks list)","name":"id","required":true}},"description":"Информация о напоминании","examples":["Получить задачу по ID:\n $ pachca tasks get","Отметить задачу выполненной:\n $ pachca tasks update","Обновить задачу (перенести срок, сменить ответственных):\n $ pachca tasks update"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"tasks:get","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"tasks:read","apiMethod":"GET","apiPath":"/tasks/{id}","defaultColumns":["id","content","created_at","kind","due_at"],"isESM":true,"relativePath":["dist","commands","tasks","get.js"]},"tasks:list":{"aliases":[],"args":{},"description":"Список напоминаний","examples":["Форма заявки/запроса:\n $ pachca tasks create","Создать напоминание:\n $ pachca tasks create","Получить список предстоящих задач:\n $ pachca tasks list"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"tasks:list","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"tasks:read","apiMethod":"GET","apiPath":"/tasks","defaultColumns":["id","content","created_at","kind","due_at"],"isESM":true,"relativePath":["dist","commands","tasks","list.js"]},"tasks:update":{"aliases":[],"args":{"id":{"description":"Идентификатор напоминания (pachca tasks list)","name":"id","required":true}},"description":"Редактирование напоминания","examples":["Получить задачу по ID:\n $ pachca tasks get","Отметить задачу выполненной:\n $ pachca tasks update","Обновить задачу (перенести срок, сменить ответственных):\n $ pachca tasks update"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"kind":{"description":"Тип","name":"kind","hasDynamicHelp":false,"multiple":false,"type":"option"},"content":{"description":"Описание","name":"content","hasDynamicHelp":false,"multiple":false,"type":"option"},"due-at":{"description":"Срок выполнения напоминания (ISO-8601) в формате YYYY-MM-DDThh:mm:ss.sssTZD. Если указано время 23:59:59.000, то напоминание будет создано на весь день (без указания времени).","name":"due-at","hasDynamicHelp":false,"multiple":false,"type":"option"},"priority":{"description":"Приоритет: 1, 2 (важно) или 3 (очень важно).","name":"priority","hasDynamicHelp":false,"multiple":false,"type":"option"},"performer-ids":{"description":"Массив идентификаторов пользователей, привязываемых к напоминанию как «ответственные»","name":"performer-ids","hasDynamicHelp":false,"multiple":false,"type":"option"},"status":{"description":"Статус","name":"status","hasDynamicHelp":false,"multiple":false,"type":"option"},"all-day":{"description":"Напоминание на весь день (без указания времени)","name":"all-day","allowNo":true,"type":"boolean"},"done-at":{"description":"Дата и время выполнения напоминания (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ","name":"done-at","hasDynamicHelp":false,"multiple":false,"type":"option"},"custom-properties":{"description":"Задаваемые дополнительные поля","name":"custom-properties","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"tasks:update","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"tasks:update","apiMethod":"PUT","apiPath":"/tasks/{id}","defaultColumns":["id","content","created_at","kind","due_at"],"isESM":true,"relativePath":["dist","commands","tasks","update.js"]},"threads:add":{"aliases":[],"args":{"id":{"description":"Идентификатор сообщения","name":"id","required":true}},"description":"Новый тред","examples":["Ответить в тред (комментарий к сообщению):\n $ pachca thread add","Подписаться на тред сообщения:\n $ pachca thread add"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"threads:add","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"threads:create","apiMethod":"POST","apiPath":"/messages/{id}/thread","defaultColumns":["id","chat_id","message_id","message_chat_id","updated_at"],"isESM":true,"relativePath":["dist","commands","threads","add.js"]},"threads:get":{"aliases":[],"args":{"id":{"description":"Идентификатор треда","name":"id","required":true}},"description":"Информация о треде","flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"threads:get","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"threads:read","apiMethod":"GET","apiPath":"/threads/{id}","defaultColumns":["id","chat_id","message_id","message_chat_id","updated_at"],"isESM":true,"relativePath":["dist","commands","threads","get.js"]},"upgrade":{"aliases":[],"args":{},"description":"Обновить CLI до последней версии","examples":["<%= config.bin %> upgrade"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"upgrade","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","upgrade.js"]},"upload":{"aliases":[],"args":{"file":{"description":"Путь к файлу или - для stdin","name":"file","required":true}},"description":"Загрузить файл (получает подпись и загружает автоматически)","examples":["<%= config.bin %> upload photo.jpg","cat data.csv | <%= config.bin %> upload -"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"upload","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"uploads:write","isESM":true,"relativePath":["dist","commands","upload.js"]},"users:create":{"aliases":[],"args":{},"description":"Создать сотрудника","examples":["Проверить, кто прочитал сообщение:\n $ pachca users list","Разослать уведомление нескольким пользователям:\n $ pachca users list","Массовое создание сотрудников с тегами:\n $ pachca users create"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"first-name":{"description":"Имя","name":"first-name","hasDynamicHelp":false,"multiple":false,"type":"option"},"last-name":{"description":"Фамилия","name":"last-name","hasDynamicHelp":false,"multiple":false,"type":"option"},"email":{"description":"Электронная почта","name":"email","hasDynamicHelp":false,"multiple":false,"type":"option"},"phone-number":{"description":"Телефон","name":"phone-number","hasDynamicHelp":false,"multiple":false,"type":"option"},"nickname":{"description":"Имя пользователя","name":"nickname","hasDynamicHelp":false,"multiple":false,"type":"option"},"department":{"description":"Департамент","name":"department","hasDynamicHelp":false,"multiple":false,"type":"option"},"title":{"description":"Должность","name":"title","hasDynamicHelp":false,"multiple":false,"type":"option"},"role":{"description":"Уровень доступа","name":"role","hasDynamicHelp":false,"multiple":false,"type":"option"},"suspended":{"description":"Деактивация пользователя","name":"suspended","allowNo":true,"type":"boolean"},"list-tags":{"description":"Массив тегов, привязываемых к сотруднику","name":"list-tags","hasDynamicHelp":false,"multiple":false,"type":"option"},"custom-properties":{"description":"Задаваемые дополнительные поля","name":"custom-properties","hasDynamicHelp":false,"multiple":false,"type":"option"},"skip-email-notify":{"description":"Пропуск этапа отправки приглашения сотруднику. Сотруднику не будет отправлено письмо на электронную почту с приглашением создать аккаунт. Полезно при предварительном создании аккаунтов перед входом через SSO.","name":"skip-email-notify","allowNo":true,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"users:create","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"users:create","apiMethod":"POST","apiPath":"/users","defaultColumns":["id","title","first_name","last_name","email"],"requiredFlags":["email"],"isESM":true,"relativePath":["dist","commands","users","create.js"]},"users:delete":{"aliases":[],"args":{"id":{"description":"Идентификатор пользователя (pachca users list)","name":"id","required":true}},"description":"Удаление сотрудника","examples":["Получить сотрудника по ID:\n $ pachca users get","Массовое создание сотрудников с тегами:\n $ pachca users update","Offboarding сотрудника:\n $ pachca users update"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"users:delete","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"users:delete","apiMethod":"DELETE","apiPath":"/users/{id}","isESM":true,"relativePath":["dist","commands","users","delete.js"]},"users:get":{"aliases":[],"args":{"id":{"description":"Идентификатор пользователя (pachca users list)","name":"id","required":true}},"description":"Информация о сотруднике","examples":["Получить сотрудника по ID:\n $ pachca users get","Массовое создание сотрудников с тегами:\n $ pachca users update","Offboarding сотрудника:\n $ pachca users update"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"users:get","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"users:read","apiMethod":"GET","apiPath":"/users/{id}","defaultColumns":["id","title","first_name","last_name","email"],"isESM":true,"relativePath":["dist","commands","users","get.js"]},"users:get-status":{"aliases":[],"args":{"user_id":{"description":"Идентификатор пользователя (pachca users list)","name":"user_id","required":true}},"description":"Статус сотрудника","examples":["Управление статусом сотрудника:\n $ pachca users get-status","Управление статусом сотрудника:\n $ pachca users update-status","Управление статусом сотрудника:\n $ pachca users remove-status"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"users:get-status","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"user_status:read","apiMethod":"GET","apiPath":"/users/{user_id}/status","defaultColumns":["title","emoji","expires_at","is_away"],"isESM":true,"relativePath":["dist","commands","users","get-status.js"]},"users:list":{"aliases":[],"args":{},"description":"Список сотрудников","examples":["Проверить, кто прочитал сообщение:\n $ pachca users list","Разослать уведомление нескольким пользователям:\n $ pachca users list","Массовое создание сотрудников с тегами:\n $ pachca users create"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"query":{"description":"Поисковая фраза для фильтрации результатов. Поиск работает по полям: `first_name` (имя), `last_name` (фамилия), `email` (электронная почта), `phone_number` (телефон) и `nickname` (никнейм).","name":"query","hasDynamicHelp":false,"multiple":false,"type":"option"},"limit":{"description":"Количество результатов на страницу","name":"limit","hasDynamicHelp":false,"multiple":false,"type":"option"},"cursor":{"description":"Курсор для следующей страницы","name":"cursor","hasDynamicHelp":false,"multiple":false,"type":"option"},"all":{"description":"Загрузить все страницы автоматически","name":"all","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"users:list","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"users:read","apiMethod":"GET","apiPath":"/users","defaultColumns":["id","title","first_name","last_name","email"],"isESM":true,"relativePath":["dist","commands","users","list.js"]},"users:remove-avatar":{"aliases":[],"args":{"user_id":{"description":"Идентификатор пользователя (pachca users list)","name":"user_id","required":true}},"description":"Удаление аватара сотрудника","examples":["Загрузить аватар сотрудника:\n $ pachca users update-avatar","Удалить аватар сотрудника:\n $ pachca users remove-avatar"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"users:remove-avatar","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"user_avatar:write","apiMethod":"DELETE","apiPath":"/users/{user_id}/avatar","isESM":true,"relativePath":["dist","commands","users","remove-avatar.js"]},"users:remove-status":{"aliases":[],"args":{"user_id":{"description":"Идентификатор пользователя (pachca users list)","name":"user_id","required":true}},"description":"Удаление статуса сотрудника","examples":["Управление статусом сотрудника:\n $ pachca users get-status","Управление статусом сотрудника:\n $ pachca users update-status","Управление статусом сотрудника:\n $ pachca users remove-status"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"force":{"description":"Пропустить подтверждение","name":"force","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"users:remove-status","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"user_status:write","apiMethod":"DELETE","apiPath":"/users/{user_id}/status","isESM":true,"relativePath":["dist","commands","users","remove-status.js"]},"users:update":{"aliases":[],"args":{"id":{"description":"Идентификатор пользователя (pachca users list)","name":"id","required":true}},"description":"Редактирование сотрудника","examples":["Получить сотрудника по ID:\n $ pachca users get","Массовое создание сотрудников с тегами:\n $ pachca users update","Offboarding сотрудника:\n $ pachca users update"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"first-name":{"description":"Имя","name":"first-name","hasDynamicHelp":false,"multiple":false,"type":"option"},"last-name":{"description":"Фамилия","name":"last-name","hasDynamicHelp":false,"multiple":false,"type":"option"},"email":{"description":"Электронная почта","name":"email","hasDynamicHelp":false,"multiple":false,"type":"option"},"phone-number":{"description":"Телефон","name":"phone-number","hasDynamicHelp":false,"multiple":false,"type":"option"},"nickname":{"description":"Имя пользователя","name":"nickname","hasDynamicHelp":false,"multiple":false,"type":"option"},"department":{"description":"Департамент","name":"department","hasDynamicHelp":false,"multiple":false,"type":"option"},"title":{"description":"Должность","name":"title","hasDynamicHelp":false,"multiple":false,"type":"option"},"role":{"description":"Уровень доступа","name":"role","hasDynamicHelp":false,"multiple":false,"type":"option"},"suspended":{"description":"Деактивация пользователя","name":"suspended","allowNo":true,"type":"boolean"},"list-tags":{"description":"Массив тегов, привязываемых к сотруднику","name":"list-tags","hasDynamicHelp":false,"multiple":false,"type":"option"},"custom-properties":{"description":"Задаваемые дополнительные поля","name":"custom-properties","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"users:update","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"users:update","apiMethod":"PUT","apiPath":"/users/{id}","defaultColumns":["id","title","first_name","last_name","email"],"isESM":true,"relativePath":["dist","commands","users","update.js"]},"users:update-avatar":{"aliases":[],"args":{"user_id":{"description":"Идентификатор пользователя (pachca users list)","name":"user_id","required":true}},"description":"Загрузка аватара сотрудника","examples":["Загрузить аватар сотрудника:\n $ pachca users update-avatar","Удалить аватар сотрудника:\n $ pachca users remove-avatar"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"file":{"description":"Файл изображения для аватара","name":"file","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"users:update-avatar","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"user_avatar:write","apiMethod":"PUT","apiPath":"/users/{user_id}/avatar","defaultColumns":["image_url"],"isESM":true,"relativePath":["dist","commands","users","update-avatar.js"]},"users:update-status":{"aliases":[],"args":{"user_id":{"description":"Идентификатор пользователя (pachca users list)","name":"user_id","required":true}},"description":"Новый статус сотрудника","examples":["Управление статусом сотрудника:\n $ pachca users get-status","Управление статусом сотрудника:\n $ pachca users update-status","Управление статусом сотрудника:\n $ pachca users remove-status"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"emoji":{"description":"Emoji символ статуса","name":"emoji","hasDynamicHelp":false,"multiple":false,"type":"option"},"title":{"description":"Текст статуса","name":"title","hasDynamicHelp":false,"multiple":false,"type":"option"},"expires-at":{"description":"Срок жизни статуса (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ","name":"expires-at","hasDynamicHelp":false,"multiple":false,"type":"option"},"is-away":{"description":"Режим «Нет на месте»","name":"is-away","allowNo":true,"type":"boolean"},"away-message":{"description":"Текст сообщения при режиме «Нет на месте». Отображается в профиле и при личных сообщениях/упоминаниях. (макс. 1024 символов)","name":"away-message","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"users:update-status","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"user_status:write","apiMethod":"PUT","apiPath":"/users/{user_id}/status","defaultColumns":["title","emoji","expires_at","is_away"],"requiredFlags":["emoji","title"],"isESM":true,"relativePath":["dist","commands","users","update-status.js"]},"version":{"aliases":[],"args":{},"description":"Версия CLI","examples":["<%= config.bin %> version"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"version","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"isESM":true,"relativePath":["dist","commands","version.js"]},"views:open":{"aliases":[],"args":{},"description":"Открытие представления","examples":["Показать интерактивную форму пользователю:\n $ pachca views open","Опрос сотрудников через форму:\n $ pachca views open","Форма заявки/запроса:\n $ pachca views open"],"flags":{"output":{"char":"o","description":"Output format: table, json, yaml, csv","name":"output","hasDynamicHelp":false,"multiple":false,"options":["table","json","yaml","csv"],"type":"option"},"columns":{"char":"c","description":"Columns to display (comma-separated)","name":"columns","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-header":{"description":"Hide table header","name":"no-header","allowNo":false,"type":"boolean"},"no-truncate":{"description":"Do not truncate values in table","name":"no-truncate","allowNo":false,"type":"boolean"},"profile":{"char":"p","description":"Profile to use for this command","name":"profile","hasDynamicHelp":false,"multiple":false,"type":"option"},"token":{"description":"Bearer token for this call (not saved)","name":"token","hasDynamicHelp":false,"multiple":false,"type":"option"},"quiet":{"char":"q","description":"Suppress output except errors","name":"quiet","allowNo":false,"type":"boolean"},"no-color":{"description":"Disable color output","name":"no-color","allowNo":false,"type":"boolean"},"verbose":{"char":"v","description":"Show HTTP request/response details","name":"verbose","allowNo":false,"type":"boolean"},"no-input":{"description":"Disable interactive prompts","name":"no-input","allowNo":false,"type":"boolean"},"dry-run":{"description":"Show HTTP request without sending","name":"dry-run","allowNo":false,"type":"boolean"},"timeout":{"description":"Request timeout in seconds","name":"timeout","hasDynamicHelp":false,"multiple":false,"type":"option"},"no-retry":{"description":"Disable auto-retry on 429/503","name":"no-retry","allowNo":false,"type":"boolean"},"json":{"description":"Output as JSON (alias for --output json)","hidden":true,"name":"json","allowNo":false,"type":"boolean"},"title":{"description":"Заголовок представления (макс. 24 символов)","name":"title","hasDynamicHelp":false,"multiple":false,"type":"option"},"close-text":{"description":"Текст кнопки закрытия представления (макс. 24 символов)","name":"close-text","hasDynamicHelp":false,"multiple":false,"type":"option"},"submit-text":{"description":"Текст кнопки отправки формы (макс. 24 символов)","name":"submit-text","hasDynamicHelp":false,"multiple":false,"type":"option"},"blocks":{"description":"Массив блоков представления","name":"blocks","hasDynamicHelp":false,"multiple":false,"type":"option"},"type":{"description":"Способ открытия представления","name":"type","hasDynamicHelp":false,"multiple":false,"options":["modal"],"type":"option"},"trigger-id":{"description":"Уникальный идентификатор события (полученный, например, в исходящем вебхуке о нажатии кнопки)","name":"trigger-id","hasDynamicHelp":false,"multiple":false,"type":"option"},"private-metadata":{"description":"Необязательная строка, которая будет отправлена в ваше приложение при отправке пользователем заполненной формы. Используйте это поле, например, для передачи в формате `JSON` какой то дополнительной информации вместе с заполненной пользователем формой. (макс. 3000 символов)","name":"private-metadata","hasDynamicHelp":false,"multiple":false,"type":"option"},"callback-id":{"description":"Необязательный идентификатор для распознавания этого представления, который будет отправлен в ваше приложение при отправке пользователем заполненной формы. Используйте это поле, например, для понимания, какую форму должен был заполнить пользователь. (макс. 255 символов)","name":"callback-id","hasDynamicHelp":false,"multiple":false,"type":"option"}},"hasDynamicHelp":false,"hiddenAliases":[],"id":"views:open","pluginAlias":"@pachca/cli","pluginName":"@pachca/cli","pluginType":"core","strict":true,"enableJsonFlag":false,"scope":"views:write","apiMethod":"POST","apiPath":"/views/open","requiredFlags":["title","blocks","type","trigger-id"],"isESM":true,"relativePath":["dist","commands","views","open.js"]}},"version":"0.0.0"} \ No newline at end of file diff --git a/packages/cli/scripts/generate-cli.ts b/packages/cli/scripts/generate-cli.ts index e41dd44c..d54e0ef6 100644 --- a/packages/cli/scripts/generate-cli.ts +++ b/packages/cli/scripts/generate-cli.ts @@ -355,6 +355,24 @@ function generateCommandCode(p: CommandGenParams): string { }),`; }); + // For list commands with a single required ID query param, add it as an optional positional arg + // e.g., `pachca messages list 36988817` as a shortcut for `--chat-id=36988817` + let shortcutArg: { paramName: string; flagName: string; argType: string } | null = null; + if (p.isList && requiredQueryFlags.length === 1 && requiredQueryFlags[0].flagName.endsWith('-id')) { + const param = p.queryParams.find((q) => q.required && toKebabCase(q.name) === requiredQueryFlags[0].flagName); + if (param) { + shortcutArg = { + paramName: param.name, + flagName: requiredQueryFlags[0].flagName, + argType: getOclifArgType(param.schema), + }; + argsCode.push(` ${param.name}: Args.${shortcutArg.argType}({ + description: ${JSON.stringify(param.description || param.name)}, + required: false, + }),`); + } + } + // Build flags for query params const queryFlagLines: string[] = []; for (const param of p.queryParams) { @@ -383,12 +401,15 @@ function generateCommandCode(p: CommandGenParams): string { } const flagName = toKebabCase(param.name); - const flagType = getOclifFlagType(param.schema); + // Resolve allOf to get enum/type/default from $ref schemas (e.g., SortOrder, ChatSortField) + const resolvedParamSchema = resolveAllOf(param.schema); + const flagType = getOclifFlagType(resolvedParamSchema); const extras: string[] = []; - if (param.schema.enum) extras.push(` options: ${JSON.stringify(param.schema.enum)},`); + if (resolvedParamSchema.enum) extras.push(` options: ${JSON.stringify(resolvedParamSchema.enum)},`); + if (resolvedParamSchema.default !== undefined) extras.push(` default: ${JSON.stringify(resolvedParamSchema.default)},`); if (flagType === 'boolean') extras.push(` allowNo: true,`); const extrasStr = extras.length > 0 ? '\n' + extras.join('\n') : ''; - const arrayHint = param.schema.type === 'array' ? ' (через запятую)' : ''; + const arrayHint = resolvedParamSchema.type === 'array' ? ' (через запятую)' : ''; queryFlagLines.push(` '${flagName}': Flags.${flagType}({ description: ${JSON.stringify(param.description || param.name)}${arrayHint ? ` + ${JSON.stringify(arrayHint)}` : ''},${extrasStr} }),`); @@ -474,6 +495,14 @@ function generateCommandCode(p: CommandGenParams): string { runBodyLines.push(` const { args, flags } = await this.parse(${p.className});`); runBodyLines.push(` this.parsedFlags = flags;`); + // Populate flag from positional arg shortcut (e.g., `messages list 123` → --chat-id=123) + if (shortcutArg) { + runBodyLines.push(''); + runBodyLines.push(` if (args.${shortcutArg.paramName} !== undefined && (flags as Record)['${shortcutArg.flagName}'] === undefined) {`); + runBodyLines.push(` (flags as Record)['${shortcutArg.flagName}'] = args.${shortcutArg.paramName};`); + runBodyLines.push(` }`); + } + // Stdin support for text fields if (p.stdinField) { const flagName = toKebabCase(p.stdinField.name); @@ -583,11 +612,11 @@ function generateCommandCode(p: CommandGenParams): string { continue; } const flagName = toKebabCase(param.name); - if (flagName !== param.name) { - queryEntries.push(` '${param.name}': flags['${flagName}'],`); - } else { - queryEntries.push(` ${param.name}: flags['${param.name}'],`); - } + const isArrayParam = param.schema.type === 'array'; + const flagRef = flagName !== param.name ? `flags['${flagName}']` : `flags['${param.name}']`; + const value = isArrayParam ? `${flagRef}?.split(',')` : flagRef; + const key = flagName !== param.name ? `'${param.name}'` : param.name; + queryEntries.push(` ${key}: ${value},`); } if (p.hasPagination) { queryEntries.push(` limit: flags.limit,`); @@ -629,7 +658,7 @@ function generateCommandCode(p: CommandGenParams): string { runBodyLines.push(` const seenCursors = new Set();`); runBodyLines.push(''); runBodyLines.push(` while (pages < 500) {`); - runBodyLines.push(` const query: Record = {`); + runBodyLines.push(` const query: Record = {`); for (const entry of queryEntries) { if (!entry.includes('cursor:')) runBodyLines.push(` ${entry}`); } @@ -639,6 +668,7 @@ function generateCommandCode(p: CommandGenParams): string { runBodyLines.push(` const body = response.data as Record;`); runBodyLines.push(` const items = body.data as unknown[];`); runBodyLines.push(` if (items) allData.push(...items);`); + runBodyLines.push(` if (!items || items.length === 0) break;`); runBodyLines.push(` const meta = body.meta as Record | undefined;`); runBodyLines.push(` const paginate = meta?.paginate as Record | undefined;`); runBodyLines.push(` nextCursor = paginate?.next_page as string | undefined;`); diff --git a/packages/cli/scripts/openapi-parser.ts b/packages/cli/scripts/openapi-parser.ts index 9c2c62ee..91c7a726 100644 --- a/packages/cli/scripts/openapi-parser.ts +++ b/packages/cli/scripts/openapi-parser.ts @@ -246,8 +246,9 @@ export function parseOpenAPI(): Endpoint[] { export function resolveAllOf(schema: Schema): Schema { if (!schema.allOf || schema.allOf.length === 0) return schema; + const { allOf, ...siblings } = schema; let merged: Schema = {}; - for (const sub of schema.allOf) { + for (const sub of allOf) { const resolved = resolveAllOf(sub); merged = { ...merged, @@ -256,7 +257,7 @@ export function resolveAllOf(schema: Schema): Schema { required: [...(merged.required || []), ...(resolved.required || [])], }; } - return merged; + return { ...merged, ...siblings }; } export function getSchemaType(schema: Schema): string { diff --git a/packages/cli/src/client.ts b/packages/cli/src/client.ts index 0f60d4c5..96a5fab1 100644 --- a/packages/cli/src/client.ts +++ b/packages/cli/src/client.ts @@ -51,7 +51,7 @@ export interface RequestOptions { path: string; token: string; body?: unknown; - query?: Record; + query?: Record; headers?: Record; timeout?: number; noRetry?: boolean; @@ -72,7 +72,7 @@ export interface ClientFlags { const MAX_RETRIES = 3; const DEFAULT_TIMEOUT = 30; -function buildUrl(apiPath: string, query?: Record): string { +function buildUrl(apiPath: string, query?: Record): string { const base = apiPath.startsWith('http://') || apiPath.startsWith('https://') ? apiPath : apiPath.startsWith('/') ? `${getBaseUrl()}${apiPath}` : `${getBaseUrl()}/${apiPath}`; @@ -80,7 +80,13 @@ function buildUrl(apiPath: string, query?: Record(); while (pages < 500) { - const query: Record = { + const query: Record = { limit: flags.limit, cursor: nextCursor, }; @@ -52,6 +52,7 @@ export default class BotsListEvents extends BaseCommand { const body = response.data as Record; const items = body.data as unknown[]; if (items) allData.push(...items); + if (!items || items.length === 0) break; const meta = body.meta as Record | undefined; const paginate = meta?.paginate as Record | undefined; nextCursor = paginate?.next_page as string | undefined; diff --git a/packages/cli/src/commands/chats/list.ts b/packages/cli/src/commands/chats/list.ts index 8cc58a63..055865be 100644 --- a/packages/cli/src/commands/chats/list.ts +++ b/packages/cli/src/commands/chats/list.ts @@ -22,16 +22,20 @@ export default class ChatsList extends BaseCommand { static override flags = { ...BaseCommand.baseFlags, - sort: Flags.string({ - description: "Поле сортировки (id — идентификатор чата, last-message-at — дата и время создания последнего сообщения)", - options: ["id","last-message-at"], + 'sort': Flags.string({ + description: "Поле сортировки", + options: ["id","last_message_at"], + default: "id", }), - order: Flags.string({ - description: "Порядок сортировки", + 'order': Flags.string({ + description: "Направление сортировки", options: ["asc","desc"], + default: "desc", }), 'availability': Flags.string({ description: "Параметр, который отвечает за доступность и выборку чатов для пользователя", + options: ["is_member","public"], + default: "is_member", }), 'last-message-at-after': Flags.string({ description: "Фильтрация по времени создания последнего сообщения. Будут возвращены те чаты, время последнего созданного сообщения в которых не раньше чем указанное (в формате YYYY-MM-DDThh:mm:ss.sssZ).", @@ -67,8 +71,9 @@ export default class ChatsList extends BaseCommand { const seenCursors = new Set(); while (pages < 500) { - const query: Record = { - ...(flags.sort ? { [`sort[${flags.sort.replace(/-/g, '_')}]`]: flags.order || 'desc' } : {}), + const query: Record = { + sort: flags['sort'], + order: flags['order'], availability: flags['availability'], 'last_message_at_after': flags['last-message-at-after'], 'last_message_at_before': flags['last-message-at-before'], @@ -80,6 +85,7 @@ export default class ChatsList extends BaseCommand { const body = response.data as Record; const items = body.data as unknown[]; if (items) allData.push(...items); + if (!items || items.length === 0) break; const meta = body.meta as Record | undefined; const paginate = meta?.paginate as Record | undefined; nextCursor = paginate?.next_page as string | undefined; @@ -111,7 +117,8 @@ export default class ChatsList extends BaseCommand { method: 'GET', path: '/chats', query: { - ...(flags.sort ? { [`sort[${flags.sort.replace(/-/g, '_')}]`]: flags.order || 'desc' } : {}), + sort: flags['sort'], + order: flags['order'], availability: flags['availability'], 'last_message_at_after': flags['last-message-at-after'], 'last_message_at_before': flags['last-message-at-before'], diff --git a/packages/cli/src/commands/group-tags/list-users.ts b/packages/cli/src/commands/group-tags/list-users.ts index 2a39a4c6..f03ed459 100644 --- a/packages/cli/src/commands/group-tags/list-users.ts +++ b/packages/cli/src/commands/group-tags/list-users.ts @@ -47,7 +47,7 @@ export default class GroupTagsListUsers extends BaseCommand { const seenCursors = new Set(); while (pages < 500) { - const query: Record = { + const query: Record = { limit: flags.limit, cursor: nextCursor, }; @@ -55,6 +55,7 @@ export default class GroupTagsListUsers extends BaseCommand { const body = response.data as Record; const items = body.data as unknown[]; if (items) allData.push(...items); + if (!items || items.length === 0) break; const meta = body.meta as Record | undefined; const paginate = meta?.paginate as Record | undefined; nextCursor = paginate?.next_page as string | undefined; diff --git a/packages/cli/src/commands/group-tags/list.ts b/packages/cli/src/commands/group-tags/list.ts index dd148fb7..4fb3938e 100644 --- a/packages/cli/src/commands/group-tags/list.ts +++ b/packages/cli/src/commands/group-tags/list.ts @@ -48,8 +48,8 @@ export default class GroupTagsList extends BaseCommand { const seenCursors = new Set(); while (pages < 500) { - const query: Record = { - names: flags['names'], + const query: Record = { + names: flags['names']?.split(','), limit: flags.limit, cursor: nextCursor, }; @@ -57,6 +57,7 @@ export default class GroupTagsList extends BaseCommand { const body = response.data as Record; const items = body.data as unknown[]; if (items) allData.push(...items); + if (!items || items.length === 0) break; const meta = body.meta as Record | undefined; const paginate = meta?.paginate as Record | undefined; nextCursor = paginate?.next_page as string | undefined; @@ -88,7 +89,7 @@ export default class GroupTagsList extends BaseCommand { method: 'GET', path: '/group_tags', query: { - names: flags['names'], + names: flags['names']?.split(','), limit: flags.limit, cursor: flags.cursor, }, diff --git a/packages/cli/src/commands/members/list.ts b/packages/cli/src/commands/members/list.ts index 1d366076..851c92be 100644 --- a/packages/cli/src/commands/members/list.ts +++ b/packages/cli/src/commands/members/list.ts @@ -27,6 +27,8 @@ export default class MembersList extends BaseCommand { ...BaseCommand.baseFlags, 'role': Flags.string({ description: "Роль в чате", + options: ["all","owner","admin","editor","member"], + default: "all", }), limit: Flags.integer({ description: 'Количество результатов на страницу', @@ -52,7 +54,7 @@ export default class MembersList extends BaseCommand { const seenCursors = new Set(); while (pages < 500) { - const query: Record = { + const query: Record = { role: flags['role'], limit: flags.limit, cursor: nextCursor, @@ -61,6 +63,7 @@ export default class MembersList extends BaseCommand { const body = response.data as Record; const items = body.data as unknown[]; if (items) allData.push(...items); + if (!items || items.length === 0) break; const meta = body.meta as Record | undefined; const paginate = meta?.paginate as Record | undefined; nextCursor = paginate?.next_page as string | undefined; diff --git a/packages/cli/src/commands/messages/list.ts b/packages/cli/src/commands/messages/list.ts index ad9a3f08..b38807f1 100644 --- a/packages/cli/src/commands/messages/list.ts +++ b/packages/cli/src/commands/messages/list.ts @@ -19,7 +19,10 @@ export default class MessagesList extends BaseCommand { static requiredFlags = ["chat-id"]; static override args = { - + chat_id: Args.integer({ + description: "Идентификатор чата (беседа, канал, диалог или чат треда)", + required: false, + }), }; static override flags = { @@ -27,13 +30,15 @@ export default class MessagesList extends BaseCommand { 'chat-id': Flags.integer({ description: "Идентификатор чата (беседа, канал, диалог или чат треда)", }), - sort: Flags.string({ - description: "Поле сортировки (id — идентификатор сообщения)", + 'sort': Flags.string({ + description: "Поле сортировки", options: ["id"], + default: "id", }), - order: Flags.string({ - description: "Порядок сортировки", + 'order': Flags.string({ + description: "Направление сортировки", options: ["asc","desc"], + default: "desc", }), limit: Flags.integer({ description: 'Количество результатов на страницу', @@ -51,6 +56,10 @@ export default class MessagesList extends BaseCommand { const { args, flags } = await this.parse(MessagesList); this.parsedFlags = flags; + if (args.chat_id !== undefined && (flags as Record)['chat-id'] === undefined) { + (flags as Record)['chat-id'] = args.chat_id; + } + const missingRequired: { flag: string; label: string; type: string }[] = [ { flag: 'chat-id', label: "Идентификатор чата (беседа, канал, диалог или чат треда)", type: 'integer' }, ].filter((f) => (flags as Record)[f.flag] === undefined || (flags as Record)[f.flag] === null); @@ -80,9 +89,10 @@ export default class MessagesList extends BaseCommand { const seenCursors = new Set(); while (pages < 500) { - const query: Record = { + const query: Record = { 'chat_id': flags['chat-id'], - ...(flags.sort ? { [`sort[${flags.sort.replace(/-/g, '_')}]`]: flags.order || 'desc' } : {}), + sort: flags['sort'], + order: flags['order'], limit: flags.limit, cursor: nextCursor, }; @@ -90,6 +100,7 @@ export default class MessagesList extends BaseCommand { const body = response.data as Record; const items = body.data as unknown[]; if (items) allData.push(...items); + if (!items || items.length === 0) break; const meta = body.meta as Record | undefined; const paginate = meta?.paginate as Record | undefined; nextCursor = paginate?.next_page as string | undefined; @@ -122,7 +133,8 @@ export default class MessagesList extends BaseCommand { path: '/messages', query: { 'chat_id': flags['chat-id'], - ...(flags.sort ? { [`sort[${flags.sort.replace(/-/g, '_')}]`]: flags.order || 'desc' } : {}), + sort: flags['sort'], + order: flags['order'], limit: flags.limit, cursor: flags.cursor, }, diff --git a/packages/cli/src/commands/profile/delete-avatar.ts b/packages/cli/src/commands/profile/delete-avatar.ts new file mode 100644 index 00000000..815252d3 --- /dev/null +++ b/packages/cli/src/commands/profile/delete-avatar.ts @@ -0,0 +1,55 @@ +// Auto-generated from openapi.yaml — DO NOT EDIT +import { Args, Flags } from '@oclif/core'; +import { BaseCommand } from '../../base-command.js'; +import * as clack from '@clack/prompts'; + +export default class ProfileDeleteAvatar extends BaseCommand { + static override description = "Удаление аватара"; + + static override examples = [ + "Загрузить аватар профиля:\n $ pachca profile update-avatar", + "Удалить аватар профиля:\n $ pachca profile delete-avatar" + ]; + + static scope = "profile_avatar:write"; + static apiMethod = "DELETE"; + static apiPath = "/profile/avatar"; + + static override args = { + + }; + + static override flags = { + ...BaseCommand.baseFlags, + force: Flags.boolean({ + description: 'Пропустить подтверждение', + default: false, + }), + }; + + async run(): Promise { + const { args, flags } = await this.parse(ProfileDeleteAvatar); + this.parsedFlags = flags; + + if (!flags.force) { + if (!this.isInteractive()) { + this.validationError( + [{ message: 'Деструктивная операция требует флага --force', flag: 'force' }], + { type: 'PACHCA_DESTRUCTIVE_OP_ERROR', hint: "pachca profile delete-avatar --force" }, + ); + } + const confirm = await clack.confirm({ message: 'Вы уверены?' }); + if (clack.isCancel(confirm) || !confirm) { + process.stderr.write('Отменено.\n'); + this.exit(0); + } + } + + const { data } = await this.apiRequest({ + method: 'DELETE', + path: '/profile/avatar', + }); + + this.success('Удалено'); + } +} diff --git a/packages/cli/src/commands/profile/update-avatar.ts b/packages/cli/src/commands/profile/update-avatar.ts new file mode 100644 index 00000000..4018da61 --- /dev/null +++ b/packages/cli/src/commands/profile/update-avatar.ts @@ -0,0 +1,59 @@ +// Auto-generated from openapi.yaml — DO NOT EDIT +import { Args, Flags } from '@oclif/core'; +import { BaseCommand } from '../../base-command.js'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +export default class ProfileUpdateAvatar extends BaseCommand { + static override description = "Загрузка аватара"; + + static override examples = [ + "Загрузить аватар профиля:\n $ pachca profile update-avatar", + "Удалить аватар профиля:\n $ pachca profile delete-avatar" + ]; + + static scope = "profile_avatar:write"; + static apiMethod = "PUT"; + static apiPath = "/profile/avatar"; + static defaultColumns = ["image_url"]; + + static override args = { + + }; + + static override flags = { + ...BaseCommand.baseFlags, + file: Flags.string({ + description: "Файл изображения для аватара", + }), + }; + + async run(): Promise { + const { args, flags } = await this.parse(ProfileUpdateAvatar); + this.parsedFlags = flags; + + let formData: FormData | undefined; + if (flags.file) { + formData = new FormData(); + if (flags.file === '-') { + const chunks: Buffer[] = []; + for await (const chunk of process.stdin) chunks.push(chunk as Buffer); + const blob = new Blob([Buffer.concat(chunks)]); + formData.append('image', blob, 'stdin'); + } else { + const blob = new Blob([fs.readFileSync(flags.file)]); + formData.append('image', blob, path.basename(flags.file)); + } + } + + const { data } = await this.apiRequest({ + method: 'PUT', + path: '/profile/avatar', + formData, + }); + + const responseBody = data as Record; + const result = responseBody.data ?? responseBody; + this.output(result); + } +} diff --git a/packages/cli/src/commands/reactions/list.ts b/packages/cli/src/commands/reactions/list.ts index e67418f1..eac350c0 100644 --- a/packages/cli/src/commands/reactions/list.ts +++ b/packages/cli/src/commands/reactions/list.ts @@ -48,7 +48,7 @@ export default class ReactionsList extends BaseCommand { const seenCursors = new Set(); while (pages < 500) { - const query: Record = { + const query: Record = { limit: flags.limit, cursor: nextCursor, }; @@ -56,6 +56,7 @@ export default class ReactionsList extends BaseCommand { const body = response.data as Record; const items = body.data as unknown[]; if (items) allData.push(...items); + if (!items || items.length === 0) break; const meta = body.meta as Record | undefined; const paginate = meta?.paginate as Record | undefined; nextCursor = paginate?.next_page as string | undefined; diff --git a/packages/cli/src/commands/read-member/list-readers.ts b/packages/cli/src/commands/read-member/list-readers.ts index 40ca2b68..e70396e8 100644 --- a/packages/cli/src/commands/read-member/list-readers.ts +++ b/packages/cli/src/commands/read-member/list-readers.ts @@ -46,7 +46,7 @@ export default class ReadMemberListReaders extends BaseCommand { const seenCursors = new Set(); while (pages < 500) { - const query: Record = { + const query: Record = { limit: flags.limit, cursor: nextCursor, }; @@ -54,6 +54,7 @@ export default class ReadMemberListReaders extends BaseCommand { const body = response.data as Record; const items = body.data as unknown[]; if (items) allData.push(...items); + if (!items || items.length === 0) break; const meta = body.meta as Record | undefined; const paginate = meta?.paginate as Record | undefined; nextCursor = paginate?.next_page as string | undefined; diff --git a/packages/cli/src/commands/search/list-chats.ts b/packages/cli/src/commands/search/list-chats.ts index c5ee1eea..a30174c2 100644 --- a/packages/cli/src/commands/search/list-chats.ts +++ b/packages/cli/src/commands/search/list-chats.ts @@ -70,7 +70,7 @@ export default class SearchListChats extends BaseCommand { const seenCursors = new Set(); while (pages < 500) { - const query: Record = { + const query: Record = { query: flags['query'], order: flags['order'], 'created_from': flags['created-from'], @@ -85,6 +85,7 @@ export default class SearchListChats extends BaseCommand { const body = response.data as Record; const items = body.data as unknown[]; if (items) allData.push(...items); + if (!items || items.length === 0) break; const meta = body.meta as Record | undefined; const paginate = meta?.paginate as Record | undefined; nextCursor = paginate?.next_page as string | undefined; diff --git a/packages/cli/src/commands/search/list-messages.ts b/packages/cli/src/commands/search/list-messages.ts index 43d1c94a..61c2d6b3 100644 --- a/packages/cli/src/commands/search/list-messages.ts +++ b/packages/cli/src/commands/search/list-messages.ts @@ -67,13 +67,13 @@ export default class SearchListMessages extends BaseCommand { const seenCursors = new Set(); while (pages < 500) { - const query: Record = { + const query: Record = { query: flags['query'], order: flags['order'], 'created_from': flags['created-from'], 'created_to': flags['created-to'], - 'chat_ids': flags['chat-ids'], - 'user_ids': flags['user-ids'], + 'chat_ids': flags['chat-ids']?.split(','), + 'user_ids': flags['user-ids']?.split(','), active: flags['active'], limit: flags.limit, cursor: nextCursor, @@ -82,6 +82,7 @@ export default class SearchListMessages extends BaseCommand { const body = response.data as Record; const items = body.data as unknown[]; if (items) allData.push(...items); + if (!items || items.length === 0) break; const meta = body.meta as Record | undefined; const paginate = meta?.paginate as Record | undefined; nextCursor = paginate?.next_page as string | undefined; @@ -117,8 +118,8 @@ export default class SearchListMessages extends BaseCommand { order: flags['order'], 'created_from': flags['created-from'], 'created_to': flags['created-to'], - 'chat_ids': flags['chat-ids'], - 'user_ids': flags['user-ids'], + 'chat_ids': flags['chat-ids']?.split(','), + 'user_ids': flags['user-ids']?.split(','), active: flags['active'], limit: flags.limit, cursor: flags.cursor, diff --git a/packages/cli/src/commands/search/list-users.ts b/packages/cli/src/commands/search/list-users.ts index 798f5dba..2b09facd 100644 --- a/packages/cli/src/commands/search/list-users.ts +++ b/packages/cli/src/commands/search/list-users.ts @@ -66,13 +66,13 @@ export default class SearchListUsers extends BaseCommand { const seenCursors = new Set(); while (pages < 500) { - const query: Record = { + const query: Record = { query: flags['query'], sort: flags['sort'], order: flags['order'], 'created_from': flags['created-from'], 'created_to': flags['created-to'], - 'company_roles': flags['company-roles'], + 'company_roles': flags['company-roles']?.split(','), limit: flags.limit, cursor: nextCursor, }; @@ -80,6 +80,7 @@ export default class SearchListUsers extends BaseCommand { const body = response.data as Record; const items = body.data as unknown[]; if (items) allData.push(...items); + if (!items || items.length === 0) break; const meta = body.meta as Record | undefined; const paginate = meta?.paginate as Record | undefined; nextCursor = paginate?.next_page as string | undefined; @@ -116,7 +117,7 @@ export default class SearchListUsers extends BaseCommand { order: flags['order'], 'created_from': flags['created-from'], 'created_to': flags['created-to'], - 'company_roles': flags['company-roles'], + 'company_roles': flags['company-roles']?.split(','), limit: flags.limit, cursor: flags.cursor, }, diff --git a/packages/cli/src/commands/security/list.ts b/packages/cli/src/commands/security/list.ts index 2cf09c22..a863fba6 100644 --- a/packages/cli/src/commands/security/list.ts +++ b/packages/cli/src/commands/security/list.ts @@ -69,7 +69,7 @@ export default class SecurityList extends BaseCommand { const seenCursors = new Set(); while (pages < 500) { - const query: Record = { + const query: Record = { 'start_time': flags['start-time'], 'end_time': flags['end-time'], 'event_key': flags['event-key'], @@ -84,6 +84,7 @@ export default class SecurityList extends BaseCommand { const body = response.data as Record; const items = body.data as unknown[]; if (items) allData.push(...items); + if (!items || items.length === 0) break; const meta = body.meta as Record | undefined; const paginate = meta?.paginate as Record | undefined; nextCursor = paginate?.next_page as string | undefined; diff --git a/packages/cli/src/commands/tasks/list.ts b/packages/cli/src/commands/tasks/list.ts index ec226366..f5ef4764 100644 --- a/packages/cli/src/commands/tasks/list.ts +++ b/packages/cli/src/commands/tasks/list.ts @@ -46,7 +46,7 @@ export default class TasksList extends BaseCommand { const seenCursors = new Set(); while (pages < 500) { - const query: Record = { + const query: Record = { limit: flags.limit, cursor: nextCursor, }; @@ -54,6 +54,7 @@ export default class TasksList extends BaseCommand { const body = response.data as Record; const items = body.data as unknown[]; if (items) allData.push(...items); + if (!items || items.length === 0) break; const meta = body.meta as Record | undefined; const paginate = meta?.paginate as Record | undefined; nextCursor = paginate?.next_page as string | undefined; diff --git a/packages/cli/src/commands/users/list.ts b/packages/cli/src/commands/users/list.ts index f95faee4..32c8135d 100644 --- a/packages/cli/src/commands/users/list.ts +++ b/packages/cli/src/commands/users/list.ts @@ -49,7 +49,7 @@ export default class UsersList extends BaseCommand { const seenCursors = new Set(); while (pages < 500) { - const query: Record = { + const query: Record = { query: flags['query'], limit: flags.limit, cursor: nextCursor, @@ -58,6 +58,7 @@ export default class UsersList extends BaseCommand { const body = response.data as Record; const items = body.data as unknown[]; if (items) allData.push(...items); + if (!items || items.length === 0) break; const meta = body.meta as Record | undefined; const paginate = meta?.paginate as Record | undefined; nextCursor = paginate?.next_page as string | undefined; diff --git a/packages/cli/src/commands/users/remove-avatar.ts b/packages/cli/src/commands/users/remove-avatar.ts new file mode 100644 index 00000000..37106544 --- /dev/null +++ b/packages/cli/src/commands/users/remove-avatar.ts @@ -0,0 +1,58 @@ +// Auto-generated from openapi.yaml — DO NOT EDIT +import { Args, Flags } from '@oclif/core'; +import { BaseCommand } from '../../base-command.js'; +import * as clack from '@clack/prompts'; + +export default class UsersRemoveAvatar extends BaseCommand { + static override description = "Удаление аватара сотрудника"; + + static override examples = [ + "Загрузить аватар сотрудника:\n $ pachca users update-avatar", + "Удалить аватар сотрудника:\n $ pachca users remove-avatar" + ]; + + static scope = "user_avatar:write"; + static apiMethod = "DELETE"; + static apiPath = "/users/{user_id}/avatar"; + + static override args = { + user_id: Args.integer({ + description: "Идентификатор пользователя (pachca users list)", + required: true, + }), + }; + + static override flags = { + ...BaseCommand.baseFlags, + force: Flags.boolean({ + description: 'Пропустить подтверждение', + default: false, + }), + }; + + async run(): Promise { + const { args, flags } = await this.parse(UsersRemoveAvatar); + this.parsedFlags = flags; + + if (!flags.force) { + if (!this.isInteractive()) { + this.validationError( + [{ message: 'Деструктивная операция требует флага --force', flag: 'force' }], + { type: 'PACHCA_DESTRUCTIVE_OP_ERROR', hint: "pachca users remove-avatar --force" }, + ); + } + const confirm = await clack.confirm({ message: 'Вы уверены?' }); + if (clack.isCancel(confirm) || !confirm) { + process.stderr.write('Отменено.\n'); + this.exit(0); + } + } + + const { data } = await this.apiRequest({ + method: 'DELETE', + path: `/users/${args.user_id}/avatar`, + }); + + this.success('Удалено'); + } +} diff --git a/packages/cli/src/commands/users/update-avatar.ts b/packages/cli/src/commands/users/update-avatar.ts new file mode 100644 index 00000000..7c7a9487 --- /dev/null +++ b/packages/cli/src/commands/users/update-avatar.ts @@ -0,0 +1,62 @@ +// Auto-generated from openapi.yaml — DO NOT EDIT +import { Args, Flags } from '@oclif/core'; +import { BaseCommand } from '../../base-command.js'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +export default class UsersUpdateAvatar extends BaseCommand { + static override description = "Загрузка аватара сотрудника"; + + static override examples = [ + "Загрузить аватар сотрудника:\n $ pachca users update-avatar", + "Удалить аватар сотрудника:\n $ pachca users remove-avatar" + ]; + + static scope = "user_avatar:write"; + static apiMethod = "PUT"; + static apiPath = "/users/{user_id}/avatar"; + static defaultColumns = ["image_url"]; + + static override args = { + user_id: Args.integer({ + description: "Идентификатор пользователя (pachca users list)", + required: true, + }), + }; + + static override flags = { + ...BaseCommand.baseFlags, + file: Flags.string({ + description: "Файл изображения для аватара", + }), + }; + + async run(): Promise { + const { args, flags } = await this.parse(UsersUpdateAvatar); + this.parsedFlags = flags; + + let formData: FormData | undefined; + if (flags.file) { + formData = new FormData(); + if (flags.file === '-') { + const chunks: Buffer[] = []; + for await (const chunk of process.stdin) chunks.push(chunk as Buffer); + const blob = new Blob([Buffer.concat(chunks)]); + formData.append('image', blob, 'stdin'); + } else { + const blob = new Blob([fs.readFileSync(flags.file)]); + formData.append('image', blob, path.basename(flags.file)); + } + } + + const { data } = await this.apiRequest({ + method: 'PUT', + path: `/users/${args.user_id}/avatar`, + formData, + }); + + const responseBody = data as Record; + const result = responseBody.data ?? responseBody; + this.output(result); + } +} diff --git a/packages/cli/src/data/alternatives.json b/packages/cli/src/data/alternatives.json index ab60b025..a5691d92 100644 --- a/packages/cli/src/data/alternatives.json +++ b/packages/cli/src/data/alternatives.json @@ -39,6 +39,8 @@ "threads:add": "Новый тред", "profile:get-info": "Информация о токене", "profile:get": "Информация о профиле", + "profile:update-avatar": "Загрузка аватара", + "profile:delete-avatar": "Удаление аватара", "profile:get-status": "Текущий статус", "profile:update-status": "Новый статус", "profile:delete-status": "Удаление статуса", @@ -57,6 +59,8 @@ "users:get": "Информация о сотруднике", "users:update": "Редактирование сотрудника", "users:delete": "Удаление сотрудника", + "users:update-avatar": "Загрузка аватара сотрудника", + "users:remove-avatar": "Удаление аватара сотрудника", "users:get-status": "Статус сотрудника", "users:update-status": "Новый статус сотрудника", "users:remove-status": "Удаление статуса сотрудника", diff --git a/packages/cli/src/data/changelog.json b/packages/cli/src/data/changelog.json index 7d0e4138..e44ef9c6 100644 --- a/packages/cli/src/data/changelog.json +++ b/packages/cli/src/data/changelog.json @@ -1,4 +1,40 @@ [ + { + "version": "2026.4.0", + "date": "7 апреля 2026", + "changes": [ + { + "type": "+", + "command": "profile update-avatar", + "description": "Загрузка аватара профиля" + }, + { + "type": "+", + "command": "profile delete-avatar", + "description": "Удаление аватара профиля" + }, + { + "type": "+", + "command": "users update-avatar", + "description": "Загрузка аватара сотрудника" + }, + { + "type": "+", + "command": "users remove-avatar", + "description": "Удаление аватара сотрудника" + }, + { + "type": "~", + "command": "chats list, messages list", + "description": "Исправлена сортировка: --order без --sort теперь корректно работает (sort по умолчанию id, order по умолчанию desc)" + }, + { + "type": "~", + "command": "messages list", + "description": "chat_id можно передать первым аргументом: pachca messages list 12345" + } + ] + }, { "version": "2026.3.10", "date": "21 марта 2026", diff --git a/packages/cli/src/data/workflows.json b/packages/cli/src/data/workflows.json index 2acb5135..3b06927b 100644 --- a/packages/cli/src/data/workflows.json +++ b/packages/cli/src/data/workflows.json @@ -598,6 +598,28 @@ } ] }, + { + "title": "Загрузить аватар сотрудника", + "skill": "pachca-users", + "steps": [ + { + "description": "Загрузи аватар сотруднику", + "command": "pachca users update-avatar --file=<путь_к_файлу>", + "notes": "Требует прав администратора. Файл передается в формате multipart/form-data" + } + ] + }, + { + "title": "Удалить аватар сотрудника", + "skill": "pachca-users", + "steps": [ + { + "description": "Удали аватар сотрудника", + "command": "pachca users remove-avatar --force", + "notes": "Требует прав администратора" + } + ] + }, { "title": "Создать напоминание", "skill": "pachca-tasks", @@ -727,6 +749,27 @@ } ] }, + { + "title": "Загрузить аватар профиля", + "skill": "pachca-profile", + "steps": [ + { + "description": "Загрузи аватар из файла", + "command": "pachca profile update-avatar --file=<путь_к_файлу>", + "notes": "Файл изображения передается в формате multipart/form-data" + } + ] + }, + { + "title": "Удалить аватар профиля", + "skill": "pachca-profile", + "steps": [ + { + "description": "Удали аватар", + "command": "pachca profile delete-avatar --force" + } + ] + }, { "title": "Найти сообщение по тексту", "skill": "pachca-search", diff --git a/packages/cli/tests/commands.test.ts b/packages/cli/tests/commands.test.ts index ef99ce6b..c246f90c 100644 --- a/packages/cli/tests/commands.test.ts +++ b/packages/cli/tests/commands.test.ts @@ -360,28 +360,31 @@ describe('generated commands — functional tests', () => { }); }); - // ----- Composite query params (chats list) ----- + // ----- Sort query params (chats list) ----- describe('chats list', () => { - it('--sort last-message-at --order desc → query sort[last_message_at]=desc', async () => { + it('--sort last_message_at --order desc → query sort=last_message_at&order=desc', async () => { mockFetchForEndpoint('/chats', 'GET'); - const { stdout, stderr, error } = await runCommand(['chats', 'list', '--sort', 'last-message-at', '--order', 'desc'], { root: CLI_ROOT }); + const { stdout, stderr, error } = await runCommand(['chats', 'list', '--sort', 'last_message_at', '--order', 'desc'], { root: CLI_ROOT }); expect(error).toBeUndefined(); expect(fetchCalls().length).toBeGreaterThan(0); const url = fetchCalls()[0][0] as string; - expect(url).toContain('sort%5Blast_message_at%5D=desc'); + expect(url).toContain('sort=last_message_at'); + expect(url).toContain('order=desc'); }); }); // ----- Array query (search list-messages) ----- describe('search list-messages', () => { - it('--chat-ids 1,2,3 → query chat_ids', async () => { + it('--chat-ids 1,2,3 → query chat_ids[] repeated', async () => { mockFetchForEndpoint('/search/messages', 'GET'); await runCommand(['search', 'list-messages', '--chat-ids', '1,2,3'], { root: CLI_ROOT }); expect(fetchCalls().length).toBeGreaterThan(0); const url = fetchCalls()[0][0] as string; - expect(url).toContain('chat_ids='); + expect(url).toContain('chat_ids%5B%5D=1'); + expect(url).toContain('chat_ids%5B%5D=2'); + expect(url).toContain('chat_ids%5B%5D=3'); }); }); }); diff --git a/packages/cli/tests/generate-cli.test.ts b/packages/cli/tests/generate-cli.test.ts index cc58c4a1..565bd92b 100644 --- a/packages/cli/tests/generate-cli.test.ts +++ b/packages/cli/tests/generate-cli.test.ts @@ -156,15 +156,14 @@ describe('generate-cli', () => { expect(content).not.toMatch(/'xAmz\w+':\s*Flags/); }); - it('should generate --sort and --order flags for composite sort params', () => { + it('should generate --sort and --order flags for sort params', () => { const content = fs.readFileSync( path.join(COMMANDS_DIR, 'chats', 'list.ts'), 'utf-8', ); - // sort[{field}] with x-param-names → --sort with options + --order with asc/desc - expect(content).toContain('sort: Flags.string('); - expect(content).toContain('order: Flags.string('); - expect(content).toContain('"last-message-at"'); + // sort and order are separate query params + expect(content).toContain("'sort': Flags.string("); + expect(content).toContain("'order': Flags.string("); expect(content).not.toMatch(/'sort-id':\s*Flags/); expect(content).not.toMatch(/'sort-last-message-at':\s*Flags/); }); diff --git a/packages/generator/src/lang/csharp.ts b/packages/generator/src/lang/csharp.ts index 95fdfcfc..267511d6 100644 --- a/packages/generator/src/lang/csharp.ts +++ b/packages/generator/src/lang/csharp.ts @@ -59,7 +59,6 @@ function csType(ft: IRFieldType): string { if (ft.primitive === 'any') return 'object'; if (ft.primitive === 'string') { if (ft.format === 'date-time') return 'DateTimeOffset'; - if (ft.format === 'date') return 'DateOnly'; } return 'string'; case 'enum': @@ -82,7 +81,7 @@ function csType(ft: IRFieldType): string { function isValueType(ft: IRFieldType): boolean { if (ft.kind === 'primitive') { if (ft.primitive === 'integer' || ft.primitive === 'number' || ft.primitive === 'boolean') return true; - if (ft.primitive === 'string' && (ft.format === 'date-time' || ft.format === 'date')) return true; + if (ft.primitive === 'string' && ft.format === 'date-time') return true; } if (ft.kind === 'enum') return true; return false; @@ -149,7 +148,7 @@ function queryParamValueExpr(p: IRParam): string { return `PachcaUtils.EnumToApiString(${paramName}${valueSuffix})`; if (p.type.kind === 'primitive' && p.type.primitive === 'string') return paramName; - return `${paramName}${valueSuffix}.ToString()`; + return `${paramName}${valueSuffix}.ToString()!`; } /** Check if ApiError model exists in IR */ @@ -402,6 +401,7 @@ function generateUtils(): string { return `#nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -644,9 +644,12 @@ function emitPaginationMethod(lines: string[], op: IROperation, ir: IR): void { const baseName = `${snakeToPascal(op.methodName)}Async`; lines.push(`${indent2} var response = await ${baseName}(${callArgs.join(', ')}).ConfigureAwait(false);`); + const rt = ir.responses.find((r) => r.name === op.successResponse.responseRef); + const metaAccess = rt?.metaIsRequired ? 'response.Meta.Paginate.NextPage' : 'response.Meta?.Paginate?.NextPage'; lines.push(`${indent2} items.AddRange(response.Data);`); - lines.push(`${indent2} cursor = response.Meta?.Paginate?.NextPage;`); - lines.push(`${indent2}} while (cursor != null);`); + lines.push(`${indent2} if (response.Data.Count == 0) break;`); + lines.push(`${indent2} cursor = ${metaAccess};`); + lines.push(rt?.metaIsRequired ? `${indent2}} while (true);` : `${indent2}} while (cursor != null);`); lines.push(`${indent2}return items;`); lines.push(`${indent}}`); } @@ -695,9 +698,7 @@ function getReturnType( if (resp.isRedirect) return 'string'; if (!resp.hasBody) return null; if (resp.isList) { - const rt = ir.responses.find( - (r) => r.dataRef === resp.dataRef && r.dataIsArray, - ); + const rt = ir.responses.find((r) => r.name === resp.responseRef); return rt?.name ?? 'object'; } if (resp.isUnwrap && resp.dataRef) return csClientTypeRef(resp.dataRef); @@ -776,13 +777,15 @@ function emitMethodBody( // Escape curly braces in param name for C# string interpolation const paramKey = p.name.replace(/\{/g, '{{').replace(/\}/g, '}}'); if (p.isArray) { + const itemIsEnum = p.type.kind === 'array' && p.type.items?.kind === 'enum'; + const itemExpr = itemIsEnum ? 'PachcaUtils.EnumToApiString(item)' : 'item.ToString()!'; if (p.required) { lines.push(`${indent2}foreach (var item in ${paramName})`); - lines.push(`${indent2} queryParts.Add($"${paramKey}={Uri.EscapeDataString(item.ToString())}");`); + lines.push(`${indent2} queryParts.Add($"${paramKey}={Uri.EscapeDataString(${itemExpr})}");`); } else { lines.push(`${indent2}if (${paramName} != null)`); lines.push(`${indent2} foreach (var item in ${paramName})`); - lines.push(`${indent2} queryParts.Add($"${paramKey}={Uri.EscapeDataString(item.ToString())}");`); + lines.push(`${indent2} queryParts.Add($"${paramKey}={Uri.EscapeDataString(${itemExpr})}");`); } } else { const valueExpr = queryParamValueExpr(p); @@ -856,6 +859,7 @@ function emitMultipartBody( const binaryField = reqModel.fields.find((f) => f.type.kind === 'binary'); const nonBinaryFields = reqModel.fields.filter((f) => f.type.kind !== 'binary'); + const isUnwrapped = shouldUnwrapBody(op.requestBody!); if (op.externalUrl) { lines.push(`${indent2}var url = ${paramSdkName(op.externalUrl)};`); @@ -866,19 +870,19 @@ function emitMultipartBody( lines.push(`${indent2}using var content = new MultipartFormDataContent();`); for (const f of nonBinaryFields) { - const sdkName = fieldSdkName(f); + const sdk = isUnwrapped ? paramSdkName(f.name) : `request.${fieldSdkName(f)}`; const isOptional = !f.required || f.nullable; if (isOptional) { - lines.push(`${indent2}if (request.${sdkName} != null)`); - lines.push(`${indent2} content.Add(new StringContent($"{request.${sdkName}}"), "${f.name}");`); + lines.push(`${indent2}if (${sdk} != null)`); + lines.push(`${indent2} content.Add(new StringContent($"{${sdk}}"), "${f.name}");`); } else { - lines.push(`${indent2}content.Add(new StringContent($"{request.${sdkName}}"), "${f.name}");`); + lines.push(`${indent2}content.Add(new StringContent($"{${sdk}}"), "${f.name}");`); } } if (binaryField) { - const sdkName = fieldSdkName(binaryField); - lines.push(`${indent2}content.Add(new ByteArrayContent(request.${sdkName}), "${binaryField.name}", "${binaryField.name}");`); + const sdk = isUnwrapped ? paramSdkName(binaryField.name) : `request.${fieldSdkName(binaryField)}`; + lines.push(`${indent2}content.Add(new ByteArrayContent(${sdk}), "${binaryField.name}", "${binaryField.name}");`); } lines.push(`${indent2}using var httpRequest = new HttpRequestMessage(HttpMethod.${httpMethodName(op.method.toUpperCase())}, url);`); @@ -915,9 +919,7 @@ function emitResponseHandling( } else if (resp.isList) { lines.push(`${indent2} case ${resp.statusCode}:`); // Use same lookup as getReturnType for consistency - const foundResp = ir.responses.find( - (r) => r.dataRef === resp.dataRef && r.dataIsArray, - ); + const foundResp = ir.responses.find((r) => r.name === resp.responseRef); const rt = foundResp?.name ?? 'object'; lines.push(`${indent2} return PachcaUtils.Deserialize<${rt}>(json);`); } else if (resp.isUnwrap && resp.dataRef) { @@ -1016,7 +1018,10 @@ function csLiteral( return `${ft.example}d`; } if (ft.primitive === 'boolean' && typeof ft.example === 'boolean') return String(ft.example); - if (ft.primitive === 'string' && typeof ft.example === 'string') return `"${ft.example}"`; + if (ft.primitive === 'string' && typeof ft.example === 'string') { + if (ft.format === 'date-time') return `DateTimeOffset.Parse(${JSON.stringify(ft.example)})`; + return JSON.stringify(ft.example); + } } if (ft.kind === 'enum' && typeof ft.example === 'string') { const e = ir.enums.find((en) => en.name === ft.ref); @@ -1032,7 +1037,6 @@ function csLiteral( if (ft.primitive === 'any') return 'new object()'; if (ft.primitive === 'string') { if (ft.format === 'date-time') return 'DateTimeOffset.UtcNow'; - if (ft.format === 'date') return '"2024-01-01"'; } return '"example"'; } @@ -1062,7 +1066,7 @@ function csLiteral( return `default(${ft.ref ?? 'object'})!`; } case 'literal': - return `"${ft.literalValue}"`; + return JSON.stringify(ft.literalValue); case 'binary': return 'Array.Empty()'; } @@ -1085,7 +1089,7 @@ function csModelLiteral( const isCyclic = (f: IRField) => f.type.kind === 'model' && f.type.ref != null && nextVisited.has(f.type.ref); const fields = model.fields.filter( - (f) => (f.type.kind !== 'binary' || f.required) && !(isCyclic(f) && (!f.required || f.nullable)), + (f) => f.type.kind !== 'literal' && (f.type.kind !== 'binary' || f.required) && !(isCyclic(f) && (!f.required || f.nullable)), ); if (fields.length === 0) return `new ${modelName}()`; diff --git a/packages/generator/src/lang/go.ts b/packages/generator/src/lang/go.ts index 081daae1..c6a9d48b 100644 --- a/packages/generator/src/lang/go.ts +++ b/packages/generator/src/lang/go.ts @@ -65,8 +65,8 @@ function goPrimitive( if (ft.primitive === 'boolean') return 'bool'; if (ft.primitive === 'any') return 'any'; if (ft.primitive === 'string') { - if (opts.forParam && (ft.format === 'date' || ft.format === 'date-time')) return 'time.Time'; - if (opts.forModelField && !opts.nullable && (ft.format === 'date' || ft.format === 'date-time')) { + if (opts.forParam && ft.format === 'date-time') return 'time.Time'; + if (opts.forModelField && !opts.nullable && ft.format === 'date-time') { return 'time.Time'; } return 'string'; @@ -281,7 +281,7 @@ function generateTypes(ir: IR): string { const needTime = [ ...ir.models.flatMap((m) => m.fields), ...ir.params.flatMap((p) => p.params), - ].some((f) => f.type.kind === 'primitive' && f.type.primitive === 'string' && (f.type.format === 'date' || f.type.format === 'date-time')); + ].some((f) => f.type.kind === 'primitive' && f.type.primitive === 'string' && f.type.format === 'date-time'); const needFmtStrings = ir.models.some((m) => m.name === 'ApiError'); const needIO = ir.models.some((m) => m.fields.some((f) => f.type.kind === 'binary')); const hasUnions = ir.unions.length > 0; @@ -342,7 +342,7 @@ function goReturn(op: IROperation, ir: IR): string { if (op.successResponse.isRedirect) return '(string, error)'; if (!op.successResponse.hasBody) return 'error'; if (op.successResponse.isList) { - const rt = ir.responses.find((r) => r.dataRef === op.successResponse.dataRef && r.dataIsArray); + const rt = ir.responses.find((r) => r.name === op.successResponse.responseRef); return `(*${rt?.name ?? 'any'}, error)`; } return `(*${op.successResponse.dataRef ?? 'any'}, error)`; @@ -420,24 +420,29 @@ function emitOp(lines: string[], op: IROperation, ir: IR): void { lines.push('\tgo func() {'); lines.push('\t\tdefer pw.Close()'); lines.push('\t\tdefer writer.Close()'); + const isUnwrapped = shouldUnwrapBody(op.requestBody!); if (req) { const bin = req.fields.find((f) => f.type.kind === 'binary'); const non = req.fields.filter((f) => f.type.kind !== 'binary'); for (const f of non.filter((x) => isOptionalField(x))) { - lines.push(`\t\tif request.${goExportName(f.name)} != nil {`); - lines.push(`\t\t\twriter.WriteField(${JSON.stringify(f.name)}, fmt.Sprintf("%v", *request.${goExportName(f.name)}))`); + const ref = isUnwrapped ? snakeToCamel(f.name) : `request.${goExportName(f.name)}`; + const deref = isUnwrapped ? snakeToCamel(f.name) : `*request.${goExportName(f.name)}`; + lines.push(`\t\tif ${ref} != nil {`); + lines.push(`\t\t\twriter.WriteField(${JSON.stringify(f.name)}, fmt.Sprintf("%v", ${deref}))`); lines.push('\t\t}'); } for (const f of non.filter((x) => !isOptionalField(x))) { - lines.push(`\t\twriter.WriteField(${JSON.stringify(f.name)}, fmt.Sprintf("%v", request.${goExportName(f.name)}))`); + const ref = isUnwrapped ? snakeToCamel(f.name) : `request.${goExportName(f.name)}`; + lines.push(`\t\twriter.WriteField(${JSON.stringify(f.name)}, fmt.Sprintf("%v", ${ref}))`); } if (bin) { + const binRef = isUnwrapped ? snakeToCamel(bin.name) : `request.${goExportName(bin.name)}`; lines.push(`\t\tpart, err := writer.CreateFormFile(${JSON.stringify(bin.name)}, "upload")`); lines.push('\t\tif err != nil {'); lines.push('\t\t\tpw.CloseWithError(err)'); lines.push('\t\t\treturn'); lines.push('\t\t}'); - lines.push(`\t\tif _, err := io.Copy(part, request.${goExportName(bin.name)}); err != nil {`); + lines.push(`\t\tif _, err := io.Copy(part, ${binRef}); err != nil {`); lines.push('\t\t\tpw.CloseWithError(err)'); lines.push('\t\t\treturn'); lines.push('\t\t}'); @@ -473,11 +478,14 @@ function emitOp(lines: string[], op: IROperation, ir: IR): void { const hasReqParams = op.queryParams.some((q) => q.required); for (const p of op.queryParams) { const pn = goExportName(p.sdkName); - const isTime = p.type.kind === 'primitive' && p.type.primitive === 'string' && (p.type.format === 'date' || p.type.format === 'date-time'); + const isTime = p.type.kind === 'primitive' && p.type.primitive === 'string' && p.type.format === 'date-time'; if (p.isArray) { - lines.push(`\tfor _, v := range params.${pn} {`); - lines.push(`\t\tq.Add(${JSON.stringify(p.name)}, fmt.Sprintf("%v", v))`); - lines.push('\t}'); + const indent = hasReqParams ? '\t' : '\t\t'; + if (!hasReqParams) lines.push(`\tif params != nil {`); + lines.push(`${indent}for _, v := range params.${pn} {`); + lines.push(`${indent}\tq.Add(${JSON.stringify(p.name)}, fmt.Sprintf("%v", v))`); + lines.push(`${indent}}`); + if (!hasReqParams) lines.push('\t}'); } else if (p.required) { let conv: string; if (isTime) conv = `params.${pn}.Format(time.RFC3339)`; @@ -534,7 +542,7 @@ function emitOp(lines: string[], op: IROperation, ir: IR): void { lines.push(`\tcase ${op.successResponse.statusCode === 201 ? 'http.StatusCreated' : 'http.StatusNoContent'}:`); lines.push('\t\treturn nil'); } else if (op.successResponse.isList) { - const rt = ir.responses.find((r) => r.dataRef === op.successResponse.dataRef && r.dataIsArray); + const rt = ir.responses.find((r) => r.name === op.successResponse.responseRef); lines.push(`\tcase ${op.successResponse.statusCode === 201 ? 'http.StatusCreated' : 'http.StatusOK'}:`); lines.push(`\t\tvar result ${rt?.name ?? 'any'}`); lines.push('\t\tif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {'); @@ -562,14 +570,23 @@ function emitOp(lines: string[], op: IROperation, ir: IR): void { if (op.hasOAuthError) { lines.push('\tcase http.StatusUnauthorized:'); lines.push('\t\tvar e OAuthError'); - lines.push('\t\tjson.NewDecoder(resp.Body).Decode(&e)'); + lines.push('\t\tif err := json.NewDecoder(resp.Body).Decode(&e); err != nil {'); + lines.push('\t\t\te.Err = fmt.Sprintf("HTTP 401: %v", err)'); + lines.push('\t\t}'); lines.push(`\t\t${retOAuth()}`); } if (op.hasApiError || ir.models.some((m) => m.name === 'ApiError')) { lines.push('\tdefault:'); lines.push('\t\tvar e ApiError'); - lines.push('\t\tjson.NewDecoder(resp.Body).Decode(&e)'); + lines.push('\t\tif err := json.NewDecoder(resp.Body).Decode(&e); err != nil {'); + const retFmt = op.successResponse.isRedirect + ? 'return "", fmt.Errorf("HTTP %d: %w", resp.StatusCode, err)' + : !op.successResponse.hasBody + ? 'return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err)' + : 'return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err)'; + lines.push(`\t\t\t${retFmt}`); + lines.push('\t\t}'); lines.push(`\t\t${retApi()}`); } else { lines.push('\tdefault:'); @@ -614,15 +631,19 @@ function emitPaginationMethod(lines: string[], op: IROperation, ir: IR): void { callArgs.push(hasReq ? '*params' : 'params'); } + const rt = ir.responses.find((r) => r.name === op.successResponse.responseRef); + const metaNilCheck = rt?.metaIsRequired ? '' : ' || result.Meta == nil'; + const metaAccess = rt?.metaIsRequired ? 'result.Meta.Paginate.NextPage' : 'result.Meta.Paginate.NextPage'; lines.push(`\t\tresult, err := s.${goMethodName(op)}(${callArgs.join(', ')})`); lines.push('\t\tif err != nil {'); lines.push('\t\t\treturn nil, err'); lines.push('\t\t}'); lines.push('\t\titems = append(items, result.Data...)'); - lines.push('\t\tif result.Meta == nil || result.Meta.Paginate == nil || result.Meta.Paginate.NextPage == nil {'); + lines.push(`\t\tif len(result.Data) == 0${metaNilCheck} {`); lines.push('\t\t\treturn items, nil'); lines.push('\t\t}'); - lines.push('\t\tcursor = result.Meta.Paginate.NextPage'); + lines.push(`\t\tnextPage := ${metaAccess}`); + lines.push('\t\tcursor = &nextPage'); lines.push('\t}'); lines.push('}'); } diff --git a/packages/generator/src/lang/kotlin.ts b/packages/generator/src/lang/kotlin.ts index cc7cb125..2b90740f 100644 --- a/packages/generator/src/lang/kotlin.ts +++ b/packages/generator/src/lang/kotlin.ts @@ -36,6 +36,7 @@ function ktType(ft: IRFieldType): string { if (ft.primitive === 'number') return 'Double'; if (ft.primitive === 'boolean') return 'Boolean'; if (ft.primitive === 'any') return 'Any'; + if (ft.primitive === 'string' && ft.format === 'date-time') return 'OffsetDateTime'; return 'String'; case 'enum': case 'model': @@ -150,12 +151,31 @@ function generateModels(ir: IR): string { lines.push('package com.pachca.sdk'); lines.push(''); + const needDateTime = ir.models.some((m) => m.fields.some((f) => f.type.kind === 'primitive' && f.type.primitive === 'string' && f.type.format === 'date-time')) + || ir.params.some((p) => p.params.some((q) => q.type.kind === 'primitive' && q.type.primitive === 'string' && q.type.format === 'date-time')); + const imports: string[] = []; + if (needDateTime) imports.push('import java.time.OffsetDateTime'); + if (needDateTime) imports.push('import java.time.format.DateTimeFormatter'); + if (needDateTime) imports.push('import kotlinx.serialization.KSerializer'); if (needSerialName) imports.push('import kotlinx.serialization.SerialName'); imports.push('import kotlinx.serialization.Serializable'); if (needTransient) imports.push('import kotlinx.serialization.Transient'); + if (needDateTime) imports.push('import kotlinx.serialization.descriptors.PrimitiveKind'); + if (needDateTime) imports.push('import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor'); + if (needDateTime) imports.push('import kotlinx.serialization.encoding.Decoder'); + if (needDateTime) imports.push('import kotlinx.serialization.encoding.Encoder'); lines.push(imports.join('\n')); + if (needDateTime) { + lines.push(''); + lines.push('object OffsetDateTimeSerializer : KSerializer {'); + lines.push(' override val descriptor = PrimitiveSerialDescriptor("OffsetDateTime", PrimitiveKind.STRING)'); + lines.push(' override fun serialize(encoder: Encoder, value: OffsetDateTime) = encoder.encodeString(value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME))'); + lines.push(' override fun deserialize(decoder: Decoder): OffsetDateTime = OffsetDateTime.parse(decoder.decodeString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME)'); + lines.push('}'); + } + // Enums for (const e of ir.enums) { lines.push(''); @@ -250,8 +270,10 @@ function emitUnion( const isOpt = !f.required; const fullType = isOpt ? `${typeName}?` : typeName; const default_ = isOpt ? ' = null' : ''; + const isDateTime = f.type.kind === 'primitive' && f.type.primitive === 'string' && f.type.format === 'date-time'; + const dtAnnotation = isDateTime ? '@Serializable(with = OffsetDateTimeSerializer::class) ' : ''; const serialName = - needsSerialName(f) ? `@SerialName("${f.name}") ` : ''; + needsSerialName(f) ? `${dtAnnotation}@SerialName("${f.name}") ` : dtAnnotation; lines.push(` ${serialName}val ${sdkName}: ${fullType}${default_},`); } lines.push(`) : ${u.name}`); @@ -315,11 +337,13 @@ function emitModel( default_ = ''; } + const isDateTime = f.type.kind === 'primitive' && f.type.primitive === 'string' && f.type.format === 'date-time'; + const dtAnnotation = isDateTime ? '@Serializable(with = OffsetDateTimeSerializer::class) ' : ''; const annotation = isBinary ? '@Transient ' : needsSerialName(f) - ? `@SerialName("${f.name}") ` - : ''; + ? `${dtAnnotation}@SerialName("${f.name}") ` + : dtAnnotation; lines.push(` ${annotation}val ${sdkName}: ${fullType}${default_},`); } @@ -375,6 +399,10 @@ function generateClient(ir: IR): string { lines.push('import io.ktor.serialization.kotlinx.json.*'); lines.push('import kotlinx.serialization.json.Json'); lines.push('import java.io.Closeable'); + const clientNeedDateTime = ir.params.some((p) => p.params.some((q) => q.type.kind === 'primitive' && q.type.primitive === 'string' && q.type.format === 'date-time')); + if (clientNeedDateTime) { + lines.push('import java.time.OffsetDateTime'); + } // Services for (const svc of ir.services) { @@ -458,9 +486,12 @@ function emitPaginationMethod(lines: string[], op: IROperation, ir: IR): void { ? `${op.methodName}(${callArgs.join(', ')})` : `${op.methodName}(\n${callArgs.map(a => `${indent2} ${a},`).join('\n')}\n${indent2} )`; lines.push(`${indent2} val response = ${callStr}`); + const rt = ir.responses.find((r) => r.name === op.successResponse.responseRef); + const metaAccess = rt?.metaIsRequired ? 'response.meta.paginate.nextPage' : 'response.meta?.paginate?.nextPage'; lines.push(`${indent2} items.addAll(response.data)`); - lines.push(`${indent2} cursor = response.meta?.paginate?.nextPage`); - lines.push(`${indent2}} while (cursor != null)`); + lines.push(`${indent2} if (response.data.isEmpty()) break`); + lines.push(`${indent2} cursor = ${metaAccess}`); + lines.push(rt?.metaIsRequired ? `${indent2}} while (true)` : `${indent2}} while (cursor != null)`); lines.push(`${indent2}return items`); lines.push(`${indent}}`); } @@ -510,9 +541,7 @@ function getReturnType( if (resp.isRedirect) return 'String'; if (!resp.hasBody) return null; if (resp.isList) { - const rt = ir.responses.find( - (r) => r.dataRef === resp.dataRef && r.dataIsArray, - ); + const rt = ir.responses.find((r) => r.name === resp.responseRef); return rt?.name ?? 'Any'; } if (resp.isUnwrap && resp.dataRef) return resp.dataRef; @@ -593,21 +622,26 @@ function emitMethodBody( ); for (const p of op.queryParams) { if (p.isArray) { + const itemIsEnum = p.type.kind === 'array' && p.type.items?.kind === 'enum'; + const itemExpr = itemIsEnum ? 'it.value' : 'it'; if (p.required) { lines.push( - `${indent3}${p.sdkName}.forEach { parameter("${p.name}", it) }`, + `${indent3}${p.sdkName}.forEach { parameter("${p.name}", ${itemExpr}) }`, ); } else { lines.push( - `${indent3}${p.sdkName}?.forEach { parameter("${p.name}", it) }`, + `${indent3}${p.sdkName}?.forEach { parameter("${p.name}", ${itemExpr}) }`, ); } } else { - const valueExpr = p.type.kind === 'enum' ? 'it.value' : 'it'; + const isDateTime = p.type.kind === 'primitive' && p.type.primitive === 'string' && p.type.format === 'date-time'; + const valueExpr = p.type.kind === 'enum' ? 'it.value' : isDateTime ? 'it.toString()' : 'it'; if (p.required) { const reqExpr = p.type.kind === 'enum' ? `${p.sdkName}.value` - : p.sdkName; + : isDateTime + ? `${p.sdkName}.toString()` + : p.sdkName; lines.push(`${indent3}parameter("${p.name}", ${reqExpr})`); } else { lines.push( @@ -678,29 +712,34 @@ function emitMultipartBody( (f) => f.type.kind !== 'binary', ); + const isUnwrapped = shouldUnwrapBody(op.requestBody!); + // Optional fields first (in schema order) for (const f of nonBinaryFields) { const sdkName = fieldSdkName(f); + const ref = isUnwrapped ? sdkName : `request.${sdkName}`; const isOptional = !f.required || f.nullable; if (isOptional) { lines.push( - `${indent4}request.${sdkName}?.let { append("${f.name}", it) }`, + `${indent4}${ref}?.let { append("${f.name}", it) }`, ); } } // Required fields for (const f of nonBinaryFields) { const sdkName = fieldSdkName(f); + const ref = isUnwrapped ? sdkName : `request.${sdkName}`; const isOptional = !f.required || f.nullable; if (!isOptional) { - lines.push(`${indent4}append("${f.name}", request.${sdkName})`); + lines.push(`${indent4}append("${f.name}", ${ref})`); } } // Binary field if (binaryField) { const sdkName = fieldSdkName(binaryField); + const ref = isUnwrapped ? sdkName : `request.${sdkName}`; lines.push( - `${indent4}append("${binaryField.name}", request.${sdkName}, Headers.build {`, + `${indent4}append("${binaryField.name}", ${ref}, Headers.build {`, ); lines.push( `${indent4} append(HttpHeaders.ContentDisposition, "filename=\\"${binaryField.name}\\"")`, @@ -844,7 +883,10 @@ function ktLiteral( return String(ft.example); } if (ft.primitive === 'boolean' && typeof ft.example === 'boolean') return String(ft.example); - if (ft.primitive === 'string' && typeof ft.example === 'string') return JSON.stringify(ft.example); + if (ft.primitive === 'string' && typeof ft.example === 'string') { + if (ft.format === 'date-time') return `OffsetDateTime.parse(${JSON.stringify(ft.example)})`; + return JSON.stringify(ft.example); + } } if (ft.kind === 'enum' && typeof ft.example === 'string') { const e = ir.enums.find((en) => en.name === ft.ref); @@ -860,7 +902,7 @@ function ktLiteral( if (ft.primitive === 'any') return 'mapOf()'; // string with format variants if (ft.primitive === 'string') { - if (ft.format === 'date-time') return '"2024-01-01T00:00:00Z"'; + if (ft.format === 'date-time') return 'OffsetDateTime.parse("2024-01-01T00:00:00Z")'; if (ft.format === 'date') return '"2024-01-01"'; } return '"example"'; diff --git a/packages/generator/src/lang/python.ts b/packages/generator/src/lang/python.ts index 7edd0ab0..4055d07b 100644 --- a/packages/generator/src/lang/python.ts +++ b/packages/generator/src/lang/python.ts @@ -63,6 +63,13 @@ function hasAnyTypeInField(ft: IRFieldType): boolean { return false; } +function hasDateTimeInField(ft: IRFieldType): boolean { + if (ft.kind === 'primitive' && ft.primitive === 'string' && ft.format === 'date-time') return true; + if (ft.items) return hasDateTimeInField(ft.items); + if (ft.valueType) return hasDateTimeInField(ft.valueType); + return false; +} + function hasAnyType(model: IRModel): boolean { return model.fields.some((f) => hasAnyTypeInField(f.type)) || model.inlineObjects.some((m) => hasAnyType(m)); @@ -75,6 +82,7 @@ function pyType(ft: IRFieldType): string { if (ft.primitive === 'number') return 'float'; if (ft.primitive === 'boolean') return 'bool'; if (ft.primitive === 'any') return 'Any'; + if (ft.primitive === 'string' && ft.format === 'date-time') return 'datetime'; return 'str'; case 'enum': case 'model': @@ -209,9 +217,11 @@ function generateModels(ir: IR): string { const needEnum = ir.enums.length > 0; const needUnion = ir.unions.length > 0; const needAny = ir.models.some((m) => hasAnyType(m)) || ir.params.some((p) => p.params.some((q) => hasAnyTypeInField(q.type))); + const needDatetime = ir.models.some((m) => m.fields.some((f) => hasDateTimeInField(f.type))) || ir.params.some((p) => p.params.some((q) => hasDateTimeInField(q.type))); lines.push('from __future__ import annotations'); lines.push(''); + if (needDatetime) lines.push('from datetime import datetime'); if (needDataclass) { lines.push('from dataclasses import dataclass'); if (needEnum) lines.push('from enum import StrEnum'); @@ -277,9 +287,7 @@ function opReturnType(op: IROperation, ir: IR): string { if (op.successResponse.isRedirect) return 'str'; if (!op.successResponse.hasBody) return 'None'; if (op.successResponse.isList) { - const rt = ir.responses.find( - (r) => r.dataRef === op.successResponse.dataRef && r.dataIsArray, - ); + const rt = ir.responses.find((r) => r.name === op.successResponse.responseRef); return rt?.name ?? 'object'; } return op.successResponse.dataRef ?? 'object'; @@ -336,9 +344,7 @@ function collectClientImports(ir: IR): string[] { } if (op.successResponse.hasBody && !op.successResponse.isRedirect) { if (op.successResponse.isList) { - const rt = ir.responses.find( - (r) => r.dataRef === op.successResponse.dataRef && r.dataIsArray, - ); + const rt = ir.responses.find((r) => r.name === op.successResponse.responseRef); if (rt) add(rt.name); if (op.isPaginated && op.successResponse.dataRef) { add(op.successResponse.dataRef); @@ -413,22 +419,28 @@ function emitOperation(lines: string[], op: IROperation, ir: IR): void { if (isMultipart) { lines.push(` data: dict[str, str] = {}`); const req = ir.models.find((m) => m.name === op.requestBody!.schemaRef); + const isUnwrapped = shouldUnwrapBody(op.requestBody!); if (req) { const binary = req.fields.find((f) => f.type.kind === 'binary'); const nonBinary = req.fields.filter((f) => f.type.kind !== 'binary'); for (const f of nonBinary.filter((x) => !isOptionalField(x))) { + const ref = isUnwrapped ? pyFieldName(f) : `request.${pyFieldName(f)}`; lines.push( - ` data[${JSON.stringify(f.name)}] = request.${pyFieldName(f)}`, + ` data[${JSON.stringify(f.name)}] = ${ref}`, ); } for (const f of nonBinary.filter((x) => isOptionalField(x))) { - lines.push(` if request.${pyFieldName(f)} is not None:`); + const ref = isUnwrapped ? pyFieldName(f) : `request.${pyFieldName(f)}`; + lines.push(` if ${ref} is not None:`); lines.push( - ` data[${JSON.stringify(f.name)}] = request.${pyFieldName(f)}`, + ` data[${JSON.stringify(f.name)}] = ${ref}`, ); } + const binaryRef = binary + ? (isUnwrapped ? pyFieldName(binary) : `request.${pyFieldName(binary)}`) + : undefined; const filesExpr = binary - ? `{"${binary.name}": request.${pyFieldName(binary)}}` + ? `{"${binary.name}": ${binaryRef}}` : '{}'; const mpPathStr = op.externalUrl ? camelToSnake(op.externalUrl) @@ -472,16 +484,20 @@ function emitOperation(lines: string[], op: IROperation, ir: IR): void { const v = `params.${paramName}`; const maybe = p.required ? v : `params.${paramName}`; if (p.isArray) { - lines.push(` if ${maybe} is not None:`); + const guard = p.required ? `${maybe} is not None` : `params is not None and ${maybe} is not None`; + lines.push(` if ${guard}:`); lines.push(` for v in ${v}:`); lines.push(` query.append((${JSON.stringify(p.name)}, str(v)))`); } else { + const isDateTime = p.type.kind === 'primitive' && p.type.primitive === 'string' && p.type.format === 'date-time'; const rhs = p.type.kind === 'primitive' && p.type.primitive === 'boolean' ? `str(${v}).lower()` : p.type.kind === 'primitive' && (p.type.primitive === 'integer' || p.type.primitive === 'number') ? `str(${v})` - : v; + : isDateTime + ? `${v}.isoformat()` + : v; if (p.required) { if (op.queryParams.some((x) => x.isArray) || op.queryParams.some((x) => x.required)) { lines.push(` query.append((${JSON.stringify(p.name)}, ${rhs}))`); @@ -560,9 +576,7 @@ function emitOperation(lines: string[], op: IROperation, ir: IR): void { lines.push(` case ${op.successResponse.statusCode}:`); lines.push(' return'); } else if (op.successResponse.isList) { - const rt = ir.responses.find( - (r) => r.dataRef === op.successResponse.dataRef && r.dataIsArray, - ); + const rt = ir.responses.find((r) => r.name === op.successResponse.responseRef); lines.push(` case ${op.successResponse.statusCode}:`); lines.push(` return deserialize(${rt?.name ?? 'object'}, body)`); } else if (op.successResponse.isUnwrap && op.successResponse.dataRef) { @@ -627,10 +641,16 @@ function emitPaginationMethod(lines: string[], op: IROperation, ir: IR): void { } lines.push(` response = await self.${pyMethodName(op)}(${callParts.join(', ')})`); } + const rt = ir.responses.find((r) => r.name === op.successResponse.responseRef); + const metaAccess = rt?.metaIsRequired ? 'response.meta.paginate.next_page' : 'response.meta.paginate.next_page if response.meta else None'; lines.push(' items.extend(response.data)'); - lines.push(' cursor = response.meta.paginate.next_page if response.meta and response.meta.paginate else None'); - lines.push(' if not cursor:'); + lines.push(' if not response.data:'); lines.push(' break'); + lines.push(` cursor = ${metaAccess}`); + if (!rt?.metaIsRequired) { + lines.push(' if not cursor:'); + lines.push(' break'); + } lines.push(' return items'); } @@ -715,6 +735,7 @@ function generateUtils(): string { 'import dataclasses', 'import keyword', 'from dataclasses import asdict, fields', + 'from datetime import datetime', 'from typing import Type, TypeVar, get_args, get_origin, get_type_hints', '', 'import httpx', @@ -770,6 +791,16 @@ function generateUtils(): string { ' item_tp = _resolve_list_item_type(hints[f.name])', ' if item_tp is not None and _is_dataclass_type(item_tp):', ' v = [deserialize(item_tp, i) if isinstance(i, dict) else i for i in v]', + ' elif isinstance(v, str):', + ' hint = hints.get(f.name)', + ' raw_hint = hint', + ' if get_origin(hint) is not None:', + ' for a in get_args(hint):', + ' if a is not type(None):', + ' raw_hint = a', + ' break', + ' if raw_hint is datetime:', + ' v = datetime.fromisoformat(v)', ' kwargs[k] = v', ' return cls(**kwargs)', '', @@ -782,6 +813,8 @@ function generateUtils(): string { ' }', ' if isinstance(val, list):', ' return [_strip_nones(v) for v in val]', + ' if isinstance(val, datetime):', + ' return val.isoformat()', ' return val', '', '', @@ -838,7 +871,10 @@ function pyLiteral( if (ft.kind === 'primitive') { if ((ft.primitive === 'integer' || ft.primitive === 'number') && typeof ft.example === 'number') return String(ft.example); if (ft.primitive === 'boolean' && typeof ft.example === 'boolean') return ft.example ? 'True' : 'False'; - if (ft.primitive === 'string' && typeof ft.example === 'string') return JSON.stringify(ft.example); + if (ft.primitive === 'string' && typeof ft.example === 'string') { + if (ft.format === 'date-time') return `datetime.fromisoformat(${JSON.stringify(ft.example)})`; + return JSON.stringify(ft.example); + } } if (ft.kind === 'enum' && typeof ft.example === 'string') { const e = ir.enums.find((en) => en.name === ft.ref); @@ -853,7 +889,7 @@ function pyLiteral( if (ft.primitive === 'boolean') return 'True'; if (ft.primitive === 'any') return '{}'; if (ft.primitive === 'string') { - if (ft.format === 'date-time') return '"2024-01-01T00:00:00Z"'; + if (ft.format === 'date-time') return 'datetime.fromisoformat("2024-01-01T00:00:00Z")'; if (ft.format === 'date') return '"2024-01-01"'; } return '"example"'; diff --git a/packages/generator/src/lang/swift.ts b/packages/generator/src/lang/swift.ts index 9e8aebea..b15f6794 100644 --- a/packages/generator/src/lang/swift.ts +++ b/packages/generator/src/lang/swift.ts @@ -35,7 +35,6 @@ function swiftType(ft: IRFieldType, opts: { nullable?: boolean } = {}): string { else if (ft.primitive === 'number') base = 'Double'; else if (ft.primitive === 'boolean') base = 'Bool'; else if (ft.primitive === 'any') base = 'AnyCodable'; - else if (ft.format === 'date' || ft.format === 'date-time') base = opts.nullable ? 'String' : 'Date'; else base = 'String'; break; case 'enum': @@ -249,7 +248,7 @@ function opReturn(op: IROperation, ir: IR): string { if (op.successResponse.isRedirect) return 'String'; if (!op.successResponse.hasBody) return 'Void'; if (op.successResponse.isList) { - const rt = ir.responses.find((r) => r.dataRef === op.successResponse.dataRef && r.dataIsArray); + const rt = ir.responses.find((r) => r.name === op.successResponse.responseRef); return rt?.name ?? 'String'; } return op.successResponse.dataRef ?? 'String'; @@ -278,18 +277,18 @@ function emitOperation(lines: string[], op: IROperation, ir: IR): void { if (op.deprecated) lines.push(' @available(*, deprecated)'); lines.push(` public func ${op.methodName}(${args.join(', ')}) async throws -> ${opReturn(op, ir)} {`); if (op.queryParams.length > 0) { - const swiftUrlBase = op.externalUrl ? `\\(${op.externalUrl})` : `\\(baseURL)${op.path}`; + let swiftUrlBase = op.externalUrl ? `\\(${op.externalUrl})` : `\\(baseURL)${op.path}`; + for (const p of op.pathParams) { + swiftUrlBase = swiftUrlBase.replace(`{${p.name}}`, `\\(${snakeToCamel(p.sdkName)})`); + } lines.push(` var components = URLComponents(string: "${swiftUrlBase}")!`); lines.push(' var queryItems: [URLQueryItem] = []'); for (const q of op.queryParams) { const n = snakeToCamel(q.sdkName); const isEnum = q.type.kind === 'enum'; - const isDate = q.type.kind === 'primitive' && (q.type.format === 'date' || q.type.format === 'date-time'); const isModel = q.type.kind === 'model' || q.type.kind === 'record'; function valueExpr(varName: string): string { if (isEnum) return `${varName}.rawValue`; - if (isDate && q.required) return `ISO8601DateFormatter().string(from: ${varName})`; - if (isDate) return varName; // optional dates are typed as String if (isModel) return `String(data: try serialize(${varName}), encoding: .utf8)!`; return `String(${varName})`; } @@ -330,7 +329,14 @@ function emitOperation(lines: string[], op: IROperation, ir: IR): void { lines.push(' request.setValue("application/json", forHTTPHeaderField: "Content-Type")'); if (shouldUnwrapBody(rb)) { const f = rb.unwrapField!; - lines.push(` request.httpBody = try JSONSerialization.data(withJSONObject: [${JSON.stringify(f.name)}: ${swiftIdentifier(f.name)}])`); + const varName = swiftIdentifier(f.name); + let valueExpr = varName; + if (f.type.kind === 'enum') { + valueExpr = `${varName}.rawValue`; + } else if (f.type.kind === 'array' && f.type.items?.kind === 'enum') { + valueExpr = `${varName}.map { $0.rawValue }`; + } + lines.push(` request.httpBody = try JSONSerialization.data(withJSONObject: [${JSON.stringify(f.name)}: ${valueExpr}])`); } else { lines.push(' request.httpBody = try serialize(body)'); } @@ -346,19 +352,22 @@ function emitOperation(lines: string[], op: IROperation, ir: IR): void { lines.push(' data.append("\\(value)\\r\\n".data(using: .utf8)!)'); lines.push(' }'); const req = ir.models.find((m) => m.name === op.requestBody!.schemaRef); + const isUnwrapped = shouldUnwrapBody(op.requestBody!); if (req) { for (const f of req.fields.filter((x) => x.type.kind !== 'binary')) { const n = swiftIdentifier(f.name); - if (isOptionalField(f)) lines.push(` if let v = body.${n} { appendField(${JSON.stringify(f.name)}, String(describing: v)) }`); - else lines.push(` appendField(${JSON.stringify(f.name)}, String(describing: body.${n}))`); + const ref = isUnwrapped ? n : `body.${n}`; + if (isOptionalField(f)) lines.push(` if let v = ${ref} { appendField(${JSON.stringify(f.name)}, String(describing: v)) }`); + else lines.push(` appendField(${JSON.stringify(f.name)}, String(describing: ${ref}))`); } const bin = req.fields.find((x) => x.type.kind === 'binary'); if (bin) { const n = swiftIdentifier(bin.name); + const binRef = isUnwrapped ? n : `body.${n}`; lines.push(' data.append("--\\(boundary)\\r\\n".data(using: .utf8)!)'); lines.push(` data.append("Content-Disposition: form-data; name=\\"${bin.name}\\"; filename=\\"upload\\"\\r\\n".data(using: .utf8)!)`); lines.push(' data.append("Content-Type: application/octet-stream\\r\\n\\r\\n".data(using: .utf8)!)'); - lines.push(` data.append(body.${n})`); + lines.push(` data.append(${binRef})`); lines.push(' data.append("\\r\\n".data(using: .utf8)!)'); } } @@ -452,10 +461,13 @@ function emitPaginationMethod(lines: string[], op: IROperation, ir: IR): void { } } + const rt = ir.responses.find((r) => r.name === op.successResponse.responseRef); + const metaAccess = rt?.metaIsRequired ? 'response.meta.paginate.nextPage' : 'response.meta?.paginate.nextPage'; lines.push(` let response = try await ${op.methodName}(${callArgs.join(', ')})`); lines.push(' items.append(contentsOf: response.data)'); - lines.push(' cursor = response.meta?.paginate?.nextPage'); - lines.push(' } while cursor != nil'); + lines.push(' if response.data.isEmpty { break }'); + lines.push(` cursor = ${metaAccess}`); + lines.push(rt?.metaIsRequired ? ' } while true' : ' } while cursor != nil'); lines.push(' return items'); lines.push(' }'); } @@ -526,13 +538,11 @@ function generateUtils(ir: IR): string { '', 'let pachcaDecoder: JSONDecoder = {', ' let decoder = JSONDecoder()', - ' decoder.dateDecodingStrategy = .iso8601', ' return decoder', '}()', '', 'let pachcaEncoder: JSONEncoder = {', ' let encoder = JSONEncoder()', - ' encoder.dateEncodingStrategy = .iso8601', ' return encoder', '}()', '', diff --git a/packages/generator/src/lang/typescript.ts b/packages/generator/src/lang/typescript.ts index 07ad6716..a609fd7b 100644 --- a/packages/generator/src/lang/typescript.ts +++ b/packages/generator/src/lang/typescript.ts @@ -339,11 +339,7 @@ function responseTypeName(op: IROperation, ir: IR): string { if (op.successResponse.isRedirect) return 'string'; if (!op.successResponse.hasBody) return 'void'; if (op.successResponse.isList) { - const rt = ir.responses.find( - (r) => - r.dataRef === op.successResponse.dataRef && - r.dataIsArray, - ); + const rt = ir.responses.find((r) => r.name === op.successResponse.responseRef); return rt?.name ?? 'unknown'; } return op.successResponse.dataRef ?? 'unknown'; @@ -515,28 +511,32 @@ function emitOperation(lines: string[], op: IROperation, ir: IR): void { if (op.requestBody?.contentType === 'multipart') { lines.push(' const form = new FormData();'); const reqModel = ir.models.find((m) => m.name === op.requestBody!.schemaRef); + const isUnwrapped = shouldUnwrapBody(op.requestBody); if (reqModel) { const nonBinary = reqModel.fields.filter((f) => f.type.kind !== 'binary'); const binary = reqModel.fields.find((f) => f.type.kind === 'binary'); for (const f of nonBinary) { const sdk = fieldSdkName(f); + const ref = isUnwrapped ? sdk : `request.${sdk}`; const optional = !f.required || f.nullable; if (optional) { lines.push( - ` if (request.${sdk} !== undefined) form.set(${JSON.stringify(f.name)}, request.${sdk});`, + ` if (${ref} !== undefined) form.set(${JSON.stringify(f.name)}, ${ref});`, ); } } for (const f of nonBinary) { const sdk = fieldSdkName(f); + const ref = isUnwrapped ? sdk : `request.${sdk}`; const optional = !f.required || f.nullable; if (!optional) { - lines.push(` form.set(${JSON.stringify(f.name)}, request.${sdk});`); + lines.push(` form.set(${JSON.stringify(f.name)}, ${ref});`); } } if (binary) { const sdk = fieldSdkName(binary); - lines.push(` form.set(${JSON.stringify(binary.name)}, request.${sdk}, "upload");`); + const ref = isUnwrapped ? sdk : `request.${sdk}`; + lines.push(` form.set(${JSON.stringify(binary.name)}, ${ref}, "upload");`); } } const fetchUrl = op.externalUrl @@ -547,7 +547,9 @@ function emitOperation(lines: string[], op: IROperation, ir: IR): void { if (!op.noAuth) lines.push(' headers: this.headers,'); lines.push(' body: form,'); lines.push(' });'); - emitResponseSwitch(lines, op, ir, false); + const preloadBody = op.successResponse.hasBody && !op.successResponse.isRedirect; + if (preloadBody) lines.push(' const body = await response.json();'); + emitResponseSwitch(lines, op, ir, preloadBody); lines.push(' }'); return; } @@ -656,9 +658,16 @@ function emitPaginationMethod(lines: string[], op: IROperation, ir: IR): void { for (const p of op.pathParams) callArgs.push(p.sdkName); if (paramsType) callArgs.push('{ ...params, cursor } as ' + paramsType); lines.push(` const response = await this.${op.methodName}(${callArgs.join(', ')});`); + const rt = ir.responses.find((r) => r.name === op.successResponse.responseRef); + const metaAccess = rt?.metaIsRequired ? 'response.meta.paginate.nextPage' : 'response.meta?.paginate.nextPage'; lines.push(' items.push(...response.data);'); - lines.push(' cursor = response.meta?.paginate?.nextPage;'); - lines.push(' } while (cursor);'); + lines.push(' if (response.data.length === 0) break;'); + lines.push(` cursor = ${metaAccess};`); + if (rt?.metaIsRequired) { + lines.push(' } while (true);'); + } else { + lines.push(' } while (cursor);'); + } lines.push(' return items;'); lines.push(' }'); } diff --git a/packages/generator/src/transform.ts b/packages/generator/src/transform.ts index 48a6d816..f332a1f6 100644 --- a/packages/generator/src/transform.ts +++ b/packages/generator/src/transform.ts @@ -41,10 +41,15 @@ function isUnion(schema: Schema): boolean { // ----- Field type resolution ----- function resolveFieldType(schema: Schema): IRFieldType { - // $ref → enum or model reference + // $ref → enum, array, or model reference if (schema.$ref) { const name = refName(schema.$ref); - return { kind: isEnumSchema(schema) ? 'enum' : 'model', ref: name }; + if (isEnumSchema(schema)) return { kind: 'enum', ref: name }; + // Array-type refs (e.g. TagNamesFilter: type: array, items: string) should be inlined + if (getSchemaType(schema) === 'array' && schema.items) { + return { kind: 'array', items: resolveFieldType(schema.items) }; + } + return { kind: 'model', ref: name }; } // anyOf / oneOf → union @@ -393,12 +398,24 @@ function transformParam(param: Parameter): IRParam { type = { kind: 'array', items: type }; } + // Wire name: use first x-param-names entry if available (e.g. "sort[{field}]" → "sort[id]") + let wireName = (param['x-param-names'] && param['x-param-names'].length > 0 && typeof param['x-param-names'][0] === 'object') + ? param['x-param-names'][0].name + : param.name; + + const isArrayParam = isArray || type.kind === 'array'; + + // Array query params need [] suffix for Rails/Rack to parse as arrays + if (isArrayParam && !wireName.endsWith('[]')) { + wireName += '[]'; + } + return { - name: param.name, + name: wireName, sdkName, type, required: !!param.required, - isArray, + isArray: isArrayParam, }; } @@ -695,6 +712,9 @@ export function transform(spec: ParsedAPI): IR { unions.push(transformUnion(name, schema, spec.schemas)); } else if (isEnumSchema(schema)) { enums.push(transformEnum(name, schema)); + } else if (getSchemaType(schema) === 'array') { + // Array-type schemas (e.g. TagNamesFilter) are inlined as array types in field references + continue; } else { models.push(transformModel(name, schema)); } diff --git a/packages/generator/tests/additional-props-bool/snapshots/cs/Utils.cs b/packages/generator/tests/additional-props-bool/snapshots/cs/Utils.cs index 88d4be58..2cf57544 100644 --- a/packages/generator/tests/additional-props-bool/snapshots/cs/Utils.cs +++ b/packages/generator/tests/additional-props-bool/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/packages/generator/tests/additional-props-bool/snapshots/swift/Utils.swift b/packages/generator/tests/additional-props-bool/snapshots/swift/Utils.swift index b9a8fbd1..a2886884 100644 --- a/packages/generator/tests/additional-props-bool/snapshots/swift/Utils.swift +++ b/packages/generator/tests/additional-props-bool/snapshots/swift/Utils.swift @@ -5,13 +5,11 @@ import FoundationNetworking let pachcaDecoder: JSONDecoder = { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 return decoder }() let pachcaEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 return encoder }() diff --git a/packages/generator/tests/allof-sibling/snapshots/cs/Utils.cs b/packages/generator/tests/allof-sibling/snapshots/cs/Utils.cs index 88d4be58..2cf57544 100644 --- a/packages/generator/tests/allof-sibling/snapshots/cs/Utils.cs +++ b/packages/generator/tests/allof-sibling/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/packages/generator/tests/allof-sibling/snapshots/kt/Models.kt b/packages/generator/tests/allof-sibling/snapshots/kt/Models.kt index 3be02bae..014a1331 100644 --- a/packages/generator/tests/allof-sibling/snapshots/kt/Models.kt +++ b/packages/generator/tests/allof-sibling/snapshots/kt/Models.kt @@ -1,18 +1,31 @@ package com.pachca.sdk +import java.time.OffsetDateTime +import java.time.format.DateTimeFormatter +import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +object OffsetDateTimeSerializer : KSerializer { + override val descriptor = PrimitiveSerialDescriptor("OffsetDateTime", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: OffsetDateTime) = encoder.encodeString(value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)) + override fun deserialize(decoder: Decoder): OffsetDateTime = OffsetDateTime.parse(decoder.decodeString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME) +} @Serializable data class BaseEntity( val id: Int, - @SerialName("created_at") val createdAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("created_at") val createdAt: OffsetDateTime, ) @Serializable data class Article( val id: Int, - @SerialName("created_at") val createdAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("created_at") val createdAt: OffsetDateTime, val title: String, val body: String, @SerialName("is_published") val isPublished: Boolean? = null, diff --git a/packages/generator/tests/allof-sibling/snapshots/py/models.py b/packages/generator/tests/allof-sibling/snapshots/py/models.py index eb736045..7e693df9 100644 --- a/packages/generator/tests/allof-sibling/snapshots/py/models.py +++ b/packages/generator/tests/allof-sibling/snapshots/py/models.py @@ -1,17 +1,18 @@ from __future__ import annotations +from datetime import datetime from dataclasses import dataclass @dataclass class BaseEntity: id: int - created_at: str + created_at: datetime @dataclass class Article: id: int - created_at: str + created_at: datetime title: str body: str is_published: bool | None = None diff --git a/packages/generator/tests/allof-sibling/snapshots/swift/Models.swift b/packages/generator/tests/allof-sibling/snapshots/swift/Models.swift index 5ecc86b7..2c5ddbf6 100644 --- a/packages/generator/tests/allof-sibling/snapshots/swift/Models.swift +++ b/packages/generator/tests/allof-sibling/snapshots/swift/Models.swift @@ -5,9 +5,9 @@ import FoundationNetworking public struct BaseEntity: Codable { public let id: Int - public let createdAt: Date + public let createdAt: String - public init(id: Int, createdAt: Date) { + public init(id: Int, createdAt: String) { self.id = id self.createdAt = createdAt } @@ -20,12 +20,12 @@ public struct BaseEntity: Codable { public struct Article: Codable { public let id: Int - public let createdAt: Date + public let createdAt: String public let title: String public let body: String public let isPublished: Bool? - public init(id: Int, createdAt: Date, title: String, body: String, isPublished: Bool? = nil) { + public init(id: Int, createdAt: String, title: String, body: String, isPublished: Bool? = nil) { self.id = id self.createdAt = createdAt self.title = title diff --git a/packages/generator/tests/allof-sibling/snapshots/swift/Utils.swift b/packages/generator/tests/allof-sibling/snapshots/swift/Utils.swift index d65d2a00..8df90b02 100644 --- a/packages/generator/tests/allof-sibling/snapshots/swift/Utils.swift +++ b/packages/generator/tests/allof-sibling/snapshots/swift/Utils.swift @@ -5,13 +5,11 @@ import FoundationNetworking let pachcaDecoder: JSONDecoder = { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 return decoder }() let pachcaEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 return encoder }() diff --git a/packages/generator/tests/array-no-brackets/fixture.yaml b/packages/generator/tests/array-no-brackets/fixture.yaml new file mode 100644 index 00000000..fe0190b2 --- /dev/null +++ b/packages/generator/tests/array-no-brackets/fixture.yaml @@ -0,0 +1,109 @@ +openapi: 3.0.0 +info: + title: Test API — Array Params Without Brackets + version: 1.0.0 +servers: + - url: https://api.pachca.com/api/shared/v1 +tags: + - name: Search +paths: + /search/messages: + get: + operationId: SearchOperations_searchMessages + description: Поиск сообщений с array-фильтрами без скобок в имени + x-paginated: true + parameters: + - name: query + in: query + required: true + description: Поисковый запрос + schema: + type: string + - name: chat_ids + in: query + required: true + description: Фильтр по чатам (required, без скобок в имени) + schema: + type: array + items: + type: integer + format: int32 + - name: user_ids + in: query + required: false + description: Фильтр по авторам (без скобок в имени) + schema: + type: array + items: + type: integer + format: int32 + - name: limit + in: query + required: false + description: Количество записей + schema: + type: integer + format: int32 + - name: cursor + in: query + required: false + description: Курсор пагинации + schema: + type: string + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + - meta + properties: + data: + type: array + items: + $ref: '#/components/schemas/MessageResult' + meta: + $ref: '#/components/schemas/PaginationMeta' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + tags: + - Search +components: + schemas: + MessageResult: + type: object + required: + - id + - content + properties: + id: + type: integer + format: int32 + content: + type: string + + PaginationMeta: + type: object + required: + - paginate + properties: + paginate: + type: object + required: + - next_page + properties: + next_page: + type: string + + OAuthError: + type: object + properties: + error: + type: string diff --git a/packages/generator/tests/array-no-brackets/snapshots/cs/Client.cs b/packages/generator/tests/array-no-brackets/snapshots/cs/Client.cs new file mode 100644 index 00000000..6a2a2a81 --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/cs/Client.cs @@ -0,0 +1,99 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Threading; + +namespace Pachca.Sdk; + +public sealed class SearchService +{ + private readonly string _baseUrl; + private readonly HttpClient _client; + + internal SearchService(string baseUrl, HttpClient client) + { + _baseUrl = baseUrl; + _client = client; + } + + public async System.Threading.Tasks.Task SearchMessagesAsync( + string query, + List chatIds, + List? userIds = null, + int? limit = null, + string? cursor = null, + CancellationToken cancellationToken = default) + { + var queryParts = new List(); + queryParts.Add($"query={Uri.EscapeDataString(query)}"); + foreach (var item in chatIds) + queryParts.Add($"chat_ids[]={Uri.EscapeDataString(item.ToString()!)}"); + if (userIds != null) + foreach (var item in userIds) + queryParts.Add($"user_ids[]={Uri.EscapeDataString(item.ToString()!)}"); + if (limit != null) + queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString()!)}"); + if (cursor != null) + queryParts.Add($"cursor={Uri.EscapeDataString(cursor)}"); + var url = $"{_baseUrl}/search/messages" + (queryParts.Count > 0 ? "?" + string.Join("&", queryParts) : ""); + using var request = new HttpRequestMessage(HttpMethod.Get, url); + using var response = await PachcaUtils.SendWithRetryAsync(_client, request, cancellationToken).ConfigureAwait(false); + var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + switch ((int)response.StatusCode) + { + case 200: + return PachcaUtils.Deserialize(json); + case 401: + throw PachcaUtils.Deserialize(json); + default: + throw new InvalidOperationException($"Unexpected status code: {(int)response.StatusCode}"); + } + } + + public async System.Threading.Tasks.Task> SearchMessagesAllAsync( + string query, + List chatIds, + List? userIds = null, + int? limit = null, + CancellationToken cancellationToken = default) + { + var items = new List(); + string? cursor = null; + do + { + var response = await SearchMessagesAsync(query: query, chatIds: chatIds, userIds: userIds, limit: limit, cursor: cursor, cancellationToken: cancellationToken).ConfigureAwait(false); + items.AddRange(response.Data); + if (response.Data.Count == 0) break; + cursor = response.Meta.Paginate.NextPage; + } while (true); + return items; + } +} + +public sealed class PachcaClient : IDisposable +{ + private readonly HttpClient _client; + + public SearchService Search { get; } + + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1") + { + _client = new HttpClient(); + _client.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Bearer", token); + + Search = new SearchService(baseUrl, _client); + } + + public void Dispose() + { + _client.Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/packages/generator/tests/array-no-brackets/snapshots/cs/Models.cs b/packages/generator/tests/array-no-brackets/snapshots/cs/Models.cs new file mode 100644 index 00000000..4f5ca99c --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/cs/Models.cs @@ -0,0 +1,42 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Pachca.Sdk; + +public class MessageResult +{ + [JsonPropertyName("id")] + public int Id { get; set; } = default!; + [JsonPropertyName("content")] + public string Content { get; set; } = default!; +} + +public class PaginationMetaPaginate +{ + [JsonPropertyName("next_page")] + public string NextPage { get; set; } = default!; +} + +public class PaginationMeta +{ + [JsonPropertyName("paginate")] + public PaginationMetaPaginate Paginate { get; set; } = default!; +} + +public class OAuthError : Exception +{ + [JsonPropertyName("error")] + public string? Error { get; set; } +} + +public class SearchMessagesResponse +{ + [JsonPropertyName("data")] + public List Data { get; set; } = new(); + [JsonPropertyName("meta")] + public PaginationMeta Meta { get; set; } = default!; +} diff --git a/packages/generator/tests/array-no-brackets/snapshots/cs/Utils.cs b/packages/generator/tests/array-no-brackets/snapshots/cs/Utils.cs new file mode 100644 index 00000000..2cf57544 --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/cs/Utils.cs @@ -0,0 +1,103 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace Pachca.Sdk; + +internal static class PachcaUtils +{ + private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); + + internal static readonly JsonSerializerOptions JsonOptions = new() + { + DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true, + }; + + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + + internal static async Task SendWithRetryAsync( + HttpClient client, + HttpRequestMessage request, + CancellationToken cancellationToken = default) + { + for (var attempt = 0; attempt <= MaxRetries; attempt++) + { + HttpRequestMessage req; + if (attempt == 0) + { + req = request; + } + else + { + req = await CloneRequestAsync(request).ConfigureAwait(false); + } + + var response = await client.SendAsync(req, cancellationToken).ConfigureAwait(false); + + if ((int)response.StatusCode == 429 && attempt < MaxRetries) + { + var delay = response.Headers.RetryAfter?.Delta + ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); + await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + response.Dispose(); + continue; + } + + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) + { + var delay = AddJitter(TimeSpan.FromSeconds(10 * Math.Pow(2, attempt))); + await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + response.Dispose(); + continue; + } + + return response; + } + + return await client.SendAsync( + await CloneRequestAsync(request).ConfigureAwait(false), + cancellationToken).ConfigureAwait(false); + } + + private static async Task CloneRequestAsync(HttpRequestMessage request) + { + var clone = new HttpRequestMessage(request.Method, request.RequestUri); + foreach (var header in request.Headers) + clone.Headers.TryAddWithoutValidation(header.Key, header.Value); + + if (request.Content != null) + { + var content = await request.Content.ReadAsByteArrayAsync().ConfigureAwait(false); + clone.Content = new ByteArrayContent(content); + if (request.Content.Headers.ContentType != null) + clone.Content.Headers.ContentType = request.Content.Headers.ContentType; + } + + return clone; + } + + internal static T Deserialize(string json) => + JsonSerializer.Deserialize(json, JsonOptions) + ?? throw new InvalidOperationException("Deserialization returned null"); + + internal static string Serialize(T value) => + JsonSerializer.Serialize(value, JsonOptions); + + internal static string EnumToApiString(T value) where T : struct, Enum => + JsonSerializer.Serialize(value, JsonOptions).Trim('"'); +} diff --git a/packages/generator/tests/array-no-brackets/snapshots/cs/examples.json b/packages/generator/tests/array-no-brackets/snapshots/cs/examples.json new file mode 100644 index 00000000..599d862d --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/cs/examples.json @@ -0,0 +1,12 @@ +{ + "Client_Init": { + "usage": "using var client = new PachcaClient(\"YOUR_TOKEN\");", + "imports": [ + "PachcaClient" + ] + }, + "SearchOperations_searchMessages": { + "usage": "var chatIds = new List { 123 };\nvar userIds = new List { 123 };\nvar response = await client.Search.SearchMessagesAsync(\"example\", chatIds, userIds, 123, \"example\");", + "output": "SearchMessagesResponse(Data: List, Meta: PaginationMeta)" + } +} diff --git a/packages/generator/tests/array-no-brackets/snapshots/go/client.go b/packages/generator/tests/array-no-brackets/snapshots/go/client.go new file mode 100644 index 00000000..26c835a4 --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/go/client.go @@ -0,0 +1,150 @@ +package pachca + +import ( + "context" + "encoding/json" + "fmt" + "math/rand" + "net/http" + "net/url" + "strconv" + "time" +) + +type authTransport struct { + token string + base http.RoundTripper +} + +func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req.Header.Set("Authorization", "Bearer "+t.token) + return t.base.RoundTrip(req) +} + +const maxRetries = 3 + +var retryable5xx = map[int]bool{500: true, 502: true, 503: true, 504: true} + +func jitter(d time.Duration) time.Duration { + return time.Duration(float64(d) * (0.5 + rand.Float64()*0.5)) +} + +func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) { + for attempt := 0; ; attempt++ { + if attempt > 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 { url = baseURL[0] } + client := &http.Client{ + Transport: &authTransport{token: token, base: http.DefaultTransport}, + } + return &PachcaClient{ + Search: &SearchService{baseURL: url, client: client}, + } +} diff --git a/packages/generator/tests/array-no-brackets/snapshots/go/examples.json b/packages/generator/tests/array-no-brackets/snapshots/go/examples.json new file mode 100644 index 00000000..4482cec2 --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/go/examples.json @@ -0,0 +1,15 @@ +{ + "Client_Init": { + "usage": "client := pachca.NewPachcaClient(\"YOUR_TOKEN\")", + "imports": [ + "PachcaClient" + ] + }, + "SearchOperations_searchMessages": { + "usage": "params := SearchMessagesParams{\n\tQuery: \"example\",\n\tChatIDs: []int32{int32(123)},\n\tUserIDs: []int32{int32(123)},\n\tLimit: Ptr(int32(123)),\n\tCursor: Ptr(\"example\"),\n}\nresponse, err := client.Search.SearchMessages(ctx, params)", + "output": "SearchMessagesResponse{Data: []MessageResult, Meta: PaginationMeta}", + "imports": [ + "SearchMessagesParams" + ] + } +} diff --git a/packages/generator/tests/array-no-brackets/snapshots/go/types.go b/packages/generator/tests/array-no-brackets/snapshots/go/types.go new file mode 100644 index 00000000..56ab3d96 --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/go/types.go @@ -0,0 +1,38 @@ +package pachca + +type MessageResult struct { + ID int32 `json:"id"` + Content string `json:"content"` +} + +type PaginationMetaPaginate struct { + NextPage string `json:"next_page"` +} + +type PaginationMeta struct { + Paginate PaginationMetaPaginate `json:"paginate"` +} + +type OAuthError struct { + Err *string `json:"error,omitempty"` +} + +func (e *OAuthError) Error() string { + if e.Err != nil { + return *e.Err + } + return "oauth error" +} + +type SearchMessagesParams struct { + Query string + ChatIDs []int32 + UserIDs []int32 + Limit *int32 + Cursor *string +} + +type SearchMessagesResponse struct { + Data []MessageResult `json:"data"` + Meta PaginationMeta `json:"meta"` +} diff --git a/packages/generator/tests/array-no-brackets/snapshots/go/utils.go b/packages/generator/tests/array-no-brackets/snapshots/go/utils.go new file mode 100644 index 00000000..d0527665 --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/go/utils.go @@ -0,0 +1,6 @@ +package pachca + +// Ptr returns a pointer to the given value. +func Ptr[T any](v T) *T { + return &v +} diff --git a/packages/generator/tests/array-no-brackets/snapshots/kt/Client.kt b/packages/generator/tests/array-no-brackets/snapshots/kt/Client.kt new file mode 100644 index 00000000..85fbc2e6 --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/kt/Client.kt @@ -0,0 +1,97 @@ +package com.pachca.sdk + +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.plugins.* +import io.ktor.client.plugins.auth.* +import io.ktor.client.plugins.auth.providers.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.* +import kotlinx.serialization.json.Json +import java.io.Closeable + +class SearchService internal constructor( + private val baseUrl: String, + private val client: HttpClient, +) { + suspend fun searchMessages( + query: String, + chatIds: List, + userIds: List? = null, + limit: Int? = null, + cursor: String? = null, + ): SearchMessagesResponse { + val response = client.get("$baseUrl/search/messages") { + parameter("query", query) + chatIds.forEach { parameter("chat_ids[]", it) } + userIds?.forEach { parameter("user_ids[]", it) } + limit?.let { parameter("limit", it) } + cursor?.let { parameter("cursor", it) } + } + return when (response.status.value) { + 200 -> response.body() + 401 -> throw response.body() + else -> throw RuntimeException("Unexpected status code: ${response.status.value}") + } + } + + suspend fun searchMessagesAll( + query: String, + chatIds: List, + userIds: List? = null, + limit: Int? = null, + ): List { + val items = mutableListOf() + var cursor: String? = null + do { + val response = searchMessages( + query = query, + chatIds = chatIds, + userIds = userIds, + limit = limit, + cursor = cursor, + ) + items.addAll(response.data) + if (response.data.isEmpty()) break + cursor = response.meta.paginate.nextPage + } while (true) + return items + } +} + +class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1") : Closeable { + private val client = HttpClient { + expectSuccess = false + install(ContentNegotiation) { + json(Json { explicitNulls = false }) + } + install(HttpRequestRetry) { + maxRetries = 3 + retryIf { _, response -> + response.status.value == 429 || response.status.value in setOf(500, 502, 503, 504) + } + delayMillis { retry -> + val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() + if (retryAfter != null && response?.status?.value == 429) { + retryAfter * 1000L + } else { + val base = 10_000L * (1L shl retry) + val jitter = 0.5 + kotlin.random.Random.nextDouble() * 0.5 + (base * jitter).toLong() + } + } + } + defaultRequest { + bearerAuth(token) + } + } + + val search = SearchService(baseUrl, client) + + override fun close() { + client.close() + } +} diff --git a/packages/generator/tests/array-no-brackets/snapshots/kt/Models.kt b/packages/generator/tests/array-no-brackets/snapshots/kt/Models.kt new file mode 100644 index 00000000..6e1cd9d7 --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/kt/Models.kt @@ -0,0 +1,31 @@ +package com.pachca.sdk + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class MessageResult( + val id: Int, + val content: String, +) + +@Serializable +data class PaginationMetaPaginate( + @SerialName("next_page") val nextPage: String, +) + +@Serializable +data class PaginationMeta( + val paginate: PaginationMetaPaginate, +) + +@Serializable +data class OAuthError( + val error: String? = null, +) : Exception() + +@Serializable +data class SearchMessagesResponse( + val data: List, + val meta: PaginationMeta, +) diff --git a/packages/generator/tests/array-no-brackets/snapshots/kt/examples.json b/packages/generator/tests/array-no-brackets/snapshots/kt/examples.json new file mode 100644 index 00000000..16676c4b --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/kt/examples.json @@ -0,0 +1,12 @@ +{ + "Client_Init": { + "usage": "val client = PachcaClient(\"YOUR_TOKEN\")", + "imports": [ + "PachcaClient" + ] + }, + "SearchOperations_searchMessages": { + "usage": "val chatIds = listOf(123)\nval userIds = listOf(123)\nval response = client.search.searchMessages(query = \"example\", chatIds = chatIds, userIds = userIds, limit = 123, cursor = \"example\")", + "output": "SearchMessagesResponse(data: List, meta: PaginationMeta)" + } +} diff --git a/packages/generator/tests/array-no-brackets/snapshots/py/__init__.py b/packages/generator/tests/array-no-brackets/snapshots/py/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/packages/generator/tests/array-no-brackets/snapshots/py/client.py b/packages/generator/tests/array-no-brackets/snapshots/py/client.py new file mode 100644 index 00000000..1b7d7ffe --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/py/client.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +import httpx + +from .models import ( + SearchMessagesParams, + SearchMessagesResponse, + MessageResult, + OAuthError, +) +from .utils import deserialize, RetryTransport + +class SearchService: + def __init__(self, client: httpx.AsyncClient) -> None: + self._client = client + + async def search_messages( + self, + params: SearchMessagesParams, + ) -> SearchMessagesResponse: + query: list[tuple[str, str]] = [] + query.append(("query", params.query)) + if params.chat_ids is not None: + for v in params.chat_ids: + query.append(("chat_ids[]", str(v))) + if params is not None and params.user_ids is not None: + for v in params.user_ids: + query.append(("user_ids[]", str(v))) + if params is not None and params.limit is not None: + query.append(("limit", str(params.limit))) + if params is not None and params.cursor is not None: + query.append(("cursor", params.cursor)) + response = await self._client.get( + "/search/messages", + params=query, + ) + body = response.json() + match response.status_code: + case 200: + return deserialize(SearchMessagesResponse, body) + case 401: + raise deserialize(OAuthError, body) + case _: + raise RuntimeError( + f"Unexpected status code: {response.status_code}" + ) + + async def search_messages_all( + self, + params: SearchMessagesParams, + ) -> list[MessageResult]: + items: list[MessageResult] = [] + cursor: str | None = None + while True: + if params is None: + params = SearchMessagesParams() + params.cursor = cursor + response = await self.search_messages(params=params) + items.extend(response.data) + if not response.data: + break + cursor = response.meta.paginate.next_page + return items + + +class PachcaClient: + def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1") -> None: + self._client = httpx.AsyncClient( + base_url=base_url, + headers={"Authorization": f"Bearer {token}"}, + transport=RetryTransport(httpx.AsyncHTTPTransport()), + ) + self.search = SearchService(self._client) + + async def close(self) -> None: + await self._client.aclose() diff --git a/packages/generator/tests/array-no-brackets/snapshots/py/examples.json b/packages/generator/tests/array-no-brackets/snapshots/py/examples.json new file mode 100644 index 00000000..1a507f34 --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/py/examples.json @@ -0,0 +1,15 @@ +{ + "Client_Init": { + "usage": "client = PachcaClient(\"YOUR_TOKEN\")", + "imports": [ + "PachcaClient" + ] + }, + "SearchOperations_searchMessages": { + "usage": "params = SearchMessagesParams(\n query=\"example\",\n chat_ids=[123],\n user_ids=[123],\n limit=123,\n cursor=\"example\"\n)\nresponse = await client.search.search_messages(params=params)", + "output": "SearchMessagesResponse(data: list[MessageResult], meta: PaginationMeta)", + "imports": [ + "SearchMessagesParams" + ] + } +} diff --git a/packages/generator/tests/array-no-brackets/snapshots/py/models.py b/packages/generator/tests/array-no-brackets/snapshots/py/models.py new file mode 100644 index 00000000..88988a19 --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/py/models.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +from dataclasses import dataclass + +@dataclass +class MessageResult: + id: int + content: str + + +@dataclass +class PaginationMetaPaginate: + next_page: str + + +@dataclass +class PaginationMeta: + paginate: PaginationMetaPaginate + + +@dataclass +class OAuthError(Exception): + error: str | None = None + + +@dataclass +class SearchMessagesParams: + query: str + chat_ids: list[int] + user_ids: list[int] | None = None + limit: int | None = None + cursor: str | None = None + + +@dataclass +class SearchMessagesResponse: + data: list[MessageResult] + meta: PaginationMeta diff --git a/packages/generator/tests/array-no-brackets/snapshots/py/utils.py b/packages/generator/tests/array-no-brackets/snapshots/py/utils.py new file mode 100644 index 00000000..9288f902 --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/py/utils.py @@ -0,0 +1,124 @@ +from __future__ import annotations + +import dataclasses +import keyword +from dataclasses import asdict, fields +from datetime import datetime +from typing import Type, TypeVar, get_args, get_origin, get_type_hints + +import httpx + +T = TypeVar("T") + + +def _is_dataclass_type(tp: type) -> bool: + return isinstance(tp, type) and dataclasses.is_dataclass(tp) + + +def _resolve_type(tp: type) -> type | None: + """Extract a concrete dataclass type from Optional[X] or X | None.""" + origin = get_origin(tp) + if origin is list: + return None # lists are handled inline + args = get_args(tp) + for arg in args: + if _is_dataclass_type(arg): + return arg + if _is_dataclass_type(tp): + return tp + return None + + +def _resolve_list_item_type(tp: type) -> type | None: + """Extract the item type from list[X].""" + origin = get_origin(tp) + if origin is list: + args = get_args(tp) + if args: + return args[0] + return None + + +def deserialize(cls: Type[T], data: dict) -> T: + """Create a dataclass instance from a dict, recursively deserializing nested dataclasses.""" + field_map = {f.name: f for f in fields(cls)} + hints = get_type_hints(cls) + norm = {k.replace("-", "_").lower(): v for k, v in data.items()} + kwargs = {} + for k, v in norm.items(): + if k not in field_map: + k = f"{k}_" + if k not in field_map: + continue + f = field_map[k] + if isinstance(v, dict): + nested = _resolve_type(hints[f.name]) + if nested is not None: + v = deserialize(nested, v) + elif isinstance(v, list) and v: + item_tp = _resolve_list_item_type(hints[f.name]) + if item_tp is not None and _is_dataclass_type(item_tp): + v = [deserialize(item_tp, i) if isinstance(i, dict) else i for i in v] + elif isinstance(v, str): + hint = hints.get(f.name) + raw_hint = hint + if get_origin(hint) is not None: + for a in get_args(hint): + if a is not type(None): + raw_hint = a + break + if raw_hint is datetime: + v = datetime.fromisoformat(v) + kwargs[k] = v + return cls(**kwargs) + + +def _strip_nones(val: object) -> object: + if isinstance(val, dict): + return { + (k[:-1] if k.endswith("_") and keyword.iskeyword(k[:-1]) else k): _strip_nones(v) + for k, v in val.items() if v is not None + } + if isinstance(val, list): + return [_strip_nones(v) for v in val] + if isinstance(val, datetime): + return val.isoformat() + return val + + +def serialize(obj: object) -> dict: + """Convert a dataclass to a dict, recursively omitting None values.""" + return _strip_nones(asdict(obj)) + + +_MAX_RETRIES = 3 +_RETRYABLE_5XX = {500, 502, 503, 504} + + +def _jitter(delay: float) -> float: + import random + return delay * (0.5 + random.random() * 0.5) + + +class RetryTransport(httpx.AsyncBaseTransport): + """Wraps an httpx transport with retry on 429 Too Many Requests and 5xx errors.""" + + def __init__(self, transport: httpx.AsyncBaseTransport, max_retries: int = _MAX_RETRIES) -> None: + self._transport = transport + self._max_retries = max_retries + + async def handle_async_request(self, request: httpx.Request) -> httpx.Response: + import asyncio + for attempt in range(self._max_retries + 1): + response = await self._transport.handle_async_request(request) + if response.status_code == 429 and attempt < self._max_retries: + retry_after = response.headers.get("retry-after") + delay = int(retry_after) if retry_after and retry_after.isdigit() else 2 ** attempt + await asyncio.sleep(delay) + continue + if response.status_code in _RETRYABLE_5XX and attempt < self._max_retries: + delay = _jitter(10 * (2 ** attempt)) + await asyncio.sleep(delay) + continue + return response + return response # unreachable diff --git a/packages/generator/tests/array-no-brackets/snapshots/swift/Client.swift b/packages/generator/tests/array-no-brackets/snapshots/swift/Client.swift new file mode 100644 index 00000000..3fe79eb7 --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/swift/Client.swift @@ -0,0 +1,60 @@ +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif + +public struct SearchService { + let baseURL: String + let headers: [String: String] + let session: URLSession + + init(baseURL: String, headers: [String: String], session: URLSession = .shared) { + self.baseURL = baseURL + self.headers = headers + self.session = session + } + + public func searchMessages(query: String, chatIds: [Int], userIds: [Int]? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> SearchMessagesResponse { + var components = URLComponents(string: "\(baseURL)/search/messages")! + var queryItems: [URLQueryItem] = [] + queryItems.append(URLQueryItem(name: "query", value: String(query))) + chatIds.forEach { queryItems.append(URLQueryItem(name: "chat_ids[]", value: String($0))) } + if let userIds { userIds.forEach { queryItems.append(URLQueryItem(name: "user_ids[]", value: String($0))) } } + if let limit { queryItems.append(URLQueryItem(name: "limit", value: String(limit))) } + if let cursor { queryItems.append(URLQueryItem(name: "cursor", value: String(cursor))) } + if !queryItems.isEmpty { components.queryItems = queryItems } + var request = URLRequest(url: components.url!) + headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } + let (data, urlResponse) = try await dataWithRetry(session: session, for: request) + let statusCode = (urlResponse as! HTTPURLResponse).statusCode + switch statusCode { + case 200: + return try deserialize(SearchMessagesResponse.self, from: data) + case 401: + throw try deserialize(OAuthError.self, from: data) + default: + throw URLError(.badServerResponse) + } + } + + public func searchMessagesAll(query: String, chatIds: [Int], userIds: [Int]? = nil, limit: Int? = nil) async throws -> [MessageResult] { + var items: [MessageResult] = [] + var cursor: String? = nil + repeat { + let response = try await searchMessages(query: query, chatIds: chatIds, userIds: userIds, limit: limit, cursor: cursor) + items.append(contentsOf: response.data) + if response.data.isEmpty { break } + cursor = response.meta.paginate.nextPage + } while true + return items + } +} + +public struct PachcaClient { + public let search: SearchService + + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1") { + let headers = ["Authorization": "Bearer \(token)"] + self.search = SearchService(baseURL: baseURL, headers: headers) + } +} diff --git a/packages/generator/tests/array-no-brackets/snapshots/swift/Models.swift b/packages/generator/tests/array-no-brackets/snapshots/swift/Models.swift new file mode 100644 index 00000000..6efcf51b --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/swift/Models.swift @@ -0,0 +1,47 @@ +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif + +public struct MessageResult: Codable { + public let id: Int + public let content: String + + public init(id: Int, content: String) { + self.id = id + self.content = content + } +} + +public struct PaginationMetaPaginate: Codable { + public let nextPage: String + + public init(nextPage: String) { + self.nextPage = nextPage + } + + enum CodingKeys: String, CodingKey { + case nextPage = "next_page" + } +} + +public struct PaginationMeta: Codable { + public let paginate: PaginationMetaPaginate + + public init(paginate: PaginationMetaPaginate) { + self.paginate = paginate + } +} + +public struct OAuthError: Codable, Error { + public let error: String? + + public init(error: String? = nil) { + self.error = error + } +} + +public struct SearchMessagesResponse: Codable { + public let data: [MessageResult] + public let meta: PaginationMeta +} diff --git a/packages/generator/tests/array-no-brackets/snapshots/swift/Utils.swift b/packages/generator/tests/array-no-brackets/snapshots/swift/Utils.swift new file mode 100644 index 00000000..8df90b02 --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/swift/Utils.swift @@ -0,0 +1,67 @@ +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif + +let pachcaDecoder: JSONDecoder = { + let decoder = JSONDecoder() + return decoder +}() + +let pachcaEncoder: JSONEncoder = { + let encoder = JSONEncoder() + return encoder +}() + +func serialize(_ value: T) throws -> Data { + let data = try pachcaEncoder.encode(value) + let json = try JSONSerialization.jsonObject(with: data) + return try JSONSerialization.data(withJSONObject: stripNulls(json)) +} + +func deserialize(_ type: T.Type, from data: Data) throws -> T { + return try pachcaDecoder.decode(type, from: data) +} + +private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func jitter(_ delay: UInt64) -> UInt64 { + return UInt64(Double(delay) * (0.5 + Double.random(in: 0..<0.5))) +} + +func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { + for attempt in 0...maxRetries { + let (data, response) = try await session.data(for: request, delegate: delegate) + if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: delay) + continue + } + if let http = response as? HTTPURLResponse, retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = jitter(10 * UInt64(pow(2.0, Double(attempt))) * 1_000_000_000) + try await _Concurrency.Task.sleep(nanoseconds: delay) + continue + } + return (data, response) + } + return try await session.data(for: request, delegate: delegate) // unreachable +} + +private func stripNulls(_ value: Any) -> Any { + if let dict = value as? [String: Any] { + return dict.compactMapValues { v -> Any? in + if v is NSNull { return nil } + return stripNulls(v) + } + } + if let arr = value as? [Any] { + return arr.map(stripNulls) + } + return value +} diff --git a/packages/generator/tests/array-no-brackets/snapshots/swift/examples.json b/packages/generator/tests/array-no-brackets/snapshots/swift/examples.json new file mode 100644 index 00000000..b8790c95 --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/swift/examples.json @@ -0,0 +1,12 @@ +{ + "Client_Init": { + "usage": "let client = PachcaClient(token: \"YOUR_TOKEN\")", + "imports": [ + "PachcaClient" + ] + }, + "SearchOperations_searchMessages": { + "usage": "let chatIds = [123]\nlet userIds = [123]\nlet response = try await client.search.searchMessages(query: \"example\", chatIds: chatIds, userIds: userIds, limit: 123, cursor: \"example\")", + "output": "SearchMessagesResponse(data: [MessageResult], meta: PaginationMeta)" + } +} diff --git a/packages/generator/tests/array-no-brackets/snapshots/ts/client.ts b/packages/generator/tests/array-no-brackets/snapshots/ts/client.ts new file mode 100644 index 00000000..e2ad6748 --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/ts/client.ts @@ -0,0 +1,60 @@ +import { + SearchMessagesParams, + SearchMessagesResponse, + MessageResult, + OAuthError, +} from "./types"; +import { deserialize, fetchWithRetry } from "./utils"; + +class SearchService { + constructor( + private baseUrl: string, + private headers: Record, + ) {} + + async searchMessages(params: SearchMessagesParams): Promise { + const query = new URLSearchParams(); + query.set("query", params.query); + if (params.chatIds !== undefined) { + params.chatIds.forEach((v) => query.append("chat_ids[]", String(v))); + } + if (params?.userIds !== undefined) { + params.userIds.forEach((v) => query.append("user_ids[]", String(v))); + } + if (params?.limit !== undefined) query.set("limit", String(params.limit)); + if (params?.cursor !== undefined) query.set("cursor", params.cursor); + const response = await fetchWithRetry(`${this.baseUrl}/search/messages?${query}`, { + headers: this.headers, + }); + const body = await response.json(); + switch (response.status) { + case 200: + return deserialize(body) as SearchMessagesResponse; + case 401: + throw new OAuthError(body.error); + default: + throw new Error(`HTTP ${response.status}: ${JSON.stringify(body)}`); + } + } + + async searchMessagesAll(params: Omit): Promise { + const items: MessageResult[] = []; + let cursor: string | undefined; + do { + const response = await this.searchMessages({ ...params, cursor } as SearchMessagesParams); + items.push(...response.data); + if (response.data.length === 0) break; + cursor = response.meta.paginate.nextPage; + } while (true); + return items; + } +} + +export class PachcaClient { + readonly search: SearchService; + + constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1") { + const headers = { Authorization: `Bearer ${token}` }; + this.search = new SearchService(baseUrl, headers); + } +} diff --git a/packages/generator/tests/array-no-brackets/snapshots/ts/examples.json b/packages/generator/tests/array-no-brackets/snapshots/ts/examples.json new file mode 100644 index 00000000..89214d07 --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/ts/examples.json @@ -0,0 +1,12 @@ +{ + "Client_Init": { + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "imports": [ + "PachcaClient" + ] + }, + "SearchOperations_searchMessages": { + "usage": "const response = client.search.searchMessages({\n query: \"example\",\n chatIds: [123],\n userIds: [123],\n limit: 123,\n cursor: \"example\"\n})", + "output": "SearchMessagesResponse({ data: MessageResult[], meta: PaginationMeta })" + } +} diff --git a/packages/generator/tests/array-no-brackets/snapshots/ts/types.ts b/packages/generator/tests/array-no-brackets/snapshots/ts/types.ts new file mode 100644 index 00000000..96e7963d --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/ts/types.ts @@ -0,0 +1,31 @@ +export interface MessageResult { + id: number; + content: string; +} + +export interface PaginationMeta { + paginate: { + nextPage: string; + }; +} + +export class OAuthError extends Error { + error?: string; + constructor(error?: string) { + super(error); + this.error = error; + } +} + +export interface SearchMessagesParams { + query: string; + chatIds: number[]; + userIds?: number[]; + limit?: number; + cursor?: string; +} + +export interface SearchMessagesResponse { + data: MessageResult[]; + meta: PaginationMeta; +} diff --git a/packages/generator/tests/array-no-brackets/snapshots/ts/utils.ts b/packages/generator/tests/array-no-brackets/snapshots/ts/utils.ts new file mode 100644 index 00000000..5cf7ef4a --- /dev/null +++ b/packages/generator/tests/array-no-brackets/snapshots/ts/utils.ts @@ -0,0 +1,58 @@ +function snakeToCamel(str: string): string { + const camel = str.replace(/[-_]([a-zA-Z])/g, (_, c) => c.toUpperCase()); + return camel.charAt(0).toLowerCase() + camel.slice(1); +} + +function camelToSnake(str: string): string { + return str + .replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2") + .replace(/([a-z0-9])([A-Z])/g, "$1_$2") + .toLowerCase(); +} + +export function deserialize(obj: unknown): unknown { + if (Array.isArray(obj)) return obj.map(deserialize); + if (obj !== null && typeof obj === "object") { + return Object.fromEntries( + Object.entries(obj).map(([k, v]) => [snakeToCamel(k), deserialize(v)]), + ); + } + return obj; +} + +export function serialize(obj: unknown): unknown { + if (Array.isArray(obj)) return obj.map(serialize); + if (obj !== null && typeof obj === "object") { + return Object.fromEntries( + Object.entries(obj) + .filter(([, v]) => v !== undefined) + .map(([k, v]) => [camelToSnake(k), serialize(v)]), + ); + } + return obj; +} + +const MAX_RETRIES = 3; +const RETRYABLE_5XX = new Set([500, 502, 503, 504]); + +function jitter(delay: number): number { + return delay * (0.5 + Math.random() * 0.5); +} + +export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestInit): Promise { + for (let attempt = 0; ; attempt++) { + const response = await fetch(input, init); + if (response.status === 429 && attempt < MAX_RETRIES) { + const retryAfter = response.headers.get("retry-after"); + const delay = retryAfter ? Number(retryAfter) * 1000 : 1000 * Math.pow(2, attempt); + await new Promise((r) => setTimeout(r, delay)); + continue; + } + if (RETRYABLE_5XX.has(response.status) && attempt < MAX_RETRIES) { + const delay = jitter(10000 * Math.pow(2, attempt)); + await new Promise((r) => setTimeout(r, delay)); + continue; + } + return response; + } +} diff --git a/packages/generator/tests/circular-ref/snapshots/cs/Utils.cs b/packages/generator/tests/circular-ref/snapshots/cs/Utils.cs index 88d4be58..2cf57544 100644 --- a/packages/generator/tests/circular-ref/snapshots/cs/Utils.cs +++ b/packages/generator/tests/circular-ref/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/packages/generator/tests/circular-ref/snapshots/swift/Utils.swift b/packages/generator/tests/circular-ref/snapshots/swift/Utils.swift index d65d2a00..8df90b02 100644 --- a/packages/generator/tests/circular-ref/snapshots/swift/Utils.swift +++ b/packages/generator/tests/circular-ref/snapshots/swift/Utils.swift @@ -5,13 +5,11 @@ import FoundationNetworking let pachcaDecoder: JSONDecoder = { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 return decoder }() let pachcaEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 return encoder }() diff --git a/packages/generator/tests/crud/snapshots/cs/Client.cs b/packages/generator/tests/crud/snapshots/cs/Client.cs index 583e8c6c..76042131 100644 --- a/packages/generator/tests/crud/snapshots/cs/Client.cs +++ b/packages/generator/tests/crud/snapshots/cs/Client.cs @@ -34,7 +34,7 @@ public async System.Threading.Tasks.Task ListChatsAsync( if (availability != null) queryParts.Add($"availability={Uri.EscapeDataString(PachcaUtils.EnumToApiString(availability.Value))}"); if (limit != null) - queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString())}"); + queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString()!)}"); if (cursor != null) queryParts.Add($"cursor={Uri.EscapeDataString(cursor)}"); if (sortField != null) @@ -69,6 +69,7 @@ public async System.Threading.Tasks.Task> ListChatsAllAsync( { var response = await ListChatsAsync(availability: availability, limit: limit, cursor: cursor, sortField: sortField, sortOrder: sortOrder, cancellationToken: cancellationToken).ConfigureAwait(false); items.AddRange(response.Data); + if (response.Data.Count == 0) break; cursor = response.Meta?.Paginate?.NextPage; } while (cursor != null); return items; diff --git a/packages/generator/tests/crud/snapshots/cs/Utils.cs b/packages/generator/tests/crud/snapshots/cs/Utils.cs index 88d4be58..2cf57544 100644 --- a/packages/generator/tests/crud/snapshots/cs/Utils.cs +++ b/packages/generator/tests/crud/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/packages/generator/tests/crud/snapshots/go/client.go b/packages/generator/tests/crud/snapshots/go/client.go index a6effee1..96f742c1 100644 --- a/packages/generator/tests/crud/snapshots/go/client.go +++ b/packages/generator/tests/crud/snapshots/go/client.go @@ -105,11 +105,15 @@ func (s *ChatsService) ListChats(ctx context.Context, params *ListChatsParams) ( return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -127,10 +131,11 @@ func (s *ChatsService) ListChatsAll(ctx context.Context, params *ListChatsParams return nil, err } items = append(items, result.Data...) - if result.Meta == nil || result.Meta.Paginate == nil || result.Meta.Paginate.NextPage == nil { + if len(result.Data) == 0 || result.Meta == nil { return items, nil } - cursor = result.Meta.Paginate.NextPage + nextPage := result.Meta.Paginate.NextPage + cursor = &nextPage } } @@ -155,11 +160,15 @@ func (s *ChatsService) GetChat(ctx context.Context, id int32) (*Chat, error) { return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -190,11 +199,15 @@ func (s *ChatsService) CreateChat(ctx context.Context, request ChatCreateRequest return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -225,11 +238,15 @@ func (s *ChatsService) UpdateChat(ctx context.Context, id int32, request ChatUpd return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -249,11 +266,15 @@ func (s *ChatsService) ArchiveChat(ctx context.Context, id int32) error { return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -273,11 +294,15 @@ func (s *ChatsService) DeleteChat(ctx context.Context, id int32) error { return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } diff --git a/packages/generator/tests/crud/snapshots/kt/Client.kt b/packages/generator/tests/crud/snapshots/kt/Client.kt index 6736208c..6633ee76 100644 --- a/packages/generator/tests/crud/snapshots/kt/Client.kt +++ b/packages/generator/tests/crud/snapshots/kt/Client.kt @@ -55,6 +55,7 @@ class ChatsService internal constructor( sortOrder = sortOrder, ) items.addAll(response.data) + if (response.data.isEmpty()) break cursor = response.meta?.paginate?.nextPage } while (cursor != null) return items diff --git a/packages/generator/tests/crud/snapshots/kt/Models.kt b/packages/generator/tests/crud/snapshots/kt/Models.kt index 2baee073..4ae43e08 100644 --- a/packages/generator/tests/crud/snapshots/kt/Models.kt +++ b/packages/generator/tests/crud/snapshots/kt/Models.kt @@ -1,7 +1,20 @@ package com.pachca.sdk +import java.time.OffsetDateTime +import java.time.format.DateTimeFormatter +import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +object OffsetDateTimeSerializer : KSerializer { + override val descriptor = PrimitiveSerialDescriptor("OffsetDateTime", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: OffsetDateTime) = encoder.encodeString(value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)) + override fun deserialize(decoder: Decoder): OffsetDateTime = OffsetDateTime.parse(decoder.decodeString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME) +} @Serializable enum class SortOrder(val value: String) { @@ -23,7 +36,7 @@ data class Chat( val name: String, @SerialName("is_channel") val isChannel: Boolean, @SerialName("is_public") val isPublic: Boolean, - @SerialName("created_at") val createdAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("created_at") val createdAt: OffsetDateTime, @SerialName("member_ids") val memberIds: List? = null, ) diff --git a/packages/generator/tests/crud/snapshots/kt/examples.json b/packages/generator/tests/crud/snapshots/kt/examples.json index 6a3515fd..ca41f1c8 100644 --- a/packages/generator/tests/crud/snapshots/kt/examples.json +++ b/packages/generator/tests/crud/snapshots/kt/examples.json @@ -15,11 +15,11 @@ }, "ChatOperations_getChat": { "usage": "val response = client.chats.getChat(id = 123)", - "output": "Chat(id: Int, name: String, isChannel: Boolean, isPublic: Boolean, createdAt: String, memberIds: List?)" + "output": "Chat(id: Int, name: String, isChannel: Boolean, isPublic: Boolean, createdAt: OffsetDateTime, memberIds: List?)" }, "ChatOperations_createChat": { "usage": "val request = ChatCreateRequest(\n chat = ChatCreateRequestChat(\n name = \"example\",\n channel = true,\n public = true,\n memberIds = listOf(123)\n )\n)\nval response = client.chats.createChat(request = request)", - "output": "Chat(id: Int, name: String, isChannel: Boolean, isPublic: Boolean, createdAt: String, memberIds: List?)", + "output": "Chat(id: Int, name: String, isChannel: Boolean, isPublic: Boolean, createdAt: OffsetDateTime, memberIds: List?)", "imports": [ "ChatCreateRequest", "ChatCreateRequestChat" @@ -27,7 +27,7 @@ }, "ChatOperations_updateChat": { "usage": "val request = ChatUpdateRequest(chat = ChatUpdateRequestChat(name = \"example\", public = true))\nval response = client.chats.updateChat(id = 123, request = request)", - "output": "Chat(id: Int, name: String, isChannel: Boolean, isPublic: Boolean, createdAt: String, memberIds: List?)", + "output": "Chat(id: Int, name: String, isChannel: Boolean, isPublic: Boolean, createdAt: OffsetDateTime, memberIds: List?)", "imports": [ "ChatUpdateRequest", "ChatUpdateRequestChat" diff --git a/packages/generator/tests/crud/snapshots/py/client.py b/packages/generator/tests/crud/snapshots/py/client.py index e73999ce..32b12fd9 100644 --- a/packages/generator/tests/crud/snapshots/py/client.py +++ b/packages/generator/tests/crud/snapshots/py/client.py @@ -59,7 +59,9 @@ async def list_chats_all( params.cursor = cursor response = await self.list_chats(params=params) items.extend(response.data) - cursor = response.meta.paginate.next_page if response.meta and response.meta.paginate else None + if not response.data: + break + cursor = response.meta.paginate.next_page if response.meta else None if not cursor: break return items diff --git a/packages/generator/tests/crud/snapshots/py/examples.json b/packages/generator/tests/crud/snapshots/py/examples.json index fde50ecd..ebeea4a5 100644 --- a/packages/generator/tests/crud/snapshots/py/examples.json +++ b/packages/generator/tests/crud/snapshots/py/examples.json @@ -16,11 +16,11 @@ }, "ChatOperations_getChat": { "usage": "response = await client.chats.get_chat(id=123)", - "output": "Chat(id: int, name: str, is_channel: bool, is_public: bool, created_at: str, member_ids: list[int] | None)" + "output": "Chat(id: int, name: str, is_channel: bool, is_public: bool, created_at: datetime, member_ids: list[int] | None)" }, "ChatOperations_createChat": { "usage": "request = ChatCreateRequest(\n chat=ChatCreateRequestChat(\n name=\"example\",\n channel=True,\n public=True,\n member_ids=[123]\n )\n)\nresponse = await client.chats.create_chat(request=request)", - "output": "Chat(id: int, name: str, is_channel: bool, is_public: bool, created_at: str, member_ids: list[int] | None)", + "output": "Chat(id: int, name: str, is_channel: bool, is_public: bool, created_at: datetime, member_ids: list[int] | None)", "imports": [ "ChatCreateRequest", "ChatCreateRequestChat" @@ -28,7 +28,7 @@ }, "ChatOperations_updateChat": { "usage": "request = ChatUpdateRequest(chat=ChatUpdateRequestChat(name=\"example\", public=True))\nresponse = await client.chats.update_chat(id=123, request=request)", - "output": "Chat(id: int, name: str, is_channel: bool, is_public: bool, created_at: str, member_ids: list[int] | None)", + "output": "Chat(id: int, name: str, is_channel: bool, is_public: bool, created_at: datetime, member_ids: list[int] | None)", "imports": [ "ChatUpdateRequest", "ChatUpdateRequestChat" diff --git a/packages/generator/tests/crud/snapshots/py/models.py b/packages/generator/tests/crud/snapshots/py/models.py index 09a37563..57668f70 100644 --- a/packages/generator/tests/crud/snapshots/py/models.py +++ b/packages/generator/tests/crud/snapshots/py/models.py @@ -1,5 +1,6 @@ from __future__ import annotations +from datetime import datetime from dataclasses import dataclass from enum import StrEnum @@ -19,7 +20,7 @@ class Chat: name: str is_channel: bool is_public: bool - created_at: str + created_at: datetime member_ids: list[int] | None = None diff --git a/packages/generator/tests/crud/snapshots/py/utils.py b/packages/generator/tests/crud/snapshots/py/utils.py index d1494873..9288f902 100644 --- a/packages/generator/tests/crud/snapshots/py/utils.py +++ b/packages/generator/tests/crud/snapshots/py/utils.py @@ -3,6 +3,7 @@ import dataclasses import keyword from dataclasses import asdict, fields +from datetime import datetime from typing import Type, TypeVar, get_args, get_origin, get_type_hints import httpx @@ -58,6 +59,16 @@ def deserialize(cls: Type[T], data: dict) -> T: item_tp = _resolve_list_item_type(hints[f.name]) if item_tp is not None and _is_dataclass_type(item_tp): v = [deserialize(item_tp, i) if isinstance(i, dict) else i for i in v] + elif isinstance(v, str): + hint = hints.get(f.name) + raw_hint = hint + if get_origin(hint) is not None: + for a in get_args(hint): + if a is not type(None): + raw_hint = a + break + if raw_hint is datetime: + v = datetime.fromisoformat(v) kwargs[k] = v return cls(**kwargs) @@ -70,6 +81,8 @@ def _strip_nones(val: object) -> object: } if isinstance(val, list): return [_strip_nones(v) for v in val] + if isinstance(val, datetime): + return val.isoformat() return val diff --git a/packages/generator/tests/crud/snapshots/swift/Client.swift b/packages/generator/tests/crud/snapshots/swift/Client.swift index a08a1a28..5393783b 100644 --- a/packages/generator/tests/crud/snapshots/swift/Client.swift +++ b/packages/generator/tests/crud/snapshots/swift/Client.swift @@ -43,7 +43,8 @@ public struct ChatsService { repeat { let response = try await listChats(availability: availability, limit: limit, cursor: cursor, sortField: sortField, sortOrder: sortOrder) items.append(contentsOf: response.data) - cursor = response.meta?.paginate?.nextPage + if response.data.isEmpty { break } + cursor = response.meta?.paginate.nextPage } while cursor != nil return items } diff --git a/packages/generator/tests/crud/snapshots/swift/Models.swift b/packages/generator/tests/crud/snapshots/swift/Models.swift index 5ee09e75..f8bf6e82 100644 --- a/packages/generator/tests/crud/snapshots/swift/Models.swift +++ b/packages/generator/tests/crud/snapshots/swift/Models.swift @@ -20,10 +20,10 @@ public struct Chat: Codable { public let name: String public let isChannel: Bool public let isPublic: Bool - public let createdAt: Date + public let createdAt: String public let memberIds: [Int]? - public init(id: Int, name: String, isChannel: Bool, isPublic: Bool, createdAt: Date, memberIds: [Int]? = nil) { + public init(id: Int, name: String, isChannel: Bool, isPublic: Bool, createdAt: String, memberIds: [Int]? = nil) { self.id = id self.name = name self.isChannel = isChannel diff --git a/packages/generator/tests/crud/snapshots/swift/Utils.swift b/packages/generator/tests/crud/snapshots/swift/Utils.swift index d65d2a00..8df90b02 100644 --- a/packages/generator/tests/crud/snapshots/swift/Utils.swift +++ b/packages/generator/tests/crud/snapshots/swift/Utils.swift @@ -5,13 +5,11 @@ import FoundationNetworking let pachcaDecoder: JSONDecoder = { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 return decoder }() let pachcaEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 return encoder }() diff --git a/packages/generator/tests/crud/snapshots/ts/client.ts b/packages/generator/tests/crud/snapshots/ts/client.ts index c00d7c3f..a8c48e5d 100644 --- a/packages/generator/tests/crud/snapshots/ts/client.ts +++ b/packages/generator/tests/crud/snapshots/ts/client.ts @@ -43,7 +43,8 @@ class ChatsService { do { const response = await this.listChats({ ...params, cursor } as ListChatsParams); items.push(...response.data); - cursor = response.meta?.paginate?.nextPage; + if (response.data.length === 0) break; + cursor = response.meta?.paginate.nextPage; } while (cursor); return items; } diff --git a/packages/generator/tests/date-format/fixture.yaml b/packages/generator/tests/date-format/fixture.yaml new file mode 100644 index 00000000..7376a9f6 --- /dev/null +++ b/packages/generator/tests/date-format/fixture.yaml @@ -0,0 +1,161 @@ +openapi: 3.0.0 +info: + title: Test API — Date Format + version: 1.0.0 +servers: + - url: https://api.pachca.com/api/shared/v1 +tags: + - name: Export +paths: + /exports: + post: + operationId: ExportOperations_createExport + description: Создание экспорта с date-only и date-time полями + parameters: [] + responses: + '201': + description: The request has succeeded and a new resource has been created as a result. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Export' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + tags: + - Export + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExportRequest' + /events: + get: + operationId: EventOperations_listEvents + description: Получение событий с date query параметрами + parameters: + - name: date_from + in: query + required: true + description: Дата начала (date-only) + schema: + type: string + format: date + - name: date_to + in: query + required: false + description: Дата окончания (date-only) + schema: + type: string + format: date + - name: created_after + in: query + required: false + description: Создано после (date-time) + schema: + type: string + format: date-time + - name: limit + in: query + required: false + description: Количество записей + schema: + type: integer + format: int32 + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: '#/components/schemas/Event' + tags: + - Export +components: + schemas: + ExportRequest: + type: object + required: + - start_at + - end_at + - webhook_url + properties: + start_at: + type: string + format: date + description: Дата начала (YYYY-MM-DD) + example: '2025-03-20' + end_at: + type: string + format: date + description: Дата окончания (YYYY-MM-DD) + example: '2025-03-20' + webhook_url: + type: string + description: URL для вебхука + + Export: + type: object + required: + - id + - start_at + - end_at + - status + - created_at + properties: + id: + type: integer + format: int32 + start_at: + type: string + format: date + description: Дата начала + end_at: + type: string + format: date + description: Дата окончания + status: + type: string + description: Статус экспорта + created_at: + type: string + format: date-time + description: Дата создания + + Event: + type: object + required: + - id + - type + - occurred_at + properties: + id: + type: integer + format: int32 + type: + type: string + occurred_at: + type: string + format: date-time + + OAuthError: + type: object + properties: + error: + type: string diff --git a/packages/generator/tests/date-format/snapshots/cs/Client.cs b/packages/generator/tests/date-format/snapshots/cs/Client.cs new file mode 100644 index 00000000..c0da1bdd --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/cs/Client.cs @@ -0,0 +1,92 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Threading; + +namespace Pachca.Sdk; + +public sealed class ExportService +{ + private readonly string _baseUrl; + private readonly HttpClient _client; + + internal ExportService(string baseUrl, HttpClient client) + { + _baseUrl = baseUrl; + _client = client; + } + + public async System.Threading.Tasks.Task ListEventsAsync( + string dateFrom, + string? dateTo = null, + DateTimeOffset? createdAfter = null, + int? limit = null, + CancellationToken cancellationToken = default) + { + var queryParts = new List(); + queryParts.Add($"date_from={Uri.EscapeDataString(dateFrom)}"); + if (dateTo != null) + queryParts.Add($"date_to={Uri.EscapeDataString(dateTo)}"); + if (createdAfter != null) + queryParts.Add($"created_after={Uri.EscapeDataString(createdAfter.Value.ToString("o"))}"); + if (limit != null) + queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString()!)}"); + var url = $"{_baseUrl}/events" + (queryParts.Count > 0 ? "?" + string.Join("&", queryParts) : ""); + using var request = new HttpRequestMessage(HttpMethod.Get, url); + using var response = await PachcaUtils.SendWithRetryAsync(_client, request, cancellationToken).ConfigureAwait(false); + var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + switch ((int)response.StatusCode) + { + case 200: + return PachcaUtils.Deserialize(json); + default: + throw new InvalidOperationException($"Unexpected status code: {(int)response.StatusCode}"); + } + } + + public async System.Threading.Tasks.Task CreateExportAsync(ExportRequest request, CancellationToken cancellationToken = default) + { + var url = $"{_baseUrl}/exports"; + using var httpRequest = new HttpRequestMessage(HttpMethod.Post, url); + httpRequest.Content = new StringContent(PachcaUtils.Serialize(request), Encoding.UTF8, "application/json"); + using var response = await PachcaUtils.SendWithRetryAsync(_client, httpRequest, cancellationToken).ConfigureAwait(false); + var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + switch ((int)response.StatusCode) + { + case 201: + return PachcaUtils.Deserialize(json).Data; + case 401: + throw PachcaUtils.Deserialize(json); + default: + throw new InvalidOperationException($"Unexpected status code: {(int)response.StatusCode}"); + } + } +} + +public sealed class PachcaClient : IDisposable +{ + private readonly HttpClient _client; + + public ExportService Export { get; } + + public PachcaClient(string token, string baseUrl = "https://api.pachca.com/api/shared/v1") + { + _client = new HttpClient(); + _client.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Bearer", token); + + Export = new ExportService(baseUrl, _client); + } + + public void Dispose() + { + _client.Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/packages/generator/tests/date-format/snapshots/cs/Models.cs b/packages/generator/tests/date-format/snapshots/cs/Models.cs new file mode 100644 index 00000000..8f1bfaf6 --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/cs/Models.cs @@ -0,0 +1,60 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Pachca.Sdk; + +public class ExportRequest +{ + [JsonPropertyName("start_at")] + public string StartAt { get; set; } = default!; + [JsonPropertyName("end_at")] + public string EndAt { get; set; } = default!; + [JsonPropertyName("webhook_url")] + public string WebhookUrl { get; set; } = default!; +} + +public class Export +{ + [JsonPropertyName("id")] + public int Id { get; set; } = default!; + [JsonPropertyName("start_at")] + public string StartAt { get; set; } = default!; + [JsonPropertyName("end_at")] + public string EndAt { get; set; } = default!; + [JsonPropertyName("status")] + public string Status { get; set; } = default!; + [JsonPropertyName("created_at")] + public DateTimeOffset CreatedAt { get; set; } = default!; +} + +public class Event +{ + [JsonPropertyName("id")] + public int Id { get; set; } = default!; + [JsonPropertyName("type")] + public string Type { get; set; } = default!; + [JsonPropertyName("occurred_at")] + public DateTimeOffset OccurredAt { get; set; } = default!; +} + +public class OAuthError : Exception +{ + [JsonPropertyName("error")] + public string? Error { get; set; } +} + +public class ListEventsResponse +{ + [JsonPropertyName("data")] + public List Data { get; set; } = new(); +} + +public class ExportDataWrapper +{ + [JsonPropertyName("data")] + public Export Data { get; set; } = default!; +} diff --git a/packages/generator/tests/date-format/snapshots/cs/Utils.cs b/packages/generator/tests/date-format/snapshots/cs/Utils.cs new file mode 100644 index 00000000..2cf57544 --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/cs/Utils.cs @@ -0,0 +1,103 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace Pachca.Sdk; + +internal static class PachcaUtils +{ + private const int MaxRetries = 3; + private static readonly HashSet Retryable5xx = new() { 500, 502, 503, 504 }; + private static readonly Random JitterRandom = new(); + + internal static readonly JsonSerializerOptions JsonOptions = new() + { + DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true, + }; + + private static TimeSpan AddJitter(TimeSpan delay) + { + var factor = 0.5 + JitterRandom.NextDouble() * 0.5; + return TimeSpan.FromMilliseconds(delay.TotalMilliseconds * factor); + } + + internal static async Task SendWithRetryAsync( + HttpClient client, + HttpRequestMessage request, + CancellationToken cancellationToken = default) + { + for (var attempt = 0; attempt <= MaxRetries; attempt++) + { + HttpRequestMessage req; + if (attempt == 0) + { + req = request; + } + else + { + req = await CloneRequestAsync(request).ConfigureAwait(false); + } + + var response = await client.SendAsync(req, cancellationToken).ConfigureAwait(false); + + if ((int)response.StatusCode == 429 && attempt < MaxRetries) + { + var delay = response.Headers.RetryAfter?.Delta + ?? TimeSpan.FromSeconds(Math.Pow(2, attempt)); + await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + response.Dispose(); + continue; + } + + if (Retryable5xx.Contains((int)response.StatusCode) && attempt < MaxRetries) + { + var delay = AddJitter(TimeSpan.FromSeconds(10 * Math.Pow(2, attempt))); + await System.Threading.Tasks.Task.Delay(delay, cancellationToken).ConfigureAwait(false); + response.Dispose(); + continue; + } + + return response; + } + + return await client.SendAsync( + await CloneRequestAsync(request).ConfigureAwait(false), + cancellationToken).ConfigureAwait(false); + } + + private static async Task CloneRequestAsync(HttpRequestMessage request) + { + var clone = new HttpRequestMessage(request.Method, request.RequestUri); + foreach (var header in request.Headers) + clone.Headers.TryAddWithoutValidation(header.Key, header.Value); + + if (request.Content != null) + { + var content = await request.Content.ReadAsByteArrayAsync().ConfigureAwait(false); + clone.Content = new ByteArrayContent(content); + if (request.Content.Headers.ContentType != null) + clone.Content.Headers.ContentType = request.Content.Headers.ContentType; + } + + return clone; + } + + internal static T Deserialize(string json) => + JsonSerializer.Deserialize(json, JsonOptions) + ?? throw new InvalidOperationException("Deserialization returned null"); + + internal static string Serialize(T value) => + JsonSerializer.Serialize(value, JsonOptions); + + internal static string EnumToApiString(T value) where T : struct, Enum => + JsonSerializer.Serialize(value, JsonOptions).Trim('"'); +} diff --git a/packages/generator/tests/date-format/snapshots/cs/examples.json b/packages/generator/tests/date-format/snapshots/cs/examples.json new file mode 100644 index 00000000..6ba6def4 --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/cs/examples.json @@ -0,0 +1,19 @@ +{ + "Client_Init": { + "usage": "using var client = new PachcaClient(\"YOUR_TOKEN\");", + "imports": [ + "PachcaClient" + ] + }, + "EventOperations_listEvents": { + "usage": "var response = await client.Export.ListEventsAsync(\"example\", \"example\", DateTimeOffset.UtcNow, 123);", + "output": "ListEventsResponse(Data: List)" + }, + "ExportOperations_createExport": { + "usage": "var request = new ExportRequest\n{\n StartAt = \"2025-03-20\",\n EndAt = \"2025-03-20\",\n WebhookUrl = \"example\"\n};\nvar response = await client.Export.CreateExportAsync(request);", + "output": "Export(Id: int, StartAt: string, EndAt: string, Status: string, CreatedAt: DateTimeOffset)", + "imports": [ + "ExportRequest" + ] + } +} diff --git a/packages/generator/tests/date-format/snapshots/go/client.go b/packages/generator/tests/date-format/snapshots/go/client.go new file mode 100644 index 00000000..89de56c8 --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/go/client.go @@ -0,0 +1,156 @@ +package pachca + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "math/rand" + "net/http" + "net/url" + "strconv" + "time" +) + +type authTransport struct { + token string + base http.RoundTripper +} + +func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req.Header.Set("Authorization", "Bearer "+t.token) + return t.base.RoundTrip(req) +} + +const maxRetries = 3 + +var retryable5xx = map[int]bool{500: true, 502: true, 503: true, 504: true} + +func jitter(d time.Duration) time.Duration { + return time.Duration(float64(d) * (0.5 + rand.Float64()*0.5)) +} + +func doWithRetry(client *http.Client, req *http.Request) (*http.Response, error) { + for attempt := 0; ; attempt++ { + if attempt > 0 && req.GetBody != nil { + req.Body, _ = req.GetBody() + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode == http.StatusTooManyRequests && attempt < maxRetries { + resp.Body.Close() + delay := time.Duration(1< 0 { url = baseURL[0] } + client := &http.Client{ + Transport: &authTransport{token: token, base: http.DefaultTransport}, + } + return &PachcaClient{ + Export: &ExportService{baseURL: url, client: client}, + } +} diff --git a/packages/generator/tests/date-format/snapshots/go/examples.json b/packages/generator/tests/date-format/snapshots/go/examples.json new file mode 100644 index 00000000..a73f6bf6 --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/go/examples.json @@ -0,0 +1,22 @@ +{ + "Client_Init": { + "usage": "client := pachca.NewPachcaClient(\"YOUR_TOKEN\")", + "imports": [ + "PachcaClient" + ] + }, + "EventOperations_listEvents": { + "usage": "params := ListEventsParams{\n\tDateFrom: \"2024-01-01\",\n\tDateTo: Ptr(\"2024-01-01\"),\n\tCreatedAfter: Ptr(\"2024-01-01T00:00:00Z\"),\n\tLimit: Ptr(int32(123)),\n}\nresponse, err := client.Export.ListEvents(ctx, params)", + "output": "ListEventsResponse{Data: []Event}", + "imports": [ + "ListEventsParams" + ] + }, + "ExportOperations_createExport": { + "usage": "request := ExportRequest{\n\tStartAt: \"2025-03-20\",\n\tEndAt: \"2025-03-20\",\n\tWebhookURL: \"example\",\n}\nresponse, err := client.Export.CreateExport(ctx, request)", + "output": "Export{ID: int32, StartAt: string, EndAt: string, Status: string, CreatedAt: string}", + "imports": [ + "ExportRequest" + ] + } +} diff --git a/packages/generator/tests/date-format/snapshots/go/types.go b/packages/generator/tests/date-format/snapshots/go/types.go new file mode 100644 index 00000000..eda829e9 --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/go/types.go @@ -0,0 +1,47 @@ +package pachca + +import ( + "time" +) + +type ExportRequest struct { + StartAt string `json:"start_at"` + EndAt string `json:"end_at"` + WebhookURL string `json:"webhook_url"` +} + +type Export struct { + ID int32 `json:"id"` + StartAt string `json:"start_at"` + EndAt string `json:"end_at"` + Status string `json:"status"` + CreatedAt time.Time `json:"created_at"` +} + +type Event struct { + ID int32 `json:"id"` + Type string `json:"type"` + OccurredAt time.Time `json:"occurred_at"` +} + +type OAuthError struct { + Err *string `json:"error,omitempty"` +} + +func (e *OAuthError) Error() string { + if e.Err != nil { + return *e.Err + } + return "oauth error" +} + +type ListEventsParams struct { + DateFrom string + DateTo *string + CreatedAfter *time.Time + Limit *int32 +} + +type ListEventsResponse struct { + Data []Event `json:"data"` +} diff --git a/packages/generator/tests/date-format/snapshots/go/utils.go b/packages/generator/tests/date-format/snapshots/go/utils.go new file mode 100644 index 00000000..d0527665 --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/go/utils.go @@ -0,0 +1,6 @@ +package pachca + +// Ptr returns a pointer to the given value. +func Ptr[T any](v T) *T { + return &v +} diff --git a/packages/generator/tests/date-format/snapshots/kt/Client.kt b/packages/generator/tests/date-format/snapshots/kt/Client.kt new file mode 100644 index 00000000..17bd2815 --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/kt/Client.kt @@ -0,0 +1,84 @@ +package com.pachca.sdk + +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.plugins.* +import io.ktor.client.plugins.auth.* +import io.ktor.client.plugins.auth.providers.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.* +import kotlinx.serialization.json.Json +import java.io.Closeable +import java.time.OffsetDateTime + +class ExportService internal constructor( + private val baseUrl: String, + private val client: HttpClient, +) { + suspend fun listEvents( + dateFrom: String, + dateTo: String? = null, + createdAfter: OffsetDateTime? = null, + limit: Int? = null, + ): ListEventsResponse { + val response = client.get("$baseUrl/events") { + parameter("date_from", dateFrom) + dateTo?.let { parameter("date_to", it) } + createdAfter?.let { parameter("created_after", it.toString()) } + limit?.let { parameter("limit", it) } + } + return when (response.status.value) { + 200 -> response.body() + else -> throw RuntimeException("Unexpected status code: ${response.status.value}") + } + } + + suspend fun createExport(request: ExportRequest): Export { + val response = client.post("$baseUrl/exports") { + contentType(ContentType.Application.Json) + setBody(request) + } + return when (response.status.value) { + 201 -> response.body().data + 401 -> throw response.body() + else -> throw RuntimeException("Unexpected status code: ${response.status.value}") + } + } +} + +class PachcaClient(token: String, baseUrl: String = "https://api.pachca.com/api/shared/v1") : Closeable { + private val client = HttpClient { + expectSuccess = false + install(ContentNegotiation) { + json(Json { explicitNulls = false }) + } + install(HttpRequestRetry) { + maxRetries = 3 + retryIf { _, response -> + response.status.value == 429 || response.status.value in setOf(500, 502, 503, 504) + } + delayMillis { retry -> + val retryAfter = response?.headers?.get("Retry-After")?.toLongOrNull() + if (retryAfter != null && response?.status?.value == 429) { + retryAfter * 1000L + } else { + val base = 10_000L * (1L shl retry) + val jitter = 0.5 + kotlin.random.Random.nextDouble() * 0.5 + (base * jitter).toLong() + } + } + } + defaultRequest { + bearerAuth(token) + } + } + + val export = ExportService(baseUrl, client) + + override fun close() { + client.close() + } +} diff --git a/packages/generator/tests/date-format/snapshots/kt/Models.kt b/packages/generator/tests/date-format/snapshots/kt/Models.kt new file mode 100644 index 00000000..66296be9 --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/kt/Models.kt @@ -0,0 +1,53 @@ +package com.pachca.sdk + +import java.time.OffsetDateTime +import java.time.format.DateTimeFormatter +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +object OffsetDateTimeSerializer : KSerializer { + override val descriptor = PrimitiveSerialDescriptor("OffsetDateTime", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: OffsetDateTime) = encoder.encodeString(value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)) + override fun deserialize(decoder: Decoder): OffsetDateTime = OffsetDateTime.parse(decoder.decodeString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME) +} + +@Serializable +data class ExportRequest( + @SerialName("start_at") val startAt: String, + @SerialName("end_at") val endAt: String, + @SerialName("webhook_url") val webhookUrl: String, +) + +@Serializable +data class Export( + val id: Int, + @SerialName("start_at") val startAt: String, + @SerialName("end_at") val endAt: String, + val status: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("created_at") val createdAt: OffsetDateTime, +) + +@Serializable +data class Event( + val id: Int, + val type: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("occurred_at") val occurredAt: OffsetDateTime, +) + +@Serializable +data class OAuthError( + val error: String? = null, +) : Exception() + +@Serializable +data class ListEventsResponse( + val data: List, +) + +@Serializable +data class ExportDataWrapper(val data: Export) diff --git a/packages/generator/tests/date-format/snapshots/kt/examples.json b/packages/generator/tests/date-format/snapshots/kt/examples.json new file mode 100644 index 00000000..e83f1c57 --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/kt/examples.json @@ -0,0 +1,19 @@ +{ + "Client_Init": { + "usage": "val client = PachcaClient(\"YOUR_TOKEN\")", + "imports": [ + "PachcaClient" + ] + }, + "EventOperations_listEvents": { + "usage": "val createdAfter = OffsetDateTime.parse(\"2024-01-01T00:00:00Z\")\nval response = client.export.listEvents(dateFrom = \"2024-01-01\", dateTo = \"2024-01-01\", createdAfter = createdAfter, limit = 123)", + "output": "ListEventsResponse(data: List)" + }, + "ExportOperations_createExport": { + "usage": "val request = ExportRequest(\n startAt = \"2025-03-20\",\n endAt = \"2025-03-20\",\n webhookUrl = \"example\"\n)\nval response = client.export.createExport(request = request)", + "output": "Export(id: Int, startAt: String, endAt: String, status: String, createdAt: OffsetDateTime)", + "imports": [ + "ExportRequest" + ] + } +} diff --git a/packages/generator/tests/date-format/snapshots/py/__init__.py b/packages/generator/tests/date-format/snapshots/py/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/packages/generator/tests/date-format/snapshots/py/client.py b/packages/generator/tests/date-format/snapshots/py/client.py new file mode 100644 index 00000000..e4cafc23 --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/py/client.py @@ -0,0 +1,73 @@ +from __future__ import annotations + +import httpx + +from .models import ( + ListEventsParams, + ListEventsResponse, + OAuthError, + ExportRequest, + Export, +) +from .utils import deserialize, serialize, RetryTransport + +class ExportService: + def __init__(self, client: httpx.AsyncClient) -> None: + self._client = client + + async def list_events( + self, + params: ListEventsParams, + ) -> ListEventsResponse: + query: list[tuple[str, str]] = [] + query.append(("date_from", params.date_from)) + if params is not None and params.date_to is not None: + query.append(("date_to", params.date_to)) + if params is not None and params.created_after is not None: + query.append(("created_after", params.created_after.isoformat())) + if params is not None and params.limit is not None: + query.append(("limit", str(params.limit))) + response = await self._client.get( + "/events", + params=query, + ) + body = response.json() + match response.status_code: + case 200: + return deserialize(ListEventsResponse, body) + case _: + raise RuntimeError( + f"Unexpected status code: {response.status_code}" + ) + + async def create_export( + self, + request: ExportRequest, + ) -> Export: + response = await self._client.post( + "/exports", + json=serialize(request), + ) + body = response.json() + match response.status_code: + case 201: + return deserialize(Export, body["data"]) + case 401: + raise deserialize(OAuthError, body) + case _: + raise RuntimeError( + f"Unexpected status code: {response.status_code}" + ) + + +class PachcaClient: + def __init__(self, token: str, base_url: str = "https://api.pachca.com/api/shared/v1") -> None: + self._client = httpx.AsyncClient( + base_url=base_url, + headers={"Authorization": f"Bearer {token}"}, + transport=RetryTransport(httpx.AsyncHTTPTransport()), + ) + self.export = ExportService(self._client) + + async def close(self) -> None: + await self._client.aclose() diff --git a/packages/generator/tests/date-format/snapshots/py/examples.json b/packages/generator/tests/date-format/snapshots/py/examples.json new file mode 100644 index 00000000..d5842f2f --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/py/examples.json @@ -0,0 +1,22 @@ +{ + "Client_Init": { + "usage": "client = PachcaClient(\"YOUR_TOKEN\")", + "imports": [ + "PachcaClient" + ] + }, + "EventOperations_listEvents": { + "usage": "params = ListEventsParams(\n date_from=\"2024-01-01\",\n date_to=\"2024-01-01\",\n created_after=datetime.fromisoformat(\"2024-01-01T00:00:00Z\"),\n limit=123\n)\nresponse = await client.export.list_events(params=params)", + "output": "ListEventsResponse(data: list[Event])", + "imports": [ + "ListEventsParams" + ] + }, + "ExportOperations_createExport": { + "usage": "request = ExportRequest(\n start_at=\"2025-03-20\",\n end_at=\"2025-03-20\",\n webhook_url=\"example\"\n)\nresponse = await client.export.create_export(request=request)", + "output": "Export(id: int, start_at: str, end_at: str, status: str, created_at: datetime)", + "imports": [ + "ExportRequest" + ] + } +} diff --git a/packages/generator/tests/date-format/snapshots/py/models.py b/packages/generator/tests/date-format/snapshots/py/models.py new file mode 100644 index 00000000..55b73f57 --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/py/models.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from datetime import datetime +from dataclasses import dataclass + +@dataclass +class ExportRequest: + start_at: str + end_at: str + webhook_url: str + + +@dataclass +class Export: + id: int + start_at: str + end_at: str + status: str + created_at: datetime + + +@dataclass +class Event: + id: int + type: str + occurred_at: datetime + + +@dataclass +class OAuthError(Exception): + error: str | None = None + + +@dataclass +class ListEventsParams: + date_from: str + date_to: str | None = None + created_after: datetime | None = None + limit: int | None = None + + +@dataclass +class ListEventsResponse: + data: list[Event] diff --git a/packages/generator/tests/date-format/snapshots/py/utils.py b/packages/generator/tests/date-format/snapshots/py/utils.py new file mode 100644 index 00000000..9288f902 --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/py/utils.py @@ -0,0 +1,124 @@ +from __future__ import annotations + +import dataclasses +import keyword +from dataclasses import asdict, fields +from datetime import datetime +from typing import Type, TypeVar, get_args, get_origin, get_type_hints + +import httpx + +T = TypeVar("T") + + +def _is_dataclass_type(tp: type) -> bool: + return isinstance(tp, type) and dataclasses.is_dataclass(tp) + + +def _resolve_type(tp: type) -> type | None: + """Extract a concrete dataclass type from Optional[X] or X | None.""" + origin = get_origin(tp) + if origin is list: + return None # lists are handled inline + args = get_args(tp) + for arg in args: + if _is_dataclass_type(arg): + return arg + if _is_dataclass_type(tp): + return tp + return None + + +def _resolve_list_item_type(tp: type) -> type | None: + """Extract the item type from list[X].""" + origin = get_origin(tp) + if origin is list: + args = get_args(tp) + if args: + return args[0] + return None + + +def deserialize(cls: Type[T], data: dict) -> T: + """Create a dataclass instance from a dict, recursively deserializing nested dataclasses.""" + field_map = {f.name: f for f in fields(cls)} + hints = get_type_hints(cls) + norm = {k.replace("-", "_").lower(): v for k, v in data.items()} + kwargs = {} + for k, v in norm.items(): + if k not in field_map: + k = f"{k}_" + if k not in field_map: + continue + f = field_map[k] + if isinstance(v, dict): + nested = _resolve_type(hints[f.name]) + if nested is not None: + v = deserialize(nested, v) + elif isinstance(v, list) and v: + item_tp = _resolve_list_item_type(hints[f.name]) + if item_tp is not None and _is_dataclass_type(item_tp): + v = [deserialize(item_tp, i) if isinstance(i, dict) else i for i in v] + elif isinstance(v, str): + hint = hints.get(f.name) + raw_hint = hint + if get_origin(hint) is not None: + for a in get_args(hint): + if a is not type(None): + raw_hint = a + break + if raw_hint is datetime: + v = datetime.fromisoformat(v) + kwargs[k] = v + return cls(**kwargs) + + +def _strip_nones(val: object) -> object: + if isinstance(val, dict): + return { + (k[:-1] if k.endswith("_") and keyword.iskeyword(k[:-1]) else k): _strip_nones(v) + for k, v in val.items() if v is not None + } + if isinstance(val, list): + return [_strip_nones(v) for v in val] + if isinstance(val, datetime): + return val.isoformat() + return val + + +def serialize(obj: object) -> dict: + """Convert a dataclass to a dict, recursively omitting None values.""" + return _strip_nones(asdict(obj)) + + +_MAX_RETRIES = 3 +_RETRYABLE_5XX = {500, 502, 503, 504} + + +def _jitter(delay: float) -> float: + import random + return delay * (0.5 + random.random() * 0.5) + + +class RetryTransport(httpx.AsyncBaseTransport): + """Wraps an httpx transport with retry on 429 Too Many Requests and 5xx errors.""" + + def __init__(self, transport: httpx.AsyncBaseTransport, max_retries: int = _MAX_RETRIES) -> None: + self._transport = transport + self._max_retries = max_retries + + async def handle_async_request(self, request: httpx.Request) -> httpx.Response: + import asyncio + for attempt in range(self._max_retries + 1): + response = await self._transport.handle_async_request(request) + if response.status_code == 429 and attempt < self._max_retries: + retry_after = response.headers.get("retry-after") + delay = int(retry_after) if retry_after and retry_after.isdigit() else 2 ** attempt + await asyncio.sleep(delay) + continue + if response.status_code in _RETRYABLE_5XX and attempt < self._max_retries: + delay = _jitter(10 * (2 ** attempt)) + await asyncio.sleep(delay) + continue + return response + return response # unreachable diff --git a/packages/generator/tests/date-format/snapshots/swift/Client.swift b/packages/generator/tests/date-format/snapshots/swift/Client.swift new file mode 100644 index 00000000..4e90d157 --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/swift/Client.swift @@ -0,0 +1,63 @@ +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif + +public struct ExportService { + let baseURL: String + let headers: [String: String] + let session: URLSession + + init(baseURL: String, headers: [String: String], session: URLSession = .shared) { + self.baseURL = baseURL + self.headers = headers + self.session = session + } + + public func listEvents(dateFrom: String, dateTo: String? = nil, createdAfter: String? = nil, limit: Int? = nil) async throws -> ListEventsResponse { + var components = URLComponents(string: "\(baseURL)/events")! + var queryItems: [URLQueryItem] = [] + queryItems.append(URLQueryItem(name: "date_from", value: String(dateFrom))) + if let dateTo { queryItems.append(URLQueryItem(name: "date_to", value: String(dateTo))) } + if let createdAfter { queryItems.append(URLQueryItem(name: "created_after", value: String(createdAfter))) } + if let limit { queryItems.append(URLQueryItem(name: "limit", value: String(limit))) } + if !queryItems.isEmpty { components.queryItems = queryItems } + var request = URLRequest(url: components.url!) + headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } + let (data, urlResponse) = try await dataWithRetry(session: session, for: request) + let statusCode = (urlResponse as! HTTPURLResponse).statusCode + switch statusCode { + case 200: + return try deserialize(ListEventsResponse.self, from: data) + default: + throw URLError(.badServerResponse) + } + } + + public func createExport(request body: ExportRequest) async throws -> Export { + var request = URLRequest(url: URL(string: "\(baseURL)/exports")!) + request.httpMethod = "POST" + headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = try serialize(body) + let (data, urlResponse) = try await dataWithRetry(session: session, for: request) + let statusCode = (urlResponse as! HTTPURLResponse).statusCode + switch statusCode { + case 201: + return try deserialize(ExportDataWrapper.self, from: data).data + case 401: + throw try deserialize(OAuthError.self, from: data) + default: + throw URLError(.badServerResponse) + } + } +} + +public struct PachcaClient { + public let export: ExportService + + public init(token: String, baseURL: String = "https://api.pachca.com/api/shared/v1") { + let headers = ["Authorization": "Bearer \(token)"] + self.export = ExportService(baseURL: baseURL, headers: headers) + } +} diff --git a/packages/generator/tests/date-format/snapshots/swift/Models.swift b/packages/generator/tests/date-format/snapshots/swift/Models.swift new file mode 100644 index 00000000..93051236 --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/swift/Models.swift @@ -0,0 +1,80 @@ +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif + +public struct ExportRequest: Codable { + public let startAt: String + public let endAt: String + public let webhookUrl: String + + public init(startAt: String, endAt: String, webhookUrl: String) { + self.startAt = startAt + self.endAt = endAt + self.webhookUrl = webhookUrl + } + + enum CodingKeys: String, CodingKey { + case startAt = "start_at" + case endAt = "end_at" + case webhookUrl = "webhook_url" + } +} + +public struct Export: Codable { + public let id: Int + public let startAt: String + public let endAt: String + public let status: String + public let createdAt: String + + public init(id: Int, startAt: String, endAt: String, status: String, createdAt: String) { + self.id = id + self.startAt = startAt + self.endAt = endAt + self.status = status + self.createdAt = createdAt + } + + enum CodingKeys: String, CodingKey { + case id + case startAt = "start_at" + case endAt = "end_at" + case status + case createdAt = "created_at" + } +} + +public struct Event: Codable { + public let id: Int + public let type: String + public let occurredAt: String + + public init(id: Int, type: String, occurredAt: String) { + self.id = id + self.type = type + self.occurredAt = occurredAt + } + + enum CodingKeys: String, CodingKey { + case id + case type + case occurredAt = "occurred_at" + } +} + +public struct OAuthError: Codable, Error { + public let error: String? + + public init(error: String? = nil) { + self.error = error + } +} + +public struct ListEventsResponse: Codable { + public let data: [Event] +} + +struct ExportDataWrapper: Codable { + let data: Export +} diff --git a/packages/generator/tests/date-format/snapshots/swift/Utils.swift b/packages/generator/tests/date-format/snapshots/swift/Utils.swift new file mode 100644 index 00000000..8df90b02 --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/swift/Utils.swift @@ -0,0 +1,67 @@ +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif + +let pachcaDecoder: JSONDecoder = { + let decoder = JSONDecoder() + return decoder +}() + +let pachcaEncoder: JSONEncoder = { + let encoder = JSONEncoder() + return encoder +}() + +func serialize(_ value: T) throws -> Data { + let data = try pachcaEncoder.encode(value) + let json = try JSONSerialization.jsonObject(with: data) + return try JSONSerialization.data(withJSONObject: stripNulls(json)) +} + +func deserialize(_ type: T.Type, from data: Data) throws -> T { + return try pachcaDecoder.decode(type, from: data) +} + +private let maxRetries = 3 +private let retryable5xx: Set = [500, 502, 503, 504] + +private func jitter(_ delay: UInt64) -> UInt64 { + return UInt64(Double(delay) * (0.5 + Double.random(in: 0..<0.5))) +} + +func dataWithRetry(session: URLSession, for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil) async throws -> (Data, URLResponse) { + for attempt in 0...maxRetries { + let (data, response) = try await session.data(for: request, delegate: delegate) + if let http = response as? HTTPURLResponse, http.statusCode == 429, attempt < maxRetries { + let delay: UInt64 + if let ra = http.value(forHTTPHeaderField: "Retry-After"), let secs = UInt64(ra) { + delay = secs * 1_000_000_000 + } else { + delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000 + } + try await _Concurrency.Task.sleep(nanoseconds: delay) + continue + } + if let http = response as? HTTPURLResponse, retryable5xx.contains(http.statusCode), attempt < maxRetries { + let delay = jitter(10 * UInt64(pow(2.0, Double(attempt))) * 1_000_000_000) + try await _Concurrency.Task.sleep(nanoseconds: delay) + continue + } + return (data, response) + } + return try await session.data(for: request, delegate: delegate) // unreachable +} + +private func stripNulls(_ value: Any) -> Any { + if let dict = value as? [String: Any] { + return dict.compactMapValues { v -> Any? in + if v is NSNull { return nil } + return stripNulls(v) + } + } + if let arr = value as? [Any] { + return arr.map(stripNulls) + } + return value +} diff --git a/packages/generator/tests/date-format/snapshots/swift/examples.json b/packages/generator/tests/date-format/snapshots/swift/examples.json new file mode 100644 index 00000000..4d2dc269 --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/swift/examples.json @@ -0,0 +1,19 @@ +{ + "Client_Init": { + "usage": "let client = PachcaClient(token: \"YOUR_TOKEN\")", + "imports": [ + "PachcaClient" + ] + }, + "EventOperations_listEvents": { + "usage": "let response = try await client.export.listEvents(dateFrom: \"2024-01-01\", dateTo: \"2024-01-01\", createdAfter: \"2024-01-01T00:00:00Z\", limit: 123)", + "output": "ListEventsResponse(data: [Event])" + }, + "ExportOperations_createExport": { + "usage": "let body = ExportRequest(\n startAt: \"2025-03-20\",\n endAt: \"2025-03-20\",\n webhookUrl: \"example\"\n)\nlet response = try await client.export.createExport(body: body)", + "output": "Export(id: Int, startAt: String, endAt: String, status: String, createdAt: String)", + "imports": [ + "ExportRequest" + ] + } +} diff --git a/packages/generator/tests/date-format/snapshots/ts/client.ts b/packages/generator/tests/date-format/snapshots/ts/client.ts new file mode 100644 index 00000000..fd9491f1 --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/ts/client.ts @@ -0,0 +1,59 @@ +import { + ListEventsParams, + ListEventsResponse, + OAuthError, + ExportRequest, + Export, +} from "./types"; +import { deserialize, serialize, fetchWithRetry } from "./utils"; + +class ExportService { + constructor( + private baseUrl: string, + private headers: Record, + ) {} + + async listEvents(params: ListEventsParams): Promise { + const query = new URLSearchParams(); + query.set("date_from", params.dateFrom); + if (params?.dateTo !== undefined) query.set("date_to", params.dateTo); + if (params?.createdAfter !== undefined) query.set("created_after", params.createdAfter); + if (params?.limit !== undefined) query.set("limit", String(params.limit)); + const response = await fetchWithRetry(`${this.baseUrl}/events?${query}`, { + headers: this.headers, + }); + const body = await response.json(); + switch (response.status) { + case 200: + return deserialize(body) as ListEventsResponse; + default: + throw new Error(`HTTP ${response.status}: ${JSON.stringify(body)}`); + } + } + + async createExport(request: ExportRequest): Promise { + const response = await fetchWithRetry(`${this.baseUrl}/exports`, { + method: "POST", + headers: { ...this.headers, "Content-Type": "application/json" }, + body: JSON.stringify(serialize(request)), + }); + const body = await response.json(); + switch (response.status) { + case 201: + return deserialize(body.data) as Export; + case 401: + throw new OAuthError(body.error); + default: + throw new Error(`HTTP ${response.status}: ${JSON.stringify(body)}`); + } + } +} + +export class PachcaClient { + readonly export: ExportService; + + constructor(token: string, baseUrl: string = "https://api.pachca.com/api/shared/v1") { + const headers = { Authorization: `Bearer ${token}` }; + this.export = new ExportService(baseUrl, headers); + } +} diff --git a/packages/generator/tests/date-format/snapshots/ts/examples.json b/packages/generator/tests/date-format/snapshots/ts/examples.json new file mode 100644 index 00000000..6ae799da --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/ts/examples.json @@ -0,0 +1,19 @@ +{ + "Client_Init": { + "usage": "const client = new PachcaClient(\"YOUR_TOKEN\")", + "imports": [ + "PachcaClient" + ] + }, + "EventOperations_listEvents": { + "usage": "const response = client.export.listEvents({\n dateFrom: \"2024-01-01\",\n dateTo: \"2024-01-01\",\n createdAfter: \"2024-01-01T00:00:00Z\",\n limit: 123\n})", + "output": "ListEventsResponse({ data: Event[] })" + }, + "ExportOperations_createExport": { + "usage": "const request: ExportRequest = {\n startAt: \"2025-03-20\",\n endAt: \"2025-03-20\",\n webhookUrl: \"example\"\n}\nconst response = client.export.createExport(request)", + "output": "Export({ id: number, startAt: string, endAt: string, status: string, createdAt: string })", + "imports": [ + "ExportRequest" + ] + } +} diff --git a/packages/generator/tests/date-format/snapshots/ts/types.ts b/packages/generator/tests/date-format/snapshots/ts/types.ts new file mode 100644 index 00000000..30390130 --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/ts/types.ts @@ -0,0 +1,38 @@ +export interface ExportRequest { + startAt: string; + endAt: string; + webhookUrl: string; +} + +export interface Export { + id: number; + startAt: string; + endAt: string; + status: string; + createdAt: string; +} + +export interface Event { + id: number; + type: string; + occurredAt: string; +} + +export class OAuthError extends Error { + error?: string; + constructor(error?: string) { + super(error); + this.error = error; + } +} + +export interface ListEventsParams { + dateFrom: string; + dateTo?: string; + createdAfter?: string; + limit?: number; +} + +export interface ListEventsResponse { + data: Event[]; +} diff --git a/packages/generator/tests/date-format/snapshots/ts/utils.ts b/packages/generator/tests/date-format/snapshots/ts/utils.ts new file mode 100644 index 00000000..5cf7ef4a --- /dev/null +++ b/packages/generator/tests/date-format/snapshots/ts/utils.ts @@ -0,0 +1,58 @@ +function snakeToCamel(str: string): string { + const camel = str.replace(/[-_]([a-zA-Z])/g, (_, c) => c.toUpperCase()); + return camel.charAt(0).toLowerCase() + camel.slice(1); +} + +function camelToSnake(str: string): string { + return str + .replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2") + .replace(/([a-z0-9])([A-Z])/g, "$1_$2") + .toLowerCase(); +} + +export function deserialize(obj: unknown): unknown { + if (Array.isArray(obj)) return obj.map(deserialize); + if (obj !== null && typeof obj === "object") { + return Object.fromEntries( + Object.entries(obj).map(([k, v]) => [snakeToCamel(k), deserialize(v)]), + ); + } + return obj; +} + +export function serialize(obj: unknown): unknown { + if (Array.isArray(obj)) return obj.map(serialize); + if (obj !== null && typeof obj === "object") { + return Object.fromEntries( + Object.entries(obj) + .filter(([, v]) => v !== undefined) + .map(([k, v]) => [camelToSnake(k), serialize(v)]), + ); + } + return obj; +} + +const MAX_RETRIES = 3; +const RETRYABLE_5XX = new Set([500, 502, 503, 504]); + +function jitter(delay: number): number { + return delay * (0.5 + Math.random() * 0.5); +} + +export async function fetchWithRetry(input: RequestInfo | URL, init?: RequestInit): Promise { + for (let attempt = 0; ; attempt++) { + const response = await fetch(input, init); + if (response.status === 429 && attempt < MAX_RETRIES) { + const retryAfter = response.headers.get("retry-after"); + const delay = retryAfter ? Number(retryAfter) * 1000 : 1000 * Math.pow(2, attempt); + await new Promise((r) => setTimeout(r, delay)); + continue; + } + if (RETRYABLE_5XX.has(response.status) && attempt < MAX_RETRIES) { + const delay = jitter(10000 * Math.pow(2, attempt)); + await new Promise((r) => setTimeout(r, delay)); + continue; + } + return response; + } +} diff --git a/packages/generator/tests/deep-nesting/snapshots/cs/Utils.cs b/packages/generator/tests/deep-nesting/snapshots/cs/Utils.cs index 88d4be58..2cf57544 100644 --- a/packages/generator/tests/deep-nesting/snapshots/cs/Utils.cs +++ b/packages/generator/tests/deep-nesting/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/packages/generator/tests/deep-nesting/snapshots/swift/Utils.swift b/packages/generator/tests/deep-nesting/snapshots/swift/Utils.swift index d65d2a00..8df90b02 100644 --- a/packages/generator/tests/deep-nesting/snapshots/swift/Utils.swift +++ b/packages/generator/tests/deep-nesting/snapshots/swift/Utils.swift @@ -5,13 +5,11 @@ import FoundationNetworking let pachcaDecoder: JSONDecoder = { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 return decoder }() let pachcaEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 return encoder }() diff --git a/packages/generator/tests/edge-cases/snapshots/cs/Client.cs b/packages/generator/tests/edge-cases/snapshots/cs/Client.cs index bc4a882d..169b2f0c 100644 --- a/packages/generator/tests/edge-cases/snapshots/cs/Client.cs +++ b/packages/generator/tests/edge-cases/snapshots/cs/Client.cs @@ -33,9 +33,10 @@ public async System.Threading.Tasks.Task ListEventsAsync( if (isActive != null) queryParts.Add($"is_active={Uri.EscapeDataString((isActive.Value ? "true" : "false"))}"); if (scopes != null) - queryParts.Add($"scopes={Uri.EscapeDataString(scopes.ToString())}"); + foreach (var item in scopes) + queryParts.Add($"scopes[]={Uri.EscapeDataString(PachcaUtils.EnumToApiString(item))}"); if (filter != null) - queryParts.Add($"filter={Uri.EscapeDataString(filter.ToString())}"); + queryParts.Add($"filter={Uri.EscapeDataString(filter.ToString()!)}"); var url = $"{_baseUrl}/events" + (queryParts.Count > 0 ? "?" + string.Join("&", queryParts) : ""); using var request = new HttpRequestMessage(HttpMethod.Get, url); using var response = await PachcaUtils.SendWithRetryAsync(_client, request, cancellationToken).ConfigureAwait(false); diff --git a/packages/generator/tests/edge-cases/snapshots/cs/Utils.cs b/packages/generator/tests/edge-cases/snapshots/cs/Utils.cs index 88d4be58..2cf57544 100644 --- a/packages/generator/tests/edge-cases/snapshots/cs/Utils.cs +++ b/packages/generator/tests/edge-cases/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/packages/generator/tests/edge-cases/snapshots/go/client.go b/packages/generator/tests/edge-cases/snapshots/go/client.go index a68b10de..2cf4d53d 100644 --- a/packages/generator/tests/edge-cases/snapshots/go/client.go +++ b/packages/generator/tests/edge-cases/snapshots/go/client.go @@ -76,8 +76,10 @@ func (s *EventsService) ListEvents(ctx context.Context, params *ListEventsParams if params != nil && params.IsActive != nil { q.Set("is_active", fmt.Sprintf("%v", *params.IsActive)) } - if params != nil && params.Scopes != nil { - q.Set("scopes", fmt.Sprintf("%v", params.Scopes)) + if params != nil { + for _, v := range params.Scopes { + q.Add("scopes[]", fmt.Sprintf("%v", v)) + } } if params != nil && params.Filter != nil { q.Set("filter", fmt.Sprintf("%v", *params.Filter)) diff --git a/packages/generator/tests/edge-cases/snapshots/kt/Client.kt b/packages/generator/tests/edge-cases/snapshots/kt/Client.kt index b7734d03..8c0178c2 100644 --- a/packages/generator/tests/edge-cases/snapshots/kt/Client.kt +++ b/packages/generator/tests/edge-cases/snapshots/kt/Client.kt @@ -25,7 +25,7 @@ class EventsService internal constructor( ): ListEventsResponse { val response = client.get("$baseUrl/events") { isActive?.let { parameter("is_active", it) } - scopes?.let { parameter("scopes", it) } + scopes?.forEach { parameter("scopes[]", it.value) } filter?.let { parameter("filter", it) } } return when (response.status.value) { diff --git a/packages/generator/tests/edge-cases/snapshots/py/client.py b/packages/generator/tests/edge-cases/snapshots/py/client.py index 687f9eb9..f28be640 100644 --- a/packages/generator/tests/edge-cases/snapshots/py/client.py +++ b/packages/generator/tests/edge-cases/snapshots/py/client.py @@ -20,13 +20,14 @@ async def list_events( self, params: ListEventsParams | None = None, ) -> ListEventsResponse: - query: dict[str, str] = {} + query: list[tuple[str, str]] = [] if params is not None and params.is_active is not None: - query["is_active"] = str(params.is_active).lower() + query.append(("is_active", str(params.is_active).lower())) if params is not None and params.scopes is not None: - query["scopes"] = params.scopes + for v in params.scopes: + query.append(("scopes[]", str(v))) if params is not None and params.filter is not None: - query["filter"] = params.filter + query.append(("filter", params.filter)) response = await self._client.get( "/events", params=query, diff --git a/packages/generator/tests/edge-cases/snapshots/py/utils.py b/packages/generator/tests/edge-cases/snapshots/py/utils.py index d1494873..9288f902 100644 --- a/packages/generator/tests/edge-cases/snapshots/py/utils.py +++ b/packages/generator/tests/edge-cases/snapshots/py/utils.py @@ -3,6 +3,7 @@ import dataclasses import keyword from dataclasses import asdict, fields +from datetime import datetime from typing import Type, TypeVar, get_args, get_origin, get_type_hints import httpx @@ -58,6 +59,16 @@ def deserialize(cls: Type[T], data: dict) -> T: item_tp = _resolve_list_item_type(hints[f.name]) if item_tp is not None and _is_dataclass_type(item_tp): v = [deserialize(item_tp, i) if isinstance(i, dict) else i for i in v] + elif isinstance(v, str): + hint = hints.get(f.name) + raw_hint = hint + if get_origin(hint) is not None: + for a in get_args(hint): + if a is not type(None): + raw_hint = a + break + if raw_hint is datetime: + v = datetime.fromisoformat(v) kwargs[k] = v return cls(**kwargs) @@ -70,6 +81,8 @@ def _strip_nones(val: object) -> object: } if isinstance(val, list): return [_strip_nones(v) for v in val] + if isinstance(val, datetime): + return val.isoformat() return val diff --git a/packages/generator/tests/edge-cases/snapshots/swift/Client.swift b/packages/generator/tests/edge-cases/snapshots/swift/Client.swift index 0cb2aa07..d55becbb 100644 --- a/packages/generator/tests/edge-cases/snapshots/swift/Client.swift +++ b/packages/generator/tests/edge-cases/snapshots/swift/Client.swift @@ -18,7 +18,7 @@ public struct EventsService { var components = URLComponents(string: "\(baseURL)/events")! var queryItems: [URLQueryItem] = [] if let isActive { queryItems.append(URLQueryItem(name: "is_active", value: String(isActive))) } - if let scopes { scopes.forEach { queryItems.append(URLQueryItem(name: "scopes", value: $0.rawValue)) } } + if let scopes { scopes.forEach { queryItems.append(URLQueryItem(name: "scopes[]", value: $0.rawValue)) } } if let filter { queryItems.append(URLQueryItem(name: "filter", value: String(data: try serialize(filter), encoding: .utf8)!)) } if !queryItems.isEmpty { components.queryItems = queryItems } var request = URLRequest(url: components.url!) @@ -38,7 +38,7 @@ public struct EventsService { request.httpMethod = "PUT" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = try JSONSerialization.data(withJSONObject: ["scope": scope]) + request.httpBody = try JSONSerialization.data(withJSONObject: ["scope": scope.rawValue]) let (data, urlResponse) = try await dataWithRetry(session: session, for: request) let statusCode = (urlResponse as! HTTPURLResponse).statusCode switch statusCode { diff --git a/packages/generator/tests/edge-cases/snapshots/swift/Utils.swift b/packages/generator/tests/edge-cases/snapshots/swift/Utils.swift index d65d2a00..8df90b02 100644 --- a/packages/generator/tests/edge-cases/snapshots/swift/Utils.swift +++ b/packages/generator/tests/edge-cases/snapshots/swift/Utils.swift @@ -5,13 +5,11 @@ import FoundationNetworking let pachcaDecoder: JSONDecoder = { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 return decoder }() let pachcaEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 return encoder }() diff --git a/packages/generator/tests/edge-cases/snapshots/ts/client.ts b/packages/generator/tests/edge-cases/snapshots/ts/client.ts index 769675da..51393421 100644 --- a/packages/generator/tests/edge-cases/snapshots/ts/client.ts +++ b/packages/generator/tests/edge-cases/snapshots/ts/client.ts @@ -16,7 +16,9 @@ class EventsService { async listEvents(params?: ListEventsParams): Promise { const query = new URLSearchParams(); if (params?.isActive !== undefined) query.set("is_active", String(params.isActive)); - if (params?.scopes !== undefined) query.set("scopes", String(params.scopes)); + if (params?.scopes !== undefined) { + params.scopes.forEach((v) => query.append("scopes[]", String(v))); + } if (params?.filter !== undefined) query.set("filter", String(params.filter)); const url = `${this.baseUrl}/events${query.toString() ? `?${query}` : ""}`; const response = await fetchWithRetry(url, { diff --git a/packages/generator/tests/enums/snapshots/cs/Utils.cs b/packages/generator/tests/enums/snapshots/cs/Utils.cs index 88d4be58..2cf57544 100644 --- a/packages/generator/tests/enums/snapshots/cs/Utils.cs +++ b/packages/generator/tests/enums/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/packages/generator/tests/enums/snapshots/swift/Utils.swift b/packages/generator/tests/enums/snapshots/swift/Utils.swift index d65d2a00..8df90b02 100644 --- a/packages/generator/tests/enums/snapshots/swift/Utils.swift +++ b/packages/generator/tests/enums/snapshots/swift/Utils.swift @@ -5,13 +5,11 @@ import FoundationNetworking let pachcaDecoder: JSONDecoder = { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 return decoder }() let pachcaEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 return encoder }() diff --git a/packages/generator/tests/models/snapshots/cs/Models.cs b/packages/generator/tests/models/snapshots/cs/Models.cs index 5938aab2..443d007f 100644 --- a/packages/generator/tests/models/snapshots/cs/Models.cs +++ b/packages/generator/tests/models/snapshots/cs/Models.cs @@ -71,7 +71,7 @@ public class User [JsonPropertyName("created_at")] public DateTimeOffset CreatedAt { get; set; } = default!; [JsonPropertyName("birthday")] - public DateOnly? Birthday { get; set; } + public string? Birthday { get; set; } [JsonPropertyName("tag_ids")] public List TagIds { get; set; } = default!; [JsonPropertyName("custom_properties")] diff --git a/packages/generator/tests/models/snapshots/cs/Utils.cs b/packages/generator/tests/models/snapshots/cs/Utils.cs index 88d4be58..2cf57544 100644 --- a/packages/generator/tests/models/snapshots/cs/Utils.cs +++ b/packages/generator/tests/models/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/packages/generator/tests/models/snapshots/kt/Models.kt b/packages/generator/tests/models/snapshots/kt/Models.kt index 9c05f48a..0781bd07 100644 --- a/packages/generator/tests/models/snapshots/kt/Models.kt +++ b/packages/generator/tests/models/snapshots/kt/Models.kt @@ -1,7 +1,20 @@ package com.pachca.sdk +import java.time.OffsetDateTime +import java.time.format.DateTimeFormatter +import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +object OffsetDateTimeSerializer : KSerializer { + override val descriptor = PrimitiveSerialDescriptor("OffsetDateTime", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: OffsetDateTime) = encoder.encodeString(value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)) + override fun deserialize(decoder: Decoder): OffsetDateTime = OffsetDateTime.parse(decoder.decodeString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME) +} /** Роль пользователя */ @Serializable @@ -26,7 +39,7 @@ data class User( val role: UserRole, @SerialName("is_active") val isActive: Boolean, @SerialName("bot_id") val botId: Long? = null, - @SerialName("created_at") val createdAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("created_at") val createdAt: OffsetDateTime, val birthday: String? = null, @SerialName("tag_ids") val tagIds: List, @SerialName("custom_properties") val customProperties: List? = null, @@ -37,7 +50,7 @@ data class User( data class UserStatus( val emoji: String? = null, val title: String? = null, - @SerialName("expires_at") val expiresAt: String? = null, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("expires_at") val expiresAt: OffsetDateTime? = null, ) @Serializable diff --git a/packages/generator/tests/models/snapshots/py/models.py b/packages/generator/tests/models/snapshots/py/models.py index f5f44494..c6dfc8b9 100644 --- a/packages/generator/tests/models/snapshots/py/models.py +++ b/packages/generator/tests/models/snapshots/py/models.py @@ -1,5 +1,6 @@ from __future__ import annotations +from datetime import datetime from dataclasses import dataclass from enum import StrEnum @@ -20,7 +21,7 @@ class User: email: str role: UserRole is_active: bool - created_at: str + created_at: datetime tag_ids: list[int] phone_number: str | None = None bot_id: int | None = None @@ -33,7 +34,7 @@ class User: class UserStatus: emoji: str | None = None title: str | None = None - expires_at: str | None = None + expires_at: datetime | None = None @dataclass diff --git a/packages/generator/tests/models/snapshots/swift/Models.swift b/packages/generator/tests/models/snapshots/swift/Models.swift index e3328aa4..bd63641b 100644 --- a/packages/generator/tests/models/snapshots/swift/Models.swift +++ b/packages/generator/tests/models/snapshots/swift/Models.swift @@ -23,13 +23,13 @@ public struct User: Codable { public let role: UserRole public let isActive: Bool public let botId: Int64? - public let createdAt: Date + public let createdAt: String public let birthday: String? public let tagIds: [Int] public let customProperties: [CustomProperty]? public let status: UserStatus? - public init(id: Int, firstName: String, lastName: String, email: String, phoneNumber: String? = nil, role: UserRole, isActive: Bool, botId: Int64? = nil, createdAt: Date, birthday: String? = nil, tagIds: [Int], customProperties: [CustomProperty]? = nil, status: UserStatus? = nil) { + public init(id: Int, firstName: String, lastName: String, email: String, phoneNumber: String? = nil, role: UserRole, isActive: Bool, botId: Int64? = nil, createdAt: String, birthday: String? = nil, tagIds: [Int], customProperties: [CustomProperty]? = nil, status: UserStatus? = nil) { self.id = id self.firstName = firstName self.lastName = lastName diff --git a/packages/generator/tests/models/snapshots/swift/Utils.swift b/packages/generator/tests/models/snapshots/swift/Utils.swift index d65d2a00..8df90b02 100644 --- a/packages/generator/tests/models/snapshots/swift/Utils.swift +++ b/packages/generator/tests/models/snapshots/swift/Utils.swift @@ -5,13 +5,11 @@ import FoundationNetworking let pachcaDecoder: JSONDecoder = { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 return decoder }() let pachcaEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 return encoder }() diff --git a/packages/generator/tests/multi-path-params/snapshots/cs/Utils.cs b/packages/generator/tests/multi-path-params/snapshots/cs/Utils.cs index 88d4be58..2cf57544 100644 --- a/packages/generator/tests/multi-path-params/snapshots/cs/Utils.cs +++ b/packages/generator/tests/multi-path-params/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/packages/generator/tests/multi-path-params/snapshots/py/utils.py b/packages/generator/tests/multi-path-params/snapshots/py/utils.py index d1494873..9288f902 100644 --- a/packages/generator/tests/multi-path-params/snapshots/py/utils.py +++ b/packages/generator/tests/multi-path-params/snapshots/py/utils.py @@ -3,6 +3,7 @@ import dataclasses import keyword from dataclasses import asdict, fields +from datetime import datetime from typing import Type, TypeVar, get_args, get_origin, get_type_hints import httpx @@ -58,6 +59,16 @@ def deserialize(cls: Type[T], data: dict) -> T: item_tp = _resolve_list_item_type(hints[f.name]) if item_tp is not None and _is_dataclass_type(item_tp): v = [deserialize(item_tp, i) if isinstance(i, dict) else i for i in v] + elif isinstance(v, str): + hint = hints.get(f.name) + raw_hint = hint + if get_origin(hint) is not None: + for a in get_args(hint): + if a is not type(None): + raw_hint = a + break + if raw_hint is datetime: + v = datetime.fromisoformat(v) kwargs[k] = v return cls(**kwargs) @@ -70,6 +81,8 @@ def _strip_nones(val: object) -> object: } if isinstance(val, list): return [_strip_nones(v) for v in val] + if isinstance(val, datetime): + return val.isoformat() return val diff --git a/packages/generator/tests/multi-path-params/snapshots/swift/Utils.swift b/packages/generator/tests/multi-path-params/snapshots/swift/Utils.swift index d65d2a00..8df90b02 100644 --- a/packages/generator/tests/multi-path-params/snapshots/swift/Utils.swift +++ b/packages/generator/tests/multi-path-params/snapshots/swift/Utils.swift @@ -5,13 +5,11 @@ import FoundationNetworking let pachcaDecoder: JSONDecoder = { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 return decoder }() let pachcaEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 return encoder }() diff --git a/packages/generator/tests/nullable-ref/snapshots/cs/Utils.cs b/packages/generator/tests/nullable-ref/snapshots/cs/Utils.cs index 88d4be58..2cf57544 100644 --- a/packages/generator/tests/nullable-ref/snapshots/cs/Utils.cs +++ b/packages/generator/tests/nullable-ref/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/packages/generator/tests/nullable-ref/snapshots/swift/Utils.swift b/packages/generator/tests/nullable-ref/snapshots/swift/Utils.swift index d65d2a00..8df90b02 100644 --- a/packages/generator/tests/nullable-ref/snapshots/swift/Utils.swift +++ b/packages/generator/tests/nullable-ref/snapshots/swift/Utils.swift @@ -5,13 +5,11 @@ import FoundationNetworking let pachcaDecoder: JSONDecoder = { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 return decoder }() let pachcaEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 return encoder }() diff --git a/packages/generator/tests/oneof/snapshots/cs/Utils.cs b/packages/generator/tests/oneof/snapshots/cs/Utils.cs index 88d4be58..2cf57544 100644 --- a/packages/generator/tests/oneof/snapshots/cs/Utils.cs +++ b/packages/generator/tests/oneof/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/packages/generator/tests/oneof/snapshots/swift/Utils.swift b/packages/generator/tests/oneof/snapshots/swift/Utils.swift index d65d2a00..8df90b02 100644 --- a/packages/generator/tests/oneof/snapshots/swift/Utils.swift +++ b/packages/generator/tests/oneof/snapshots/swift/Utils.swift @@ -5,13 +5,11 @@ import FoundationNetworking let pachcaDecoder: JSONDecoder = { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 return decoder }() let pachcaEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 return encoder }() diff --git a/packages/generator/tests/patch/snapshots/cs/Utils.cs b/packages/generator/tests/patch/snapshots/cs/Utils.cs index 88d4be58..2cf57544 100644 --- a/packages/generator/tests/patch/snapshots/cs/Utils.cs +++ b/packages/generator/tests/patch/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/packages/generator/tests/patch/snapshots/go/client.go b/packages/generator/tests/patch/snapshots/go/client.go index 7aa12eb2..24401c3f 100644 --- a/packages/generator/tests/patch/snapshots/go/client.go +++ b/packages/generator/tests/patch/snapshots/go/client.go @@ -90,7 +90,9 @@ func (s *ItemsService) PatchItem(ctx context.Context, id int32, request ItemPatc return &result.Data, nil default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } diff --git a/packages/generator/tests/patch/snapshots/py/utils.py b/packages/generator/tests/patch/snapshots/py/utils.py index d1494873..9288f902 100644 --- a/packages/generator/tests/patch/snapshots/py/utils.py +++ b/packages/generator/tests/patch/snapshots/py/utils.py @@ -3,6 +3,7 @@ import dataclasses import keyword from dataclasses import asdict, fields +from datetime import datetime from typing import Type, TypeVar, get_args, get_origin, get_type_hints import httpx @@ -58,6 +59,16 @@ def deserialize(cls: Type[T], data: dict) -> T: item_tp = _resolve_list_item_type(hints[f.name]) if item_tp is not None and _is_dataclass_type(item_tp): v = [deserialize(item_tp, i) if isinstance(i, dict) else i for i in v] + elif isinstance(v, str): + hint = hints.get(f.name) + raw_hint = hint + if get_origin(hint) is not None: + for a in get_args(hint): + if a is not type(None): + raw_hint = a + break + if raw_hint is datetime: + v = datetime.fromisoformat(v) kwargs[k] = v return cls(**kwargs) @@ -70,6 +81,8 @@ def _strip_nones(val: object) -> object: } if isinstance(val, list): return [_strip_nones(v) for v in val] + if isinstance(val, datetime): + return val.isoformat() return val diff --git a/packages/generator/tests/patch/snapshots/swift/Utils.swift b/packages/generator/tests/patch/snapshots/swift/Utils.swift index d65d2a00..8df90b02 100644 --- a/packages/generator/tests/patch/snapshots/swift/Utils.swift +++ b/packages/generator/tests/patch/snapshots/swift/Utils.swift @@ -5,13 +5,11 @@ import FoundationNetworking let pachcaDecoder: JSONDecoder = { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 return decoder }() let pachcaEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 return encoder }() diff --git a/packages/generator/tests/record/snapshots/cs/Utils.cs b/packages/generator/tests/record/snapshots/cs/Utils.cs index 88d4be58..2cf57544 100644 --- a/packages/generator/tests/record/snapshots/cs/Utils.cs +++ b/packages/generator/tests/record/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/packages/generator/tests/record/snapshots/go/client.go b/packages/generator/tests/record/snapshots/go/client.go index d1c1119a..e8c4ebf3 100644 --- a/packages/generator/tests/record/snapshots/go/client.go +++ b/packages/generator/tests/record/snapshots/go/client.go @@ -84,11 +84,15 @@ func (s *LinkPreviewsService) CreateLinkPreviews(ctx context.Context, id int32, return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } diff --git a/packages/generator/tests/record/snapshots/py/utils.py b/packages/generator/tests/record/snapshots/py/utils.py index d1494873..9288f902 100644 --- a/packages/generator/tests/record/snapshots/py/utils.py +++ b/packages/generator/tests/record/snapshots/py/utils.py @@ -3,6 +3,7 @@ import dataclasses import keyword from dataclasses import asdict, fields +from datetime import datetime from typing import Type, TypeVar, get_args, get_origin, get_type_hints import httpx @@ -58,6 +59,16 @@ def deserialize(cls: Type[T], data: dict) -> T: item_tp = _resolve_list_item_type(hints[f.name]) if item_tp is not None and _is_dataclass_type(item_tp): v = [deserialize(item_tp, i) if isinstance(i, dict) else i for i in v] + elif isinstance(v, str): + hint = hints.get(f.name) + raw_hint = hint + if get_origin(hint) is not None: + for a in get_args(hint): + if a is not type(None): + raw_hint = a + break + if raw_hint is datetime: + v = datetime.fromisoformat(v) kwargs[k] = v return cls(**kwargs) @@ -70,6 +81,8 @@ def _strip_nones(val: object) -> object: } if isinstance(val, list): return [_strip_nones(v) for v in val] + if isinstance(val, datetime): + return val.isoformat() return val diff --git a/packages/generator/tests/record/snapshots/swift/Utils.swift b/packages/generator/tests/record/snapshots/swift/Utils.swift index d65d2a00..8df90b02 100644 --- a/packages/generator/tests/record/snapshots/swift/Utils.swift +++ b/packages/generator/tests/record/snapshots/swift/Utils.swift @@ -5,13 +5,11 @@ import FoundationNetworking let pachcaDecoder: JSONDecoder = { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 return decoder }() let pachcaEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 return encoder }() diff --git a/packages/generator/tests/redirect/snapshots/cs/Utils.cs b/packages/generator/tests/redirect/snapshots/cs/Utils.cs index 88d4be58..2cf57544 100644 --- a/packages/generator/tests/redirect/snapshots/cs/Utils.cs +++ b/packages/generator/tests/redirect/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/packages/generator/tests/redirect/snapshots/go/client.go b/packages/generator/tests/redirect/snapshots/go/client.go index 9069939b..e0ce9ffa 100644 --- a/packages/generator/tests/redirect/snapshots/go/client.go +++ b/packages/generator/tests/redirect/snapshots/go/client.go @@ -83,11 +83,15 @@ func (s *CommonService) DownloadExport(ctx context.Context, id int32) (string, e return location, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return "", &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return "", fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return "", &e } } diff --git a/packages/generator/tests/redirect/snapshots/py/utils.py b/packages/generator/tests/redirect/snapshots/py/utils.py index d1494873..9288f902 100644 --- a/packages/generator/tests/redirect/snapshots/py/utils.py +++ b/packages/generator/tests/redirect/snapshots/py/utils.py @@ -3,6 +3,7 @@ import dataclasses import keyword from dataclasses import asdict, fields +from datetime import datetime from typing import Type, TypeVar, get_args, get_origin, get_type_hints import httpx @@ -58,6 +59,16 @@ def deserialize(cls: Type[T], data: dict) -> T: item_tp = _resolve_list_item_type(hints[f.name]) if item_tp is not None and _is_dataclass_type(item_tp): v = [deserialize(item_tp, i) if isinstance(i, dict) else i for i in v] + elif isinstance(v, str): + hint = hints.get(f.name) + raw_hint = hint + if get_origin(hint) is not None: + for a in get_args(hint): + if a is not type(None): + raw_hint = a + break + if raw_hint is datetime: + v = datetime.fromisoformat(v) kwargs[k] = v return cls(**kwargs) @@ -70,6 +81,8 @@ def _strip_nones(val: object) -> object: } if isinstance(val, list): return [_strip_nones(v) for v in val] + if isinstance(val, datetime): + return val.isoformat() return val diff --git a/packages/generator/tests/redirect/snapshots/swift/Utils.swift b/packages/generator/tests/redirect/snapshots/swift/Utils.swift index d65d2a00..8df90b02 100644 --- a/packages/generator/tests/redirect/snapshots/swift/Utils.swift +++ b/packages/generator/tests/redirect/snapshots/swift/Utils.swift @@ -5,13 +5,11 @@ import FoundationNetworking let pachcaDecoder: JSONDecoder = { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 return decoder }() let pachcaEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 return encoder }() diff --git a/packages/generator/tests/reserved-keywords/snapshots/cs/Utils.cs b/packages/generator/tests/reserved-keywords/snapshots/cs/Utils.cs index 88d4be58..2cf57544 100644 --- a/packages/generator/tests/reserved-keywords/snapshots/cs/Utils.cs +++ b/packages/generator/tests/reserved-keywords/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/packages/generator/tests/reserved-keywords/snapshots/swift/Utils.swift b/packages/generator/tests/reserved-keywords/snapshots/swift/Utils.swift index d65d2a00..8df90b02 100644 --- a/packages/generator/tests/reserved-keywords/snapshots/swift/Utils.swift +++ b/packages/generator/tests/reserved-keywords/snapshots/swift/Utils.swift @@ -5,13 +5,11 @@ import FoundationNetworking let pachcaDecoder: JSONDecoder = { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 return decoder }() let pachcaEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 return encoder }() diff --git a/packages/generator/tests/search/snapshots/cs/Client.cs b/packages/generator/tests/search/snapshots/cs/Client.cs index d098afc2..850b1e1c 100644 --- a/packages/generator/tests/search/snapshots/cs/Client.cs +++ b/packages/generator/tests/search/snapshots/cs/Client.cs @@ -37,10 +37,10 @@ public async System.Threading.Tasks.Task SearchMessagesA queryParts.Add($"query={Uri.EscapeDataString(query)}"); if (chatIds != null) foreach (var item in chatIds) - queryParts.Add($"chat_ids[]={Uri.EscapeDataString(item.ToString())}"); + queryParts.Add($"chat_ids[]={Uri.EscapeDataString(item.ToString()!)}"); if (userIds != null) foreach (var item in userIds) - queryParts.Add($"user_ids[]={Uri.EscapeDataString(item.ToString())}"); + queryParts.Add($"user_ids[]={Uri.EscapeDataString(item.ToString()!)}"); if (createdFrom != null) queryParts.Add($"created_from={Uri.EscapeDataString(createdFrom.Value.ToString("o"))}"); if (createdTo != null) @@ -48,7 +48,7 @@ public async System.Threading.Tasks.Task SearchMessagesA if (sort != null) queryParts.Add($"sort={Uri.EscapeDataString(PachcaUtils.EnumToApiString(sort.Value))}"); if (limit != null) - queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString())}"); + queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString()!)}"); if (cursor != null) queryParts.Add($"cursor={Uri.EscapeDataString(cursor)}"); var url = $"{_baseUrl}/search/messages" + (queryParts.Count > 0 ? "?" + string.Join("&", queryParts) : ""); @@ -82,8 +82,9 @@ public async System.Threading.Tasks.Task> SearchMessag { var response = await SearchMessagesAsync(query: query, chatIds: chatIds, userIds: userIds, createdFrom: createdFrom, createdTo: createdTo, sort: sort, limit: limit, cursor: cursor, cancellationToken: cancellationToken).ConfigureAwait(false); items.AddRange(response.Data); - cursor = response.Meta?.Paginate?.NextPage; - } while (cursor != null); + if (response.Data.Count == 0) break; + cursor = response.Meta.Paginate.NextPage; + } while (true); return items; } } diff --git a/packages/generator/tests/search/snapshots/cs/Utils.cs b/packages/generator/tests/search/snapshots/cs/Utils.cs index 88d4be58..2cf57544 100644 --- a/packages/generator/tests/search/snapshots/cs/Utils.cs +++ b/packages/generator/tests/search/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/packages/generator/tests/search/snapshots/go/client.go b/packages/generator/tests/search/snapshots/go/client.go index 828079e1..91b8a3e6 100644 --- a/packages/generator/tests/search/snapshots/go/client.go +++ b/packages/generator/tests/search/snapshots/go/client.go @@ -111,7 +111,9 @@ func (s *SearchService) SearchMessages(ctx context.Context, params SearchMessage return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) @@ -131,10 +133,11 @@ func (s *SearchService) SearchMessagesAll(ctx context.Context, params *SearchMes return nil, err } items = append(items, result.Data...) - if result.Meta == nil || result.Meta.Paginate == nil || result.Meta.Paginate.NextPage == nil { + if len(result.Data) == 0 { return items, nil } - cursor = result.Meta.Paginate.NextPage + nextPage := result.Meta.Paginate.NextPage + cursor = &nextPage } } diff --git a/packages/generator/tests/search/snapshots/kt/Client.kt b/packages/generator/tests/search/snapshots/kt/Client.kt index 13681255..c871bfa4 100644 --- a/packages/generator/tests/search/snapshots/kt/Client.kt +++ b/packages/generator/tests/search/snapshots/kt/Client.kt @@ -12,6 +12,7 @@ import io.ktor.http.* import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable +import java.time.OffsetDateTime class SearchService internal constructor( private val baseUrl: String, @@ -21,8 +22,8 @@ class SearchService internal constructor( query: String, chatIds: List? = null, userIds: List? = null, - createdFrom: String? = null, - createdTo: String? = null, + createdFrom: OffsetDateTime? = null, + createdTo: OffsetDateTime? = null, sort: SearchSort? = null, limit: Int? = null, cursor: String? = null, @@ -31,8 +32,8 @@ class SearchService internal constructor( parameter("query", query) chatIds?.forEach { parameter("chat_ids[]", it) } userIds?.forEach { parameter("user_ids[]", it) } - createdFrom?.let { parameter("created_from", it) } - createdTo?.let { parameter("created_to", it) } + createdFrom?.let { parameter("created_from", it.toString()) } + createdTo?.let { parameter("created_to", it.toString()) } sort?.let { parameter("sort", it.value) } limit?.let { parameter("limit", it) } cursor?.let { parameter("cursor", it) } @@ -48,8 +49,8 @@ class SearchService internal constructor( query: String, chatIds: List? = null, userIds: List? = null, - createdFrom: String? = null, - createdTo: String? = null, + createdFrom: OffsetDateTime? = null, + createdTo: OffsetDateTime? = null, sort: SearchSort? = null, limit: Int? = null, ): List { @@ -67,8 +68,9 @@ class SearchService internal constructor( cursor = cursor, ) items.addAll(response.data) - cursor = response.meta?.paginate?.nextPage - } while (cursor != null) + if (response.data.isEmpty()) break + cursor = response.meta.paginate.nextPage + } while (true) return items } } diff --git a/packages/generator/tests/search/snapshots/kt/Models.kt b/packages/generator/tests/search/snapshots/kt/Models.kt index bd3aba95..d9fc6405 100644 --- a/packages/generator/tests/search/snapshots/kt/Models.kt +++ b/packages/generator/tests/search/snapshots/kt/Models.kt @@ -1,7 +1,20 @@ package com.pachca.sdk +import java.time.OffsetDateTime +import java.time.format.DateTimeFormatter +import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +object OffsetDateTimeSerializer : KSerializer { + override val descriptor = PrimitiveSerialDescriptor("OffsetDateTime", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: OffsetDateTime) = encoder.encodeString(value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)) + override fun deserialize(decoder: Decoder): OffsetDateTime = OffsetDateTime.parse(decoder.decodeString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME) +} @Serializable enum class SearchSort(val value: String) { @@ -17,7 +30,7 @@ data class MessageSearchResult( @SerialName("chat_id") val chatId: Int, @SerialName("user_id") val userId: Int, val content: String, - @SerialName("created_at") val createdAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("created_at") val createdAt: OffsetDateTime, ) @Serializable diff --git a/packages/generator/tests/search/snapshots/kt/examples.json b/packages/generator/tests/search/snapshots/kt/examples.json index 8b3de7c0..64deed93 100644 --- a/packages/generator/tests/search/snapshots/kt/examples.json +++ b/packages/generator/tests/search/snapshots/kt/examples.json @@ -6,7 +6,7 @@ ] }, "SearchOperations_searchMessages": { - "usage": "val chatIds = listOf(123)\nval userIds = listOf(123)\nval response = client.search.searchMessages(query = \"example\", chatIds = chatIds, userIds = userIds, createdFrom = \"2024-01-01T00:00:00Z\", createdTo = \"2024-01-01T00:00:00Z\", sort = SearchSort.RELEVANCE, limit = 123, cursor = \"example\")", + "usage": "val chatIds = listOf(123)\nval userIds = listOf(123)\nval createdFrom = OffsetDateTime.parse(\"2024-01-01T00:00:00Z\")\nval createdTo = OffsetDateTime.parse(\"2024-01-01T00:00:00Z\")\nval response = client.search.searchMessages(query = \"example\", chatIds = chatIds, userIds = userIds, createdFrom = createdFrom, createdTo = createdTo, sort = SearchSort.RELEVANCE, limit = 123, cursor = \"example\")", "output": "SearchMessagesResponse(data: List, meta: SearchPaginationMeta)", "imports": [ "SearchSort" diff --git a/packages/generator/tests/search/snapshots/py/client.py b/packages/generator/tests/search/snapshots/py/client.py index b7f30dd4..50f1b5ff 100644 --- a/packages/generator/tests/search/snapshots/py/client.py +++ b/packages/generator/tests/search/snapshots/py/client.py @@ -21,16 +21,16 @@ async def search_messages( ) -> SearchMessagesResponse: query: list[tuple[str, str]] = [] query.append(("query", params.query)) - if params.chat_ids is not None: + if params is not None and params.chat_ids is not None: for v in params.chat_ids: query.append(("chat_ids[]", str(v))) - if params.user_ids is not None: + if params is not None and params.user_ids is not None: for v in params.user_ids: query.append(("user_ids[]", str(v))) if params is not None and params.created_from is not None: - query.append(("created_from", params.created_from)) + query.append(("created_from", params.created_from.isoformat())) if params is not None and params.created_to is not None: - query.append(("created_to", params.created_to)) + query.append(("created_to", params.created_to.isoformat())) if params is not None and params.sort is not None: query.append(("sort", params.sort)) if params is not None and params.limit is not None: @@ -64,9 +64,9 @@ async def search_messages_all( params.cursor = cursor response = await self.search_messages(params=params) items.extend(response.data) - cursor = response.meta.paginate.next_page if response.meta and response.meta.paginate else None - if not cursor: + if not response.data: break + cursor = response.meta.paginate.next_page return items diff --git a/packages/generator/tests/search/snapshots/py/examples.json b/packages/generator/tests/search/snapshots/py/examples.json index fc0f909b..94292fea 100644 --- a/packages/generator/tests/search/snapshots/py/examples.json +++ b/packages/generator/tests/search/snapshots/py/examples.json @@ -6,7 +6,7 @@ ] }, "SearchOperations_searchMessages": { - "usage": "params = SearchMessagesParams(\n query=\"example\",\n chat_ids=[123],\n user_ids=[123],\n created_from=\"2024-01-01T00:00:00Z\",\n created_to=\"2024-01-01T00:00:00Z\",\n sort=SearchSort.RELEVANCE,\n limit=123,\n cursor=\"example\"\n)\nresponse = await client.search.search_messages(params=params)", + "usage": "params = SearchMessagesParams(\n query=\"example\",\n chat_ids=[123],\n user_ids=[123],\n created_from=datetime.fromisoformat(\"2024-01-01T00:00:00Z\"),\n created_to=datetime.fromisoformat(\"2024-01-01T00:00:00Z\"),\n sort=SearchSort.RELEVANCE,\n limit=123,\n cursor=\"example\"\n)\nresponse = await client.search.search_messages(params=params)", "output": "SearchMessagesResponse(data: list[MessageSearchResult], meta: SearchPaginationMeta)", "imports": [ "SearchMessagesParams", diff --git a/packages/generator/tests/search/snapshots/py/models.py b/packages/generator/tests/search/snapshots/py/models.py index e12e1756..1397eab1 100644 --- a/packages/generator/tests/search/snapshots/py/models.py +++ b/packages/generator/tests/search/snapshots/py/models.py @@ -1,5 +1,6 @@ from __future__ import annotations +from datetime import datetime from dataclasses import dataclass from enum import StrEnum @@ -14,7 +15,7 @@ class MessageSearchResult: chat_id: int user_id: int content: str - created_at: str + created_at: datetime @dataclass @@ -38,8 +39,8 @@ class SearchMessagesParams: query: str chat_ids: list[int] | None = None user_ids: list[int] | None = None - created_from: str | None = None - created_to: str | None = None + created_from: datetime | None = None + created_to: datetime | None = None sort: SearchSort | None = None limit: int | None = None cursor: str | None = None diff --git a/packages/generator/tests/search/snapshots/py/utils.py b/packages/generator/tests/search/snapshots/py/utils.py index d1494873..9288f902 100644 --- a/packages/generator/tests/search/snapshots/py/utils.py +++ b/packages/generator/tests/search/snapshots/py/utils.py @@ -3,6 +3,7 @@ import dataclasses import keyword from dataclasses import asdict, fields +from datetime import datetime from typing import Type, TypeVar, get_args, get_origin, get_type_hints import httpx @@ -58,6 +59,16 @@ def deserialize(cls: Type[T], data: dict) -> T: item_tp = _resolve_list_item_type(hints[f.name]) if item_tp is not None and _is_dataclass_type(item_tp): v = [deserialize(item_tp, i) if isinstance(i, dict) else i for i in v] + elif isinstance(v, str): + hint = hints.get(f.name) + raw_hint = hint + if get_origin(hint) is not None: + for a in get_args(hint): + if a is not type(None): + raw_hint = a + break + if raw_hint is datetime: + v = datetime.fromisoformat(v) kwargs[k] = v return cls(**kwargs) @@ -70,6 +81,8 @@ def _strip_nones(val: object) -> object: } if isinstance(val, list): return [_strip_nones(v) for v in val] + if isinstance(val, datetime): + return val.isoformat() return val diff --git a/packages/generator/tests/search/snapshots/swift/Client.swift b/packages/generator/tests/search/snapshots/swift/Client.swift index 98fc752e..7bfa96fb 100644 --- a/packages/generator/tests/search/snapshots/swift/Client.swift +++ b/packages/generator/tests/search/snapshots/swift/Client.swift @@ -20,8 +20,8 @@ public struct SearchService { queryItems.append(URLQueryItem(name: "query", value: String(query))) if let chatIds { chatIds.forEach { queryItems.append(URLQueryItem(name: "chat_ids[]", value: String($0))) } } if let userIds { userIds.forEach { queryItems.append(URLQueryItem(name: "user_ids[]", value: String($0))) } } - if let createdFrom { queryItems.append(URLQueryItem(name: "created_from", value: createdFrom)) } - if let createdTo { queryItems.append(URLQueryItem(name: "created_to", value: createdTo)) } + if let createdFrom { queryItems.append(URLQueryItem(name: "created_from", value: String(createdFrom))) } + if let createdTo { queryItems.append(URLQueryItem(name: "created_to", value: String(createdTo))) } if let sort { queryItems.append(URLQueryItem(name: "sort", value: sort.rawValue)) } if let limit { queryItems.append(URLQueryItem(name: "limit", value: String(limit))) } if let cursor { queryItems.append(URLQueryItem(name: "cursor", value: String(cursor))) } @@ -46,8 +46,9 @@ public struct SearchService { repeat { let response = try await searchMessages(query: query, chatIds: chatIds, userIds: userIds, createdFrom: createdFrom, createdTo: createdTo, sort: sort, limit: limit, cursor: cursor) items.append(contentsOf: response.data) - cursor = response.meta?.paginate?.nextPage - } while cursor != nil + if response.data.isEmpty { break } + cursor = response.meta.paginate.nextPage + } while true return items } } diff --git a/packages/generator/tests/search/snapshots/swift/Models.swift b/packages/generator/tests/search/snapshots/swift/Models.swift index 9b78a3d9..4604fc82 100644 --- a/packages/generator/tests/search/snapshots/swift/Models.swift +++ b/packages/generator/tests/search/snapshots/swift/Models.swift @@ -15,9 +15,9 @@ public struct MessageSearchResult: Codable { public let chatId: Int public let userId: Int public let content: String - public let createdAt: Date + public let createdAt: String - public init(id: Int, chatId: Int, userId: Int, content: String, createdAt: Date) { + public init(id: Int, chatId: Int, userId: Int, content: String, createdAt: String) { self.id = id self.chatId = chatId self.userId = userId diff --git a/packages/generator/tests/search/snapshots/swift/Utils.swift b/packages/generator/tests/search/snapshots/swift/Utils.swift index d65d2a00..8df90b02 100644 --- a/packages/generator/tests/search/snapshots/swift/Utils.swift +++ b/packages/generator/tests/search/snapshots/swift/Utils.swift @@ -5,13 +5,11 @@ import FoundationNetworking let pachcaDecoder: JSONDecoder = { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 return decoder }() let pachcaEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 return encoder }() diff --git a/packages/generator/tests/search/snapshots/ts/client.ts b/packages/generator/tests/search/snapshots/ts/client.ts index aa0b0e4a..9a1b5848 100644 --- a/packages/generator/tests/search/snapshots/ts/client.ts +++ b/packages/generator/tests/search/snapshots/ts/client.ts @@ -46,8 +46,9 @@ class SearchService { do { const response = await this.searchMessages({ ...params, cursor } as SearchMessagesParams); items.push(...response.data); - cursor = response.meta?.paginate?.nextPage; - } while (cursor); + if (response.data.length === 0) break; + cursor = response.meta.paginate.nextPage; + } while (true); return items; } } diff --git a/packages/generator/tests/unions/snapshots/cs/Utils.cs b/packages/generator/tests/unions/snapshots/cs/Utils.cs index 88d4be58..2cf57544 100644 --- a/packages/generator/tests/unions/snapshots/cs/Utils.cs +++ b/packages/generator/tests/unions/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/packages/generator/tests/unions/snapshots/swift/Utils.swift b/packages/generator/tests/unions/snapshots/swift/Utils.swift index d65d2a00..8df90b02 100644 --- a/packages/generator/tests/unions/snapshots/swift/Utils.swift +++ b/packages/generator/tests/unions/snapshots/swift/Utils.swift @@ -5,13 +5,11 @@ import FoundationNetworking let pachcaDecoder: JSONDecoder = { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 return decoder }() let pachcaEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 return encoder }() diff --git a/packages/generator/tests/unwrap/snapshots/cs/Utils.cs b/packages/generator/tests/unwrap/snapshots/cs/Utils.cs index 88d4be58..2cf57544 100644 --- a/packages/generator/tests/unwrap/snapshots/cs/Utils.cs +++ b/packages/generator/tests/unwrap/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/packages/generator/tests/unwrap/snapshots/go/client.go b/packages/generator/tests/unwrap/snapshots/go/client.go index 72b6405e..0a81614b 100644 --- a/packages/generator/tests/unwrap/snapshots/go/client.go +++ b/packages/generator/tests/unwrap/snapshots/go/client.go @@ -84,11 +84,15 @@ func (s *MembersService) AddMembers(ctx context.Context, id int32, memberIds []i return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -124,11 +128,15 @@ func (s *ChatsService) CreateChat(ctx context.Context, request ChatCreateRequest return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -148,11 +156,15 @@ func (s *ChatsService) ArchiveChat(ctx context.Context, id int32) error { return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } diff --git a/packages/generator/tests/unwrap/snapshots/kt/Models.kt b/packages/generator/tests/unwrap/snapshots/kt/Models.kt index 1c52fb77..617e45d2 100644 --- a/packages/generator/tests/unwrap/snapshots/kt/Models.kt +++ b/packages/generator/tests/unwrap/snapshots/kt/Models.kt @@ -1,7 +1,20 @@ package com.pachca.sdk +import java.time.OffsetDateTime +import java.time.format.DateTimeFormatter +import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +object OffsetDateTimeSerializer : KSerializer { + override val descriptor = PrimitiveSerialDescriptor("OffsetDateTime", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: OffsetDateTime) = encoder.encodeString(value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)) + override fun deserialize(decoder: Decoder): OffsetDateTime = OffsetDateTime.parse(decoder.decodeString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME) +} @Serializable data class AddMembersRequest( @@ -27,7 +40,7 @@ data class Chat( val name: String, @SerialName("is_channel") val isChannel: Boolean, @SerialName("is_public") val isPublic: Boolean, - @SerialName("created_at") val createdAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("created_at") val createdAt: OffsetDateTime, ) @Serializable diff --git a/packages/generator/tests/unwrap/snapshots/kt/examples.json b/packages/generator/tests/unwrap/snapshots/kt/examples.json index 33628842..0e88a673 100644 --- a/packages/generator/tests/unwrap/snapshots/kt/examples.json +++ b/packages/generator/tests/unwrap/snapshots/kt/examples.json @@ -10,7 +10,7 @@ }, "ChatOperations_createChat": { "usage": "val request = ChatCreateRequest(\n chat = ChatCreateRequestChat(\n name = \"example\",\n channel = true,\n public = true,\n memberIds = listOf(123)\n )\n)\nval response = client.chats.createChat(request = request)", - "output": "Chat(id: Int, name: String, isChannel: Boolean, isPublic: Boolean, createdAt: String)", + "output": "Chat(id: Int, name: String, isChannel: Boolean, isPublic: Boolean, createdAt: OffsetDateTime)", "imports": [ "ChatCreateRequest", "ChatCreateRequestChat" diff --git a/packages/generator/tests/unwrap/snapshots/py/examples.json b/packages/generator/tests/unwrap/snapshots/py/examples.json index dafafc08..d9ff7b21 100644 --- a/packages/generator/tests/unwrap/snapshots/py/examples.json +++ b/packages/generator/tests/unwrap/snapshots/py/examples.json @@ -10,7 +10,7 @@ }, "ChatOperations_createChat": { "usage": "request = ChatCreateRequest(\n chat=ChatCreateRequestChat(\n name=\"example\",\n channel=True,\n public=True,\n member_ids=[123]\n )\n)\nresponse = await client.chats.create_chat(request=request)", - "output": "Chat(id: int, name: str, is_channel: bool, is_public: bool, created_at: str)", + "output": "Chat(id: int, name: str, is_channel: bool, is_public: bool, created_at: datetime)", "imports": [ "ChatCreateRequest", "ChatCreateRequestChat" diff --git a/packages/generator/tests/unwrap/snapshots/py/models.py b/packages/generator/tests/unwrap/snapshots/py/models.py index e639028a..49aa7d67 100644 --- a/packages/generator/tests/unwrap/snapshots/py/models.py +++ b/packages/generator/tests/unwrap/snapshots/py/models.py @@ -1,5 +1,6 @@ from __future__ import annotations +from datetime import datetime from dataclasses import dataclass @dataclass @@ -26,7 +27,7 @@ class Chat: name: str is_channel: bool is_public: bool - created_at: str + created_at: datetime @dataclass diff --git a/packages/generator/tests/unwrap/snapshots/py/utils.py b/packages/generator/tests/unwrap/snapshots/py/utils.py index d1494873..9288f902 100644 --- a/packages/generator/tests/unwrap/snapshots/py/utils.py +++ b/packages/generator/tests/unwrap/snapshots/py/utils.py @@ -3,6 +3,7 @@ import dataclasses import keyword from dataclasses import asdict, fields +from datetime import datetime from typing import Type, TypeVar, get_args, get_origin, get_type_hints import httpx @@ -58,6 +59,16 @@ def deserialize(cls: Type[T], data: dict) -> T: item_tp = _resolve_list_item_type(hints[f.name]) if item_tp is not None and _is_dataclass_type(item_tp): v = [deserialize(item_tp, i) if isinstance(i, dict) else i for i in v] + elif isinstance(v, str): + hint = hints.get(f.name) + raw_hint = hint + if get_origin(hint) is not None: + for a in get_args(hint): + if a is not type(None): + raw_hint = a + break + if raw_hint is datetime: + v = datetime.fromisoformat(v) kwargs[k] = v return cls(**kwargs) @@ -70,6 +81,8 @@ def _strip_nones(val: object) -> object: } if isinstance(val, list): return [_strip_nones(v) for v in val] + if isinstance(val, datetime): + return val.isoformat() return val diff --git a/packages/generator/tests/unwrap/snapshots/swift/Models.swift b/packages/generator/tests/unwrap/snapshots/swift/Models.swift index b1437c90..a068a7de 100644 --- a/packages/generator/tests/unwrap/snapshots/swift/Models.swift +++ b/packages/generator/tests/unwrap/snapshots/swift/Models.swift @@ -49,9 +49,9 @@ public struct Chat: Codable { public let name: String public let isChannel: Bool public let isPublic: Bool - public let createdAt: Date + public let createdAt: String - public init(id: Int, name: String, isChannel: Bool, isPublic: Bool, createdAt: Date) { + public init(id: Int, name: String, isChannel: Bool, isPublic: Bool, createdAt: String) { self.id = id self.name = name self.isChannel = isChannel diff --git a/packages/generator/tests/unwrap/snapshots/swift/Utils.swift b/packages/generator/tests/unwrap/snapshots/swift/Utils.swift index d65d2a00..8df90b02 100644 --- a/packages/generator/tests/unwrap/snapshots/swift/Utils.swift +++ b/packages/generator/tests/unwrap/snapshots/swift/Utils.swift @@ -5,13 +5,11 @@ import FoundationNetworking let pachcaDecoder: JSONDecoder = { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 return decoder }() let pachcaEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 return encoder }() diff --git a/packages/generator/tests/upload/snapshots/cs/Utils.cs b/packages/generator/tests/upload/snapshots/cs/Utils.cs index 88d4be58..2cf57544 100644 --- a/packages/generator/tests/upload/snapshots/cs/Utils.cs +++ b/packages/generator/tests/upload/snapshots/cs/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/packages/generator/tests/upload/snapshots/go/client.go b/packages/generator/tests/upload/snapshots/go/client.go index e2d07fe2..5a586743 100644 --- a/packages/generator/tests/upload/snapshots/go/client.go +++ b/packages/generator/tests/upload/snapshots/go/client.go @@ -104,7 +104,9 @@ func (s *CommonService) UploadFile(ctx context.Context, directUrl string, reques return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: return fmt.Errorf("unexpected status code: %d", resp.StatusCode) @@ -132,7 +134,9 @@ func (s *CommonService) GetUploadParams(ctx context.Context) (*UploadParams, err return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) diff --git a/packages/generator/tests/upload/snapshots/py/utils.py b/packages/generator/tests/upload/snapshots/py/utils.py index d1494873..9288f902 100644 --- a/packages/generator/tests/upload/snapshots/py/utils.py +++ b/packages/generator/tests/upload/snapshots/py/utils.py @@ -3,6 +3,7 @@ import dataclasses import keyword from dataclasses import asdict, fields +from datetime import datetime from typing import Type, TypeVar, get_args, get_origin, get_type_hints import httpx @@ -58,6 +59,16 @@ def deserialize(cls: Type[T], data: dict) -> T: item_tp = _resolve_list_item_type(hints[f.name]) if item_tp is not None and _is_dataclass_type(item_tp): v = [deserialize(item_tp, i) if isinstance(i, dict) else i for i in v] + elif isinstance(v, str): + hint = hints.get(f.name) + raw_hint = hint + if get_origin(hint) is not None: + for a in get_args(hint): + if a is not type(None): + raw_hint = a + break + if raw_hint is datetime: + v = datetime.fromisoformat(v) kwargs[k] = v return cls(**kwargs) @@ -70,6 +81,8 @@ def _strip_nones(val: object) -> object: } if isinstance(val, list): return [_strip_nones(v) for v in val] + if isinstance(val, datetime): + return val.isoformat() return val diff --git a/packages/generator/tests/upload/snapshots/swift/Utils.swift b/packages/generator/tests/upload/snapshots/swift/Utils.swift index d65d2a00..8df90b02 100644 --- a/packages/generator/tests/upload/snapshots/swift/Utils.swift +++ b/packages/generator/tests/upload/snapshots/swift/Utils.swift @@ -5,13 +5,11 @@ import FoundationNetworking let pachcaDecoder: JSONDecoder = { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 return decoder }() let pachcaEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 return encoder }() diff --git a/packages/openapi-parser/package.json b/packages/openapi-parser/package.json index 3340dbbf..fc59433f 100644 --- a/packages/openapi-parser/package.json +++ b/packages/openapi-parser/package.json @@ -7,8 +7,9 @@ "types": "dist/index.d.ts", "exports": { ".": { + "types": "./dist/index.d.ts", "import": "./dist/index.js", - "types": "./dist/index.d.ts" + "default": "./dist/index.js" } }, "scripts": { diff --git a/packages/spec/examples.ts b/packages/spec/examples.ts new file mode 100644 index 00000000..db9c6fbf --- /dev/null +++ b/packages/spec/examples.ts @@ -0,0 +1,193 @@ +/** + * Shared example data for n8n node, docs, skills, and CLI. + * + * This file provides high-level preset data that is NOT derivable from OpenAPI schemas: + * form templates, validation rules, button presets, file upload examples. + * + * For automatic per-field examples (types, formats, heuristics), see: + * apps/docs/lib/openapi/example-generator.ts + */ + +export const EXAMPLES = { + /** Button examples — used in docs, skills, n8n node (default values) */ + buttons: [ + { + text: 'Approve', + action_type: 'url' as const, + url: 'https://example.com/approve', + }, + { + text: 'Reject', + action_type: 'action' as const, + action_name: 'reject_request', + }, + ], + + /** Form templates — used in n8n node (preset forms), docs, skills */ + formTemplates: { + timeoff_request: { + name: 'Time Off Request', + blocks: [ + { + type: 'input', + element: { type: 'datepicker', action_id: 'date_start' }, + label: { type: 'plain_text', text: 'Start date' }, + }, + { + type: 'input', + element: { type: 'datepicker', action_id: 'date_end' }, + label: { type: 'plain_text', text: 'End date' }, + }, + { + type: 'input', + element: { type: 'plain_text_input', action_id: 'reason' }, + label: { type: 'plain_text', text: 'Reason' }, + optional: true, + }, + ], + submit: { type: 'plain_text', text: 'Request' }, + }, + feedback_form: { + name: 'Feedback Form', + blocks: [ + { + type: 'input', + element: { + type: 'radio', + action_id: 'rating', + options: [ + { text: { type: 'plain_text', text: '1 — Poor' }, value: '1' }, + { text: { type: 'plain_text', text: '2 — Fair' }, value: '2' }, + { text: { type: 'plain_text', text: '3 — Good' }, value: '3' }, + { text: { type: 'plain_text', text: '4 — Great' }, value: '4' }, + { text: { type: 'plain_text', text: '5 — Excellent' }, value: '5' }, + ], + }, + label: { type: 'plain_text', text: 'Rating' }, + }, + { + type: 'input', + element: { type: 'plain_text_input', action_id: 'comment', multiline: true }, + label: { type: 'plain_text', text: 'Comment' }, + optional: true, + }, + ], + submit: { type: 'plain_text', text: 'Send' }, + }, + task_request: { + name: 'Task Request', + blocks: [ + { + type: 'input', + element: { type: 'plain_text_input', action_id: 'title' }, + label: { type: 'plain_text', text: 'Title' }, + }, + { + type: 'input', + element: { type: 'plain_text_input', action_id: 'description', multiline: true }, + label: { type: 'plain_text', text: 'Description' }, + }, + ], + submit: { type: 'plain_text', text: 'Create' }, + }, + survey_form: { + name: 'Employee Survey', + blocks: [ + { + type: 'input', + element: { + type: 'radio', + action_id: 'satisfaction', + options: [ + { text: { type: 'plain_text', text: '1' }, value: '1' }, + { text: { type: 'plain_text', text: '2' }, value: '2' }, + { text: { type: 'plain_text', text: '3' }, value: '3' }, + { text: { type: 'plain_text', text: '4' }, value: '4' }, + { text: { type: 'plain_text', text: '5' }, value: '5' }, + ], + }, + label: { type: 'plain_text', text: 'Overall satisfaction (1-5)' }, + }, + { + type: 'input', + element: { type: 'plain_text_input', action_id: 'suggestions', multiline: true }, + label: { type: 'plain_text', text: 'Suggestions' }, + optional: true, + }, + ], + submit: { type: 'plain_text', text: 'Submit' }, + }, + access_request: { + name: 'Access Request', + blocks: [ + { + type: 'input', + element: { type: 'plain_text_input', action_id: 'resource' }, + label: { type: 'plain_text', text: 'Resource name' }, + }, + { + type: 'input', + element: { type: 'plain_text_input', action_id: 'justification', multiline: true }, + label: { type: 'plain_text', text: 'Justification' }, + }, + ], + submit: { type: 'plain_text', text: 'Request access' }, + }, + }, + + /** Client-side form validation rules — n8n node */ + formValidationRules: { + timeoff_request: { + date_end: { + rule: 'after_field' as const, + field: 'date_start', + message: 'End date must be after start date', + }, + }, + feedback_form: { + comment: { + rule: 'min_length' as const, + value: 10, + message: 'Comment must be at least 10 characters', + }, + }, + task_request: { + title: { + rule: 'min_length' as const, + value: 5, + message: 'Title must be at least 5 characters', + }, + description: { + rule: 'min_length' as const, + value: 20, + message: 'Description must be at least 20 characters', + }, + }, + }, + + /** Upload response example — from POST /uploads */ + upload: { + direct_url: 'https://s3.amazonaws.com/...', + key: 'uploads/abc123/file.pdf', + }, + + /** Message with file attachments */ + messageWithFiles: { + content: 'Here is the report', + files: [{ key: 'uploads/abc123/file.pdf', name: 'report.pdf' }], + }, + + /** MIME types for file upload detection */ + mimeTypes: { + pdf: 'application/pdf', + jpg: 'image/jpeg', + jpeg: 'image/jpeg', + png: 'image/png', + gif: 'image/gif', + doc: 'application/msword', + docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + xls: 'application/vnd.ms-excel', + xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + zip: 'application/zip', + } as Record, +} as const; diff --git a/packages/spec/openapi.en.yaml b/packages/spec/openapi.en.yaml new file mode 100644 index 00000000..7a01a079 --- /dev/null +++ b/packages/spec/openapi.en.yaml @@ -0,0 +1,8540 @@ +openapi: 3.0.0 +info: + title: Pachca API + version: 1.0.0 +tags: + - name: Common + - name: Profile + - name: Users + - name: Group tags + - name: Chats + - name: Members + - name: Threads + - name: Messages + - name: Read members + - name: Reactions + - name: Link Previews + - name: Search + - name: Tasks + - name: Views + - name: Bots + - name: Security +paths: + /audit_events: + get: + operationId: SecurityOperations_getAuditEvents + description: |- + Audit event log + + Retrieve event logs based on the specified filters. + parameters: + - name: start_time + in: query + required: false + description: Start timestamp (inclusive) + schema: + type: string + format: date-time + example: '2025-05-01T09:11:00Z' + example: '2025-05-01T09:11:00Z' + explode: false + - name: end_time + in: query + required: false + description: End timestamp (exclusive) + schema: + type: string + format: date-time + example: '2025-05-02T09:11:00Z' + example: '2025-05-02T09:11:00Z' + explode: false + - name: event_key + in: query + required: false + description: Filter by specific event type + schema: + $ref: '#/components/schemas/AuditEventKey' + example: user_login + example: user_login + explode: false + - name: actor_id + in: query + required: false + description: ID of the user who performed the action + schema: + type: string + example: '98765' + example: '98765' + explode: false + - name: actor_type + in: query + required: false + description: Actor type + schema: + type: string + example: User + example: User + explode: false + - name: entity_id + in: query + required: false + description: ID of the affected entity + schema: + type: string + example: '98765' + example: '98765' + explode: false + - name: entity_type + in: query + required: false + description: Entity type + schema: + type: string + example: User + example: User + explode: false + - name: limit + in: query + required: false + description: Number of records to return + schema: + type: integer + format: int32 + maximum: 50 + example: 1 + default: 50 + example: 1 + explode: false + - name: cursor + in: query + required: false + description: Pagination cursor (from `meta.paginate.next_page`) + schema: + type: string + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + explode: false + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + - meta + properties: + data: + type: array + items: + $ref: '#/components/schemas/AuditEvent' + meta: + $ref: '#/components/schemas/PaginationMeta' + description: Response wrapper with data and pagination + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Security + x-paginated: true + x-requirements: + scope: audit_events:read + plan: corporation + /bots/{id}: + put: + operationId: BotOperations_updateBot + description: >- + Edit bot + + + Edit a bot's settings. + + + To edit a bot you need to know its `user_id` and specify it in the request `URL`. All editable bot parameters + are specified in the request body. You can find the bot's `user_id` in the bot settings under the "API" tab. + + + You cannot edit a bot whose settings are not accessible to you (the "Who can edit bot settings" field is located + on the "General" tab in the bot settings). + parameters: + - name: id + in: path + required: true + description: Bot ID + schema: + type: integer + format: int32 + example: 1738816 + example: 1738816 + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/BotResponse' + description: Response wrapper with data + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Bots + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BotUpdateRequest' + x-requirements: + scope: bots:write + /chats: + post: + operationId: ChatOperations_createChat + description: |- + New chat + + Create a new chat. + + To create a one-on-one direct message with a user, use the [New message](POST /messages) method. + + When creating a chat, you automatically become a member. + parameters: [] + responses: + '201': + description: The request has succeeded and a new resource has been created as a result. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Chat' + description: Response wrapper with data + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Chats + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ChatCreateRequest' + x-requirements: + scope: chats:create + get: + operationId: ChatOperations_listChats + description: |- + List chats + + Retrieve a list of chats based on the specified parameters. + parameters: + - name: sort + in: query + required: false + description: Sort field + schema: + allOf: + - $ref: '#/components/schemas/ChatSortField' + default: id + example: id + explode: false + - name: order + in: query + required: false + description: Sort direction + schema: + allOf: + - $ref: '#/components/schemas/SortOrder' + default: desc + example: desc + explode: false + - name: availability + in: query + required: false + description: Parameter that controls chat availability and filtering for the user + schema: + allOf: + - $ref: '#/components/schemas/ChatAvailability' + default: is_member + example: is_member + explode: false + - name: last_message_at_after + in: query + required: false + 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). + schema: + type: string + format: date-time + example: '2025-01-01T00:00:00.000Z' + example: '2025-01-01T00:00:00.000Z' + explode: false + - name: last_message_at_before + in: query + required: false + 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). + schema: + type: string + format: date-time + example: '2025-02-01T00:00:00.000Z' + example: '2025-02-01T00:00:00.000Z' + explode: false + - name: personal + in: query + required: false + description: Filter by direct and group chats. If not specified, all chats are returned. + schema: + type: boolean + example: false + example: false + explode: false + - name: limit + in: query + required: false + description: Number of records to return per request + schema: + type: integer + format: int32 + minimum: 1 + maximum: 50 + example: 1 + default: 50 + example: 1 + explode: false + - name: cursor + in: query + required: false + description: Pagination cursor (from `meta.paginate.next_page`) + schema: + type: string + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + explode: false + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + - meta + properties: + data: + type: array + items: + $ref: '#/components/schemas/Chat' + meta: + $ref: '#/components/schemas/PaginationMeta' + description: Response wrapper with data and pagination + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Chats + x-paginated: true + x-requirements: + scope: chats:read + /chats/exports: + post: + operationId: ExportOperations_requestExport + description: |- + Export messages + + Request a message export for the specified time period. + parameters: [] + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/ApiError' + - $ref: '#/components/schemas/OAuthError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Common + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExportRequest' + x-requirements: + scope: chat_exports:write + plan: corporation + /chats/exports/{id}: + get: + operationId: ExportOperations_downloadExport + description: >- + Download export archive + + + Download a completed message export archive. + + + To download the archive you need to know its `id` and specify it in the request `URL`. + + + The server will respond with `302 Found` and a `Location` header containing a temporary download link. Most HTTP + clients automatically follow the redirect and download the file. + parameters: + - name: id + in: path + required: true + description: Export ID + schema: + type: integer + format: int32 + example: 22322 + example: 22322 + responses: + '302': + description: Redirection + headers: + location: + required: true + schema: + type: string + format: uri + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/OAuthError' + - anyOf: + - $ref: '#/components/schemas/ApiError' + - $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Common + x-requirements: + scope: chat_exports:read + plan: corporation + /chats/{id}: + get: + operationId: ChatOperations_getChat + description: |- + Chat info + + Retrieve information about a chat. + + To get a chat you need to know its `id` and specify it in the request `URL`. + parameters: + - name: id + in: path + required: true + description: Chat ID + schema: + type: integer + format: int32 + example: 334 + example: 334 + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Chat' + description: Response wrapper with data + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Chats + x-requirements: + scope: chats:read + put: + operationId: ChatOperations_updateChat + description: >- + Update chat + + + Update chat parameters. + + + To update a chat you need to know its `id` and specify it in the `URL`. All updatable fields are passed in the + request body. + parameters: + - name: id + in: path + required: true + description: Chat ID + schema: + type: integer + format: int32 + example: 334 + example: 334 + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Chat' + description: Response wrapper with data + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Chats + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ChatUpdateRequest' + x-requirements: + scope: chats:update + /chats/{id}/archive: + put: + operationId: ChatOperations_archiveChat + description: |- + Archive chat + + Archive a chat. + + To archive a chat you need to know its `id` and specify it in the request `URL`. + parameters: + - name: id + in: path + required: true + description: Chat ID + schema: + type: integer + format: int32 + example: 334 + example: 334 + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Chats + x-requirements: + scope: chats:archive + /chats/{id}/group_tags: + post: + operationId: ChatMemberOperations_addTags + description: >- + Add tags + + + Add tags to the member list of a conversation or channel. + + + After adding a tag, all its members automatically become members of the chat. The tag and chat member lists are + synchronized automatically: when a new member is added to the tag, they immediately appear in the chat; when + removed from the tag, they are removed from the chat. + parameters: + - name: id + in: path + required: true + description: Chat ID + schema: + type: integer + format: int32 + example: 334 + example: 334 + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Members + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AddTagsRequest' + x-requirements: + scope: chat_members:write + /chats/{id}/group_tags/{tag_id}: + delete: + operationId: ChatMemberOperations_removeTag + description: |- + Remove tag + + Remove a tag from the member list of a conversation or channel. + + To remove a tag you need to know its `id` and specify it in the request `URL`. + parameters: + - name: id + in: path + required: true + description: Chat ID + schema: + type: integer + format: int32 + example: 334 + example: 334 + - name: tag_id + in: path + required: true + description: Tag ID + schema: + type: integer + format: int32 + example: 86 + example: 86 + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Members + x-requirements: + scope: chat_members:write + /chats/{id}/leave: + delete: + operationId: ChatMemberOperations_leaveChat + description: |- + Leave conversation or channel + + Leave a conversation or channel on your own. + parameters: + - name: id + in: path + required: true + description: Chat ID + schema: + type: integer + format: int32 + example: 334 + example: 334 + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Members + x-requirements: + scope: chats:leave + /chats/{id}/members: + get: + operationId: ChatMemberOperations_listMembers + description: >- + List chat members + + + Retrieve the current list of chat members. + + + The workspace owner can retrieve the member list of any chat in the workspace. Admins and bots can only retrieve + the member list of chats they belong to (or that are public). + parameters: + - name: id + in: path + required: true + description: Chat ID + schema: + type: integer + format: int32 + example: 334 + example: 334 + - name: role + in: query + required: false + description: Role in the chat + schema: + allOf: + - $ref: '#/components/schemas/ChatMemberRoleFilter' + default: all + example: all + explode: false + - name: limit + in: query + required: false + description: Number of records to return per request + schema: + type: integer + format: int32 + minimum: 1 + maximum: 50 + example: 1 + default: 50 + example: 1 + explode: false + - name: cursor + in: query + required: false + description: Pagination cursor (from `meta.paginate.next_page`) + schema: + type: string + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + explode: false + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + - meta + properties: + data: + type: array + items: + $ref: '#/components/schemas/User' + meta: + $ref: '#/components/schemas/PaginationMeta' + description: Response wrapper with data and pagination + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Members + x-paginated: true + x-requirements: + scope: chat_members:read + post: + operationId: ChatMemberOperations_addMembers + description: |- + Add users + + Add users to the member list of a conversation, channel, or thread. + parameters: + - name: id + in: path + required: true + description: Chat ID (conversation, channel, or thread chat) + schema: + type: integer + format: int32 + example: 334 + example: 334 + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Members + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AddMembersRequest' + x-requirements: + scope: chat_members:write + /chats/{id}/members/{user_id}: + delete: + operationId: ChatMemberOperations_removeMember + description: >- + Remove user + + + Remove a user from the member list of a conversation or channel. + + + If the user is the chat owner, they cannot be removed. They can only leave the chat on their own using the + [Leave conversation or channel](DELETE /chats/{id}/leave) method. + parameters: + - name: id + in: path + required: true + description: Chat ID + schema: + type: integer + format: int32 + example: 334 + example: 334 + - name: user_id + in: path + required: true + description: User ID + schema: + type: integer + format: int32 + example: 186 + example: 186 + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Members + x-requirements: + scope: chat_members:write + put: + operationId: ChatMemberOperations_updateMemberRole + description: >- + Edit role + + + Edit a user's or bot's role in a conversation or channel. + + + To edit a role in a conversation or channel you need to know the `id` of the chat and the user (or bot) and + specify them in the request `URL`. All editable role parameters are specified in the request body. + + + The chat owner's role cannot be changed. They always have Admin privileges in the chat. + parameters: + - name: id + in: path + required: true + description: Chat ID + schema: + type: integer + format: int32 + example: 334 + example: 334 + - name: user_id + in: path + required: true + description: User ID + schema: + type: integer + format: int32 + example: 186 + example: 186 + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Members + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateMemberRoleRequest' + x-requirements: + scope: chat_members:write + /chats/{id}/unarchive: + put: + operationId: ChatOperations_unarchiveChat + description: |- + Unarchive chat + + Restore a chat from the archive. + + To unarchive a chat you need to know its `id` and specify it in the request `URL`. + parameters: + - name: id + in: path + required: true + description: Chat ID + schema: + type: integer + format: int32 + example: 334 + example: 334 + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Chats + x-requirements: + scope: chats:archive + /custom_properties: + get: + operationId: CommonOperations_listProperties + description: >- + List custom properties + + + Working with "File" type custom properties is currently unavailable. + + + Retrieve the current list of custom properties for employees and tasks in your workspace. + + + By default, all entities in your workspace only have basic fields. However, your workspace administrator can + add, edit, and delete custom properties. If you use custom properties that are no longer current (deleted or + non-existent) when creating employees (or tasks), you will receive an error. + parameters: + - name: entity_type + in: query + required: true + description: Entity type + schema: + $ref: '#/components/schemas/SearchEntityType' + example: User + example: User + explode: false + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: '#/components/schemas/CustomPropertyDefinition' + description: Response wrapper with data + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Common + x-requirements: + scope: custom_properties:read + /direct_url: + post: + operationId: DirectUploadOperations_uploadFile + description: >- + Upload file + + + Upload a file to the server using `multipart/form-data` format. Upload parameters are obtained via the [Get + signature, key and other parameters](POST /uploads) method. + parameters: [] + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + tags: + - Common + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/FileUploadRequest' + x-external-url: directUrl + x-requirements: + auth: false + /group_tags: + post: + operationId: GroupTagOperations_createTag + description: |- + New tag + + Create a new tag. + parameters: [] + responses: + '201': + description: The request has succeeded and a new resource has been created as a result. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/GroupTag' + description: Response wrapper with data + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Group tags + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GroupTagRequest' + x-requirements: + scope: group_tags:write + get: + operationId: GroupTagOperations_listTags + description: |- + List employee tags + + Retrieve the current list of employee tags. Tag names are unique within the workspace. + parameters: + - name: names + in: query + required: false + description: Array of tag names to filter by + schema: + $ref: '#/components/schemas/TagNamesFilter' + example: + - Design + - Product + example: + - Design + - Product + - name: limit + in: query + required: false + description: Number of records to return per request + schema: + type: integer + format: int32 + minimum: 1 + maximum: 50 + example: 1 + default: 50 + example: 1 + explode: false + - name: cursor + in: query + required: false + description: Pagination cursor (from `meta.paginate.next_page`) + schema: + type: string + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + explode: false + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + - meta + properties: + data: + type: array + items: + $ref: '#/components/schemas/GroupTag' + meta: + $ref: '#/components/schemas/PaginationMeta' + description: Response wrapper with data and pagination + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Group tags + x-paginated: true + x-requirements: + scope: group_tags:read + /group_tags/{id}: + get: + operationId: GroupTagOperations_getTag + description: |- + Tag info + + Retrieve information about a tag. Tag names are unique within the workspace. + + To get a tag you need to know its `id` and specify it in the request `URL`. + parameters: + - name: id + in: path + required: true + description: Tag ID + schema: + type: integer + format: int32 + example: 9111 + example: 9111 + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/GroupTag' + description: Response wrapper with data + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Group tags + x-requirements: + scope: group_tags:read + put: + operationId: GroupTagOperations_updateTag + description: >- + Edit tag + + + Edit a tag. + + + To edit a tag you need to know its `id` and specify it in the request `URL`. All editable tag parameters are + specified in the request body. + parameters: + - name: id + in: path + required: true + description: Tag ID + schema: + type: integer + format: int32 + example: 9111 + example: 9111 + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/GroupTag' + description: Response wrapper with data + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Group tags + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GroupTagRequest' + x-requirements: + scope: group_tags:write + delete: + operationId: GroupTagOperations_deleteTag + description: |- + Delete tag + + Delete a tag. + + To delete a tag you need to know its `id` and specify it in the request `URL`. + parameters: + - name: id + in: path + required: true + description: Tag ID + schema: + type: integer + format: int32 + example: 9111 + example: 9111 + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Group tags + x-requirements: + scope: group_tags:write + /group_tags/{id}/users: + get: + operationId: GroupTagOperations_getTagUsers + description: |- + List tag employees + + Retrieve the current list of employees in a tag. + parameters: + - name: id + in: path + required: true + description: Tag ID + schema: + type: integer + format: int32 + example: 9111 + example: 9111 + - name: limit + in: query + required: false + description: Number of records to return per request + schema: + type: integer + format: int32 + minimum: 1 + maximum: 50 + example: 1 + default: 50 + example: 1 + explode: false + - name: cursor + in: query + required: false + description: Pagination cursor (from `meta.paginate.next_page`) + schema: + type: string + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + explode: false + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + - meta + properties: + data: + type: array + items: + $ref: '#/components/schemas/User' + meta: + $ref: '#/components/schemas/PaginationMeta' + description: Response wrapper with data and pagination + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Group tags + x-paginated: true + x-requirements: + scope: group_tags:read + /messages: + post: + operationId: MessageOperations_createMessage + description: >- + New message + + + Send a message to a conversation or channel, a direct message to a user, or a comment to a thread. + + + When using `entity_type: "discussion"` (or simply without specifying `entity_type`), any `chat_id` can be passed + in the `entity_id` field. This means you can send a message knowing only the chat ID. Additionally, you can send + a message to a thread by its ID or a direct message by the user's ID. + + + To send a direct message to a user, you do not need to create a chat. Simply specify `entity_type: "user"` and + the user's ID. A chat will be created automatically if there has been no prior conversation between you. Only + one direct chat can exist between two users. + parameters: [] + responses: + '201': + description: The request has succeeded and a new resource has been created as a result. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Message' + description: Response wrapper with data + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Messages + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/MessageCreateRequest' + x-requirements: + scope: messages:create + get: + operationId: ChatMessageOperations_listChatMessages + description: >- + List chat messages + + + Retrieve a list of messages from conversations, channels, threads, and direct messages. + + + To retrieve messages you need to know the `chat_id` of the required conversation, channel, thread, or direct + message, and specify it in the request `URL`. Messages are returned in descending order by send date (i.e., the + most recent messages come first). To retrieve earlier messages, use the `limit` and `cursor` parameters. + parameters: + - name: chat_id + in: query + required: true + description: Chat ID (conversation, channel, direct message, or thread chat) + schema: + type: integer + format: int32 + example: 198 + example: 198 + explode: false + - name: sort + in: query + required: false + description: Sort field + schema: + allOf: + - $ref: '#/components/schemas/MessageSortField' + default: id + example: id + explode: false + - name: order + in: query + required: false + description: Sort direction + schema: + allOf: + - $ref: '#/components/schemas/SortOrder' + default: desc + example: desc + explode: false + - name: limit + in: query + required: false + description: Number of records to return per request + schema: + type: integer + format: int32 + minimum: 1 + maximum: 50 + example: 1 + default: 50 + example: 1 + explode: false + - name: cursor + in: query + required: false + description: Pagination cursor (from `meta.paginate.next_page`) + schema: + type: string + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + explode: false + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + - meta + properties: + data: + type: array + items: + $ref: '#/components/schemas/Message' + meta: + $ref: '#/components/schemas/PaginationMeta' + description: Response wrapper with data and pagination + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Messages + x-paginated: true + x-requirements: + scope: messages:read + /messages/{id}: + get: + operationId: MessageOperations_getMessage + description: |- + Message info + + Retrieve information about a message. + + To get a message you need to know its `id` and specify it in the request `URL`. + parameters: + - name: id + in: path + required: true + description: Message ID + schema: + type: integer + format: int32 + example: 194275 + example: 194275 + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Message' + description: Response wrapper with data + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Messages + x-requirements: + scope: messages:read + put: + operationId: MessageOperations_updateMessage + description: >- + Edit message + + + Edit a message or comment. + + + To edit a message you need to know its `id` and specify it in the request `URL`. All editable message parameters + are specified in the request body. + parameters: + - name: id + in: path + required: true + description: Message ID + schema: + type: integer + format: int32 + example: 194275 + example: 194275 + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Message' + description: Response wrapper with data + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Messages + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/MessageUpdateRequest' + x-requirements: + scope: messages:update + delete: + operationId: MessageOperations_deleteMessage + description: >- + Delete message + + + Delete a message. + + + Message deletion is available to the sender, admins, and editors in the chat. In direct messages, both users are + editors. There are no time restrictions on message deletion. + + + To delete a message you need to know its `id` and specify it in the request `URL`. + parameters: + - name: id + in: path + required: true + description: Message ID + schema: + type: integer + format: int32 + example: 194275 + example: 194275 + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Messages + x-requirements: + scope: messages:delete + /messages/{id}/link_previews: + post: + operationId: LinkPreviewOperations_createLinkPreviews + description: |- + Unfurl (link previews) + + Create link previews in messages. Only available for Unfurl bots. + parameters: + - name: id + in: path + required: true + description: Message ID + schema: + type: integer + format: int32 + example: 194275 + example: 194275 + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Link Previews + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LinkPreviewsRequest' + x-requirements: + scope: link_previews:write + /messages/{id}/pin: + post: + operationId: MessageOperations_pinMessage + description: |- + Pin message + + Pin a message in a chat. + + To pin a message you need to know the message `id` and specify it in the request `URL`. + parameters: + - name: id + in: path + required: true + description: Message ID + schema: + type: integer + format: int32 + example: 194275 + example: 194275 + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + tags: + - Messages + x-requirements: + scope: pins:write + delete: + operationId: MessageOperations_unpinMessage + description: |- + Unpin message + + Unpin a message from a chat. + + To unpin a message you need to know the message `id` and specify it in the request `URL`. + parameters: + - name: id + in: path + required: true + description: Message ID + schema: + type: integer + format: int32 + example: 194275 + example: 194275 + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Messages + x-requirements: + scope: pins:write + /messages/{id}/reactions: + post: + operationId: ReactionOperations_addReaction + description: >- + Add reaction + + + Add a reaction to a message. + + + To add a reaction you need to know the message `id` and specify it in the request `URL`. Message reactions are + sent as `Emoji` characters. If the user has already added the same reaction, it will not be duplicated. To + remove a reaction, use the [Delete reaction](DELETE /messages/{id}/reactions) method. + + + **Reaction limits:** + + + - Each user can add no more than **20 unique** reactions + + - A message can have no more than **30 unique** reactions + + - The total number of reactions on a message cannot exceed **1000** + parameters: + - name: id + in: path + required: true + description: Message ID + schema: + type: integer + format: int32 + example: 7231942 + example: 7231942 + responses: + '201': + description: The request has succeeded and a new resource has been created as a result. + content: + application/json: + schema: + $ref: '#/components/schemas/Reaction' + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Reactions + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ReactionRequest' + x-requirements: + scope: reactions:write + delete: + operationId: ReactionOperations_removeReaction + description: >- + Delete reaction + + + Remove a reaction from a message. + + + To remove a reaction you need to know the message `id` and specify it in the request `URL`. Message reactions + are stored as `Emoji` characters. + + + You can only remove reactions that were added by the authenticated user. + parameters: + - name: id + in: path + required: true + description: Message ID + schema: + type: integer + format: int32 + example: 7231942 + example: 7231942 + - name: code + in: query + required: true + description: Emoji character of the reaction + schema: + type: string + example: 👍 + example: 👍 + explode: false + - name: name + in: query + required: false + description: Text name of the emoji (used for custom emoji) + schema: + type: string + example: ':+1:' + example: ':+1:' + explode: false + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Reactions + x-requirements: + scope: reactions:write + get: + operationId: ReactionOperations_listReactions + description: |- + List reactions + + Retrieve the current list of reactions on a message. + parameters: + - name: id + in: path + required: true + description: Message ID + schema: + type: integer + format: int32 + example: 194275 + example: 194275 + - name: limit + in: query + required: false + description: Number of records to return per request + schema: + type: integer + format: int32 + minimum: 1 + maximum: 50 + example: 1 + default: 50 + example: 1 + explode: false + - name: cursor + in: query + required: false + description: Pagination cursor (from `meta.paginate.next_page`) + schema: + type: string + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + explode: false + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + - meta + properties: + data: + type: array + items: + $ref: '#/components/schemas/Reaction' + meta: + $ref: '#/components/schemas/PaginationMeta' + description: Response wrapper with data and pagination + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Reactions + x-paginated: true + x-requirements: + scope: reactions:read + /messages/{id}/read_member_ids: + get: + operationId: ReadMemberOperations_listReadMembers + description: |- + List users who read the message + + Retrieve the current list of users who have read the message. + parameters: + - name: id + in: path + required: true + description: Message ID + schema: + type: integer + format: int32 + example: 194275 + example: 194275 + - name: limit + in: query + required: false + description: Number of records to return per request + schema: + type: integer + format: int32 + minimum: 1 + maximum: 300 + example: 300 + default: 300 + example: 300 + explode: false + - name: cursor + in: query + required: false + description: Pagination cursor (from `meta.paginate.next_page`) + schema: + type: string + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + explode: false + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + - meta + properties: + data: + type: array + items: + type: integer + format: int32 + meta: + $ref: '#/components/schemas/PaginationMeta' + description: Response wrapper with data and pagination + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Read members + x-paginated: true + x-requirements: + scope: messages:read + /messages/{id}/thread: + post: + operationId: ThreadOperations_createThread + description: >- + New thread + + + Create a new thread on a message. + + + If a thread has already been created for the message, the response will return information about the previously + created thread. + parameters: + - name: id + in: path + required: true + description: Message ID + schema: + type: integer + format: int32 + example: 154332686 + example: 154332686 + responses: + '201': + description: The request has succeeded and a new resource has been created as a result. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Thread' + description: Response wrapper with data + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Threads + x-requirements: + scope: threads:create + /oauth/token/info: + get: + operationId: OAuthOperations_getTokenInfo + description: >- + Token info + + + Retrieve information about the current OAuth token, including its scopes, creation date, and last usage date. + The token in the response is masked — only the first 8 and last 4 characters are visible. + parameters: [] + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/AccessTokenInfo' + description: Response wrapper with data + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + tags: + - Profile + /profile: + get: + operationId: ProfileOperations_getProfile + description: |- + Profile info + + Retrieve information about your own profile. + parameters: [] + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/User' + description: Response wrapper with data + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + tags: + - Profile + x-requirements: + scope: profile:read + /profile/avatar: + put: + operationId: ProfileAvatarOperations_updateProfileAvatar + description: |- + Upload avatar + + Upload or update your profile avatar. The file is sent in `multipart/form-data` format. + parameters: [] + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/AvatarData' + description: Response wrapper with data + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/ApiError' + - $ref: '#/components/schemas/OAuthError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Profile + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + image: + type: string + format: binary + description: Avatar image file + required: + - image + x-requirements: + scope: profile_avatar:write + delete: + operationId: ProfileAvatarOperations_deleteProfileAvatar + description: |- + Delete avatar + + Delete your profile avatar. + parameters: [] + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/ApiError' + - $ref: '#/components/schemas/OAuthError' + tags: + - Profile + x-requirements: + scope: profile_avatar:write + /profile/status: + get: + operationId: ProfileOperations_getStatus + description: |- + Current status + + Retrieve information about your current status. + parameters: [] + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + type: object + allOf: + - $ref: '#/components/schemas/UserStatus' + nullable: true + description: Response wrapper with data + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + tags: + - Profile + x-requirements: + scope: profile_status:read + put: + operationId: ProfileOperations_updateStatus + description: |- + New status + + Set a new status for yourself. + parameters: [] + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/UserStatus' + description: Response wrapper with data + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Profile + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/StatusUpdateRequest' + x-requirements: + scope: profile_status:write + delete: + operationId: ProfileOperations_deleteStatus + description: |- + Delete status + + Delete your current status. + parameters: [] + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + tags: + - Profile + x-requirements: + scope: profile_status:write + /search/chats: + get: + operationId: SearchOperations_searchChats + description: |- + Search chats + + Full-text search for channels and conversations. + parameters: + - name: query + in: query + required: false + description: Search query text + schema: + type: string + example: Development + example: Development + explode: false + - name: limit + in: query + required: false + description: Number of results to return per request + schema: + type: integer + format: int32 + maximum: 100 + example: 10 + default: 100 + example: 10 + explode: false + - name: cursor + in: query + required: false + description: Pagination cursor (from `meta.paginate.next_page`) + schema: + type: string + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + explode: false + - name: order + in: query + required: false + description: Sort direction + schema: + $ref: '#/components/schemas/SortOrder' + example: desc + example: desc + explode: false + - name: created_from + in: query + required: false + description: Filter by creation date (from) + schema: + type: string + format: date-time + example: '2025-01-01T00:00:00.000Z' + example: '2025-01-01T00:00:00.000Z' + explode: false + - name: created_to + in: query + required: false + description: Filter by creation date (to) + schema: + type: string + format: date-time + example: '2025-02-01T00:00:00.000Z' + example: '2025-02-01T00:00:00.000Z' + explode: false + - name: active + in: query + required: false + description: Filter by chat activity + schema: + type: boolean + example: true + example: true + explode: false + - name: chat_subtype + in: query + required: false + description: Filter by chat type + schema: + $ref: '#/components/schemas/ChatSubtype' + example: discussion + example: discussion + explode: false + - name: personal + in: query + required: false + description: Filter by direct chats + schema: + type: boolean + example: false + example: false + explode: false + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + - meta + properties: + data: + type: array + items: + $ref: '#/components/schemas/Chat' + meta: + $ref: '#/components/schemas/SearchPaginationMeta' + description: Search results response wrapper with data and pagination + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + tags: + - Search + x-paginated: true + x-requirements: + scope: search:chats + /search/messages: + get: + operationId: SearchOperations_searchMessages + description: |- + Search messages + + Full-text search for messages. + parameters: + - name: query + in: query + required: false + description: Search query text + schema: + type: string + example: t-shirts + example: t-shirts + explode: false + - name: limit + in: query + required: false + description: Number of results to return per request + schema: + type: integer + format: int32 + maximum: 200 + example: 10 + default: 200 + example: 10 + explode: false + - name: cursor + in: query + required: false + description: Pagination cursor (from `meta.paginate.next_page`) + schema: + type: string + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + explode: false + - name: order + in: query + required: false + description: Sort direction + schema: + $ref: '#/components/schemas/SortOrder' + example: desc + example: desc + explode: false + - name: created_from + in: query + required: false + description: Filter by creation date (from) + schema: + type: string + format: date-time + example: '2025-01-01T00:00:00.000Z' + example: '2025-01-01T00:00:00.000Z' + explode: false + - name: created_to + in: query + required: false + description: Filter by creation date (to) + schema: + type: string + format: date-time + example: '2025-02-01T00:00:00.000Z' + example: '2025-02-01T00:00:00.000Z' + explode: false + - name: chat_ids + in: query + required: false + description: Filter by chat IDs + schema: + type: array + items: + type: integer + format: int32 + example: + - 198 + - 334 + example: + - 198 + - 334 + explode: false + - name: user_ids + in: query + required: false + description: Filter by message author IDs + schema: + type: array + items: + type: integer + format: int32 + example: + - 12 + - 185 + example: + - 12 + - 185 + explode: false + - name: active + in: query + required: false + description: Filter by chat activity + schema: + type: boolean + example: true + example: true + explode: false + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + - meta + properties: + data: + type: array + items: + $ref: '#/components/schemas/Message' + meta: + $ref: '#/components/schemas/SearchPaginationMeta' + description: Search results response wrapper with data and pagination + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + tags: + - Search + x-paginated: true + x-requirements: + scope: search:messages + /search/users: + get: + operationId: SearchOperations_searchUsers + description: |- + Search employees + + Full-text search for employees by name, email, position, and other fields. + parameters: + - name: query + in: query + required: false + description: Search query text + schema: + type: string + example: Oleg + example: Oleg + explode: false + - name: limit + in: query + required: false + description: Number of results to return per request + schema: + type: integer + format: int32 + maximum: 200 + example: 10 + default: 200 + example: 10 + explode: false + - name: cursor + in: query + required: false + description: Pagination cursor (from `meta.paginate.next_page`) + schema: + type: string + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + explode: false + - name: sort + in: query + required: false + description: Sort results by + schema: + $ref: '#/components/schemas/SearchSortOrder' + example: by_score + example: by_score + explode: false + - name: order + in: query + required: false + description: Sort direction + schema: + $ref: '#/components/schemas/SortOrder' + example: desc + example: desc + explode: false + - name: created_from + in: query + required: false + description: Filter by creation date (from) + schema: + type: string + format: date-time + example: '2025-01-01T00:00:00.000Z' + example: '2025-01-01T00:00:00.000Z' + explode: false + - name: created_to + in: query + required: false + description: Filter by creation date (to) + schema: + type: string + format: date-time + example: '2025-02-01T00:00:00.000Z' + example: '2025-02-01T00:00:00.000Z' + explode: false + - name: company_roles + in: query + required: false + description: Filter by employee roles + schema: + type: array + items: + $ref: '#/components/schemas/UserRole' + example: + - admin + - user + example: + - admin + - user + explode: false + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + - meta + properties: + data: + type: array + items: + $ref: '#/components/schemas/User' + meta: + $ref: '#/components/schemas/SearchPaginationMeta' + description: Search results response wrapper with data and pagination + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + tags: + - Search + x-paginated: true + x-requirements: + scope: search:users + /tasks: + post: + operationId: TaskOperations_createTask + description: >- + New task + + + Create a new task. + + + When creating a task, specifying the task type is required: call, meeting, simple reminder, event, or email. No + additional description is needed — you simply create a task with the corresponding text. If you specify a task + description, it will become the task's text. + + + A task must have assignees; if none are specified, you are assigned as the responsible person. + + + Any workspace employee can be assigned to a task that is not linked to any entity. You can get the current + employee list using the [List employees](GET /users) method. + + + A task can be linked to a chat by specifying `chat_id`. To link to a chat, you must be a member of that chat. + parameters: [] + responses: + '201': + description: The request has succeeded and a new resource has been created as a result. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Task' + description: Response wrapper with data + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Tasks + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TaskCreateRequest' + x-requirements: + scope: tasks:create + get: + operationId: TaskOperations_listTasks + description: |- + List tasks + + Retrieve a list of tasks. + parameters: + - name: limit + in: query + required: false + description: Number of records to return per request + schema: + type: integer + format: int32 + minimum: 1 + maximum: 50 + example: 1 + default: 50 + example: 1 + explode: false + - name: cursor + in: query + required: false + description: Pagination cursor (from `meta.paginate.next_page`) + schema: + type: string + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + explode: false + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + - meta + properties: + data: + type: array + items: + $ref: '#/components/schemas/Task' + meta: + $ref: '#/components/schemas/PaginationMeta' + description: Response wrapper with data and pagination + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + tags: + - Tasks + x-paginated: true + x-requirements: + scope: tasks:read + /tasks/{id}: + get: + operationId: TaskOperations_getTask + description: |- + Task info + + Retrieve information about a task. + + To get a task you need to know its `id` and specify it in the request `URL`. + parameters: + - name: id + in: path + required: true + description: Task ID + schema: + type: integer + format: int32 + example: 22283 + example: 22283 + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Task' + description: Response wrapper with data + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Tasks + x-requirements: + scope: tasks:read + put: + operationId: TaskOperations_updateTask + description: >- + Edit task + + + Edit a task. + + + To edit a task you need to know its `id` and specify it in the request `URL`. All editable task parameters are + specified in the request body. + parameters: + - name: id + in: path + required: true + description: Task ID + schema: + type: integer + format: int32 + example: 22283 + example: 22283 + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Task' + description: Response wrapper with data + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Tasks + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TaskUpdateRequest' + x-requirements: + scope: tasks:update + delete: + operationId: TaskOperations_deleteTask + description: |- + Delete task + + Delete a task. + + To delete a task you need to know its `id` and specify it in the request `URL`. + parameters: + - name: id + in: path + required: true + description: Task ID + schema: + type: integer + format: int32 + example: 22283 + example: 22283 + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Tasks + x-requirements: + scope: tasks:delete + /threads/{id}: + get: + operationId: ThreadOperations_getThread + description: |- + Thread info + + Retrieve information about a thread. + + To get a thread you need to know its `id` and specify it in the request `URL`. + parameters: + - name: id + in: path + required: true + description: Thread ID + schema: + type: integer + format: int32 + example: 265142 + example: 265142 + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Thread' + description: Response wrapper with data + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Threads + x-requirements: + scope: threads:read + /uploads: + post: + operationId: UploadOperations_getUploadParams + description: |- + Get signature, key and other parameters + + Retrieve the signature, key, and other parameters required for file upload. + + This method must be used for each file upload. + parameters: [] + responses: + '201': + description: The request has succeeded and a new resource has been created as a result. + content: + application/json: + schema: + $ref: '#/components/schemas/UploadParams' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + tags: + - Common + x-requirements: + scope: uploads:write + /users: + post: + operationId: UserOperations_createUser + description: >- + Create employee + + + Create a new employee in your workspace. + + + You can fill in custom properties for the employee that have been created in your workspace. You can get the + current list of employee custom property IDs using the [List custom properties](GET /custom_properties) method. + parameters: [] + responses: + '201': + description: The request has succeeded and a new resource has been created as a result. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/User' + description: Response wrapper with data + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Users + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UserCreateRequest' + x-requirements: + scope: users:create + get: + operationId: UserOperations_listUsers + description: |- + List employees + + Retrieve the current list of employees in your workspace. + parameters: + - name: query + in: query + required: false + description: >- + Search phrase to filter results. Search works on the following fields: `first_name`, `last_name`, `email`, + `phone_number`, and `nickname`. + schema: + type: string + example: Oleg + example: Oleg + explode: false + - name: limit + in: query + required: false + description: Number of records to return per request + schema: + type: integer + format: int32 + minimum: 1 + maximum: 50 + example: 1 + default: 50 + example: 1 + explode: false + - name: cursor + in: query + required: false + description: Pagination cursor (from `meta.paginate.next_page`) + schema: + type: string + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + explode: false + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + - meta + properties: + data: + type: array + items: + $ref: '#/components/schemas/User' + meta: + $ref: '#/components/schemas/PaginationMeta' + description: Response wrapper with data and pagination + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Users + x-paginated: true + x-requirements: + scope: users:read + /users/{id}: + get: + operationId: UserOperations_getUser + description: |- + Employee info + + Retrieve information about an employee. + + To get an employee you need to know their `id` and specify it in the request `URL`. + parameters: + - name: id + in: path + required: true + description: User ID + schema: + type: integer + format: int32 + example: 12 + example: 12 + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/User' + description: Response wrapper with data + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Users + x-requirements: + scope: users:read + put: + operationId: UserOperations_updateUser + description: >- + Edit employee + + + Edit an employee. + + + To edit an employee you need to know their `id` and specify it in the request `URL`. All editable employee + parameters are specified in the request body. You can get the current list of employee custom property IDs using + the [List custom properties](GET /custom_properties) method. + parameters: + - name: id + in: path + required: true + description: User ID + schema: + type: integer + format: int32 + example: 12 + example: 12 + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/User' + description: Response wrapper with data + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Users + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UserUpdateRequest' + x-requirements: + scope: users:update + delete: + operationId: UserOperations_deleteUser + description: |- + Delete employee + + Delete an employee. + + To delete an employee you need to know their `id` and specify it in the request `URL`. + parameters: + - name: id + in: path + required: true + description: User ID + schema: + type: integer + format: int32 + example: 12 + example: 12 + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Users + x-requirements: + scope: users:delete + /users/{user_id}/avatar: + put: + operationId: UserAvatarOperations_updateUserAvatar + description: |- + Upload employee avatar + + Upload or update an employee's avatar. The file is sent in `multipart/form-data` format. + parameters: + - name: user_id + in: path + required: true + description: User ID + schema: + type: integer + format: int32 + example: 12 + example: 12 + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/AvatarData' + description: Response wrapper with data + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/ApiError' + - $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Users + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + image: + type: string + format: binary + description: Avatar image file + required: + - image + x-requirements: + scope: user_avatar:write + delete: + operationId: UserAvatarOperations_deleteUserAvatar + description: |- + Delete employee avatar + + Delete an employee's avatar. + parameters: + - name: user_id + in: path + required: true + description: User ID + schema: + type: integer + format: int32 + example: 12 + example: 12 + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/ApiError' + - $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Users + x-requirements: + scope: user_avatar:write + /users/{user_id}/status: + get: + operationId: UserStatusOperations_getUserStatus + description: |- + Employee status + + Retrieve information about an employee's status. + parameters: + - name: user_id + in: path + required: true + description: User ID + schema: + type: integer + format: int32 + example: 12 + example: 12 + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + type: object + allOf: + - $ref: '#/components/schemas/UserStatus' + nullable: true + description: Response wrapper with data + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Users + x-requirements: + scope: user_status:read + put: + operationId: UserStatusOperations_updateUserStatus + description: |- + New employee status + + Set a new status for an employee. + parameters: + - name: user_id + in: path + required: true + description: User ID + schema: + type: integer + format: int32 + example: 12 + example: 12 + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/UserStatus' + description: Response wrapper with data + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Users + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/StatusUpdateRequest' + x-requirements: + scope: user_status:write + delete: + operationId: UserStatusOperations_deleteUserStatus + description: |- + Delete employee status + + Delete an employee's status. + parameters: + - name: user_id + in: path + required: true + description: User ID + schema: + type: integer + format: int32 + example: 12 + example: 12 + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Users + x-requirements: + scope: user_status:write + /views/open: + post: + operationId: FormOperations_openView + description: |- + Open view + + Open a modal window with a view for the user. + + To open a modal window with a view, your application must have a valid, non-expired `trigger_id`. + parameters: [] + responses: + '201': + description: The request has succeeded and a new resource has been created as a result. + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '410': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Views + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/OpenViewRequest' + x-requirements: + scope: views:write + /webhooks/events: + get: + operationId: BotOperations_getWebhookEvents + description: >- + Event history + + + Retrieve the history of a bot's recent events. This method is useful if you cannot receive events in real time + at your `URL`, but need to process all events you have subscribed to. + + + Event history is only saved when the "Save event history" option is enabled in the "Outgoing webhook" tab of the + bot settings. Specifying a "Webhook `URL`" is not required. + + + To retrieve the event history of a specific bot, you need to know its `access_token` and use it in the request. + Each event is a webhook `JSON` object. + parameters: + - name: limit + in: query + required: false + description: Number of records to return per request + schema: + type: integer + format: int32 + minimum: 1 + maximum: 50 + example: 1 + default: 50 + example: 1 + explode: false + - name: cursor + in: query + required: false + description: Pagination cursor (from `meta.paginate.next_page`) + schema: + type: string + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + example: eyJpZCI6MTAsImRpciI6ImFzYyJ9 + explode: false + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + - meta + properties: + data: + type: array + items: + $ref: '#/components/schemas/WebhookEvent' + meta: + $ref: '#/components/schemas/PaginationMeta' + description: Response wrapper with data and pagination + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Bots + x-paginated: true + x-requirements: + scope: webhooks:events:read + /webhooks/events/{id}: + delete: + operationId: BotOperations_deleteWebhookEvent + description: |- + Delete event + + This method is only available with a bot's `access_token`. + + Delete an event from the bot's event history. + + To delete an event you need to know the bot's `access_token` that owns the event and the event `id`. + parameters: + - name: id + in: path + required: true + description: Event ID + schema: + type: string + example: 01KAJZ2XDSS2S3DSW9EXJZ0TBV + example: 01KAJZ2XDSS2S3DSW9EXJZ0TBV + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Bots + x-requirements: + scope: webhooks:events:delete +security: + - BearerAuth: [] +components: + schemas: + AccessTokenInfo: + type: object + required: + - id + - token + - name + - user_id + - scopes + - created_at + - revoked_at + - expires_in + - last_used_at + properties: + id: + type: integer + format: int64 + description: Token ID + example: 4827 + token: + type: string + description: Masked token (first 8 and last 4 characters visible) + example: cH5kR9mN...x7Qp + name: + type: string + nullable: true + description: User-defined token name + example: My API Token + user_id: + type: integer + format: int64 + description: Token owner ID + example: 12 + scopes: + type: array + items: + $ref: '#/components/schemas/OAuthScope' + description: List of token scopes + example: + - messages:read + - chats:read + created_at: + type: string + format: date-time + description: Token creation date + example: '2025-01-15T10:30:00.000Z' + revoked_at: + type: string + format: date-time + nullable: true + description: Token revocation date + example: null + expires_in: + type: integer + format: int32 + nullable: true + description: Token lifetime in seconds + example: null + last_used_at: + type: string + format: date-time + nullable: true + description: Token last used date + example: '2025-02-24T14:20:00.000Z' + description: Access token + AddMembersRequest: + type: object + required: + - member_ids + properties: + member_ids: + type: array + items: + type: integer + format: int32 + description: Array of user IDs who will become members + example: + - 186 + - 187 + silent: + type: boolean + description: Do not create a system message in the chat about adding a member + example: true + description: Request to add members to a chat + AddTagsRequest: + type: object + required: + - group_tag_ids + properties: + group_tag_ids: + type: array + items: + type: integer + format: int32 + description: Array of tag IDs to be added as members + example: + - 86 + - 18 + description: Request to add tags to a chat + ApiError: + type: object + required: + - errors + properties: + errors: + type: array + items: + $ref: '#/components/schemas/ApiErrorItem' + description: Array of errors + description: API error (used for 400, 402, 403, 404, 409, 410, 422) + x-error: true + ApiErrorItem: + type: object + required: + - key + - value + - message + - code + - payload + properties: + key: + type: string + description: Field key that caused the error + example: field.name + value: + type: string + nullable: true + description: Field value that caused the error + example: invalid_value + message: + type: string + description: Error message + example: Field cannot be empty + code: + allOf: + - $ref: '#/components/schemas/ValidationErrorCode' + description: Error code + example: blank + payload: + type: object + additionalProperties: {} + nullable: true + description: >- + Additional error data. Content depends on the error code: `{id: number}` for custom property errors, `{type: + string}` for type mismatch errors + example: null + description: Detailed error information + AuditDetailsChatId: + type: object + required: + - chat_id + properties: + chat_id: + type: integer + format: int32 + description: Chat ID + description: 'For: tag_removed_from_chat' + AuditDetailsChatPermission: + type: object + required: + - public_access + properties: + public_access: + type: boolean + description: Public access + description: 'For: chat_permission_changed' + AuditDetailsChatRenamed: + type: object + required: + - old_name + - new_name + properties: + old_name: + type: string + description: Previous chat name + new_name: + type: string + description: New chat name + description: 'For: chat_renamed' + AuditDetailsDlp: + type: object + required: + - dlp_rule_id + - dlp_rule_name + - message_id + - chat_id + - user_id + - action_message + - conditions_matched + properties: + dlp_rule_id: + type: integer + format: int32 + description: DLP rule ID + dlp_rule_name: + type: string + description: DLP rule name + message_id: + type: integer + format: int32 + description: Message ID + chat_id: + type: integer + format: int32 + description: Chat ID + user_id: + type: integer + format: int32 + description: User ID + action_message: + type: string + description: Action description + conditions_matched: + type: boolean + description: Rule conditions check result (true — conditions matched) + description: 'For: dlp_violation_detected' + AuditDetailsEmpty: + type: object + description: >- + Empty details. For: user_login, user_logout, user_2fa_fail, user_2fa_success, user_created, user_deleted, + chat_created, message_created, message_updated, message_deleted, reaction_created, reaction_deleted, + thread_created, audit_events_accessed + AuditDetailsInitiator: + type: object + required: + - initiator_id + properties: + initiator_id: + type: integer + format: int32 + description: Action initiator ID + description: 'For: user_added_to_tag, user_removed_from_tag, user_chat_leave' + AuditDetailsInviter: + type: object + required: + - inviter_id + properties: + inviter_id: + type: integer + format: int32 + description: Inviter ID + description: 'For: user_chat_join' + AuditDetailsKms: + type: object + required: + - chat_id + - message_id + - reason + properties: + chat_id: + type: integer + format: int32 + description: Chat ID + message_id: + type: integer + format: int32 + description: Message ID + reason: + type: string + description: Operation reason + description: 'For: kms_encrypt, kms_decrypt' + AuditDetailsRoleChanged: + type: object + required: + - new_company_role + - previous_company_role + - initiator_id + properties: + new_company_role: + type: string + description: New role + previous_company_role: + type: string + description: Previous role + initiator_id: + type: integer + format: int32 + description: Initiator ID + description: 'For: user_role_changed' + AuditDetailsSearch: + type: object + required: + - search_type + - query_present + - cursor_present + - limit + - filters + properties: + search_type: + type: string + description: Search type + query_present: + type: boolean + description: Whether a search query was specified + cursor_present: + type: boolean + description: Whether a cursor was used + limit: + type: integer + format: int32 + description: Number of returned results + filters: + type: object + additionalProperties: {} + description: >- + Applied filters. Possible keys depend on the search type: order, sort, created_from, created_to, + company_roles (users), active, chat_subtype, personal (chats), chat_ids, user_ids (messages) + description: 'For: search_users_api, search_chats_api, search_messages_api' + AuditDetailsTagChat: + type: object + required: + - chat_id + - tag_name + properties: + chat_id: + type: integer + format: int32 + description: Chat ID + tag_name: + type: string + description: Tag name + description: 'For: tag_added_to_chat' + AuditDetailsTagName: + type: object + required: + - name + properties: + name: + type: string + description: Tag name + description: 'For: tag_created, tag_deleted' + AuditDetailsTokenScopes: + type: object + required: + - scopes + properties: + scopes: + type: array + items: + type: string + description: Token scopes + description: 'For: access_token_created, access_token_updated, access_token_destroy' + AuditDetailsUserUpdated: + type: object + required: + - changed_attrs + properties: + changed_attrs: + type: array + items: + type: string + description: List of changed fields + description: 'For: user_updated' + AuditEvent: + type: object + required: + - id + - created_at + - event_key + - entity_id + - entity_type + - actor_id + - actor_type + - details + - ip_address + - user_agent + properties: + id: + type: string + description: Unique event ID + example: a1b2c3d4-5e6f-7g8h-9i10-j11k12l13m14 + created_at: + type: string + format: date-time + description: Event creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2025-05-15T14:30:00.000Z' + event_key: + allOf: + - $ref: '#/components/schemas/AuditEventKey' + description: Event type key + example: user_login + entity_id: + type: string + description: Affected entity ID + example: '98765' + entity_type: + type: string + description: Affected entity type + example: User + actor_id: + type: string + description: ID of the user who performed the action + example: '98765' + actor_type: + type: string + description: Actor type + example: User + details: + allOf: + - $ref: '#/components/schemas/AuditEventDetailsUnion' + description: >- + Additional event details. Structure depends on the event_key value — see event_key field value descriptions. + For events without details, an empty object is returned + ip_address: + type: string + description: IP address from which the action was performed + example: 192.168.1.100 + user_agent: + type: string + description: Client user agent + example: Pachca/3.60.0 (co.staply.pachca; build:15; iOS 18.5.0) Alamofire/5.0.0 + description: Audit event + AuditEventDetailsUnion: + anyOf: + - $ref: '#/components/schemas/AuditDetailsEmpty' + - $ref: '#/components/schemas/AuditDetailsUserUpdated' + - $ref: '#/components/schemas/AuditDetailsRoleChanged' + - $ref: '#/components/schemas/AuditDetailsTagName' + - $ref: '#/components/schemas/AuditDetailsInitiator' + - $ref: '#/components/schemas/AuditDetailsInviter' + - $ref: '#/components/schemas/AuditDetailsChatRenamed' + - $ref: '#/components/schemas/AuditDetailsChatPermission' + - $ref: '#/components/schemas/AuditDetailsTagChat' + - $ref: '#/components/schemas/AuditDetailsChatId' + - $ref: '#/components/schemas/AuditDetailsTokenScopes' + - $ref: '#/components/schemas/AuditDetailsKms' + - $ref: '#/components/schemas/AuditDetailsDlp' + - $ref: '#/components/schemas/AuditDetailsSearch' + description: Additional audit event details. Structure depends on the event_key value + AuditEventKey: + type: string + enum: + - user_login + - user_logout + - user_2fa_fail + - user_2fa_success + - user_created + - user_deleted + - user_role_changed + - user_updated + - tag_created + - tag_deleted + - user_added_to_tag + - user_removed_from_tag + - chat_created + - chat_renamed + - chat_permission_changed + - user_chat_join + - user_chat_leave + - tag_added_to_chat + - tag_removed_from_chat + - message_updated + - message_deleted + - message_created + - reaction_created + - reaction_deleted + - thread_created + - access_token_created + - access_token_updated + - access_token_destroy + - kms_encrypt + - kms_decrypt + - audit_events_accessed + - dlp_violation_detected + - search_users_api + - search_chats_api + - search_messages_api + description: Audit event type + x-enum-descriptions: + user_login: User logged in successfully + user_logout: User logged out + user_2fa_fail: Failed two-factor authentication attempt + user_2fa_success: Successful two-factor authentication + user_created: New user account created + user_deleted: User account deleted + user_role_changed: User role changed + user_updated: User data updated + tag_created: New tag created + tag_deleted: Tag deleted + user_added_to_tag: User added to tag + user_removed_from_tag: User removed from tag + chat_created: New chat created + chat_renamed: Chat renamed + chat_permission_changed: Chat access permissions changed + user_chat_join: User joined the chat + user_chat_leave: User left the chat + tag_added_to_chat: Tag added to chat + tag_removed_from_chat: Tag removed from chat + message_updated: Message edited + message_deleted: Message deleted + message_created: Message created + reaction_created: Reaction added + reaction_deleted: Reaction removed + thread_created: Thread created + access_token_created: New access token created + access_token_updated: Access token updated + access_token_destroy: Access token deleted + kms_encrypt: Data encrypted + kms_decrypt: Data decrypted + audit_events_accessed: Audit logs accessed + dlp_violation_detected: DLP rule violation detected + search_users_api: Employee search via API + search_chats_api: Chat search via API + search_messages_api: Message search via API + AvatarData: + type: object + required: + - image_url + properties: + image_url: + type: string + description: Avatar URL + example: https://pachca-prod.s3.amazonaws.com/uploads/0001/0001/image.jpg + description: Avatar data + BotResponse: + type: object + required: + - id + - webhook + properties: + id: + type: integer + format: int32 + description: Bot ID + example: 1738816 + webhook: + type: object + properties: + outgoing_url: + type: string + description: Outgoing webhook URL + example: https://www.website.com/tasks/new + required: + - outgoing_url + description: Webhook parameters object + description: Bot parameters + BotUpdateRequest: + type: object + required: + - bot + properties: + bot: + type: object + properties: + webhook: + type: object + properties: + outgoing_url: + type: string + description: Outgoing webhook URL + example: https://www.website.com/tasks/new + required: + - outgoing_url + description: Webhook parameters object + required: + - webhook + description: Bot parameters object to update + description: Bot update request + Button: + type: object + required: + - text + properties: + text: + type: string + maxLength: 255 + description: Text displayed on the button + example: Learn more + url: + type: string + description: URL that will be opened when the button is clicked + example: https://example.com/details + data: + type: string + maxLength: 255 + description: Data that will be sent in the outgoing webhook when the button is clicked + example: awesome + description: Button + ButtonWebhookPayload: + type: object + required: + - type + - event + - message_id + - trigger_id + - data + - user_id + - chat_id + - webhook_timestamp + properties: + type: + type: string + enum: + - button + description: Object type + example: button + x-enum-descriptions: + button: Always button for buttons + event: + type: string + enum: + - click + description: Event type + example: click + x-enum-descriptions: + click: Button click + message_id: + type: integer + format: int32 + description: ID of the message the button belongs to + example: 1245817 + trigger_id: + type: string + description: Unique event identifier. Lifetime — 3 seconds. Can be used, for example, to open a view for the user + example: a1b2c3d4-5e6f-7g8h-9i10-j11k12l13m14 + data: + type: string + description: Clicked button data + example: button_data + user_id: + type: integer + format: int32 + description: ID of the user who clicked the button + example: 2345 + chat_id: + type: integer + format: int32 + description: ID of the chat where the button was clicked + example: 9012 + webhook_timestamp: + type: integer + format: int32 + description: Webhook send date and time (UTC+0) in UNIX format + example: 1747574400 + description: Outgoing webhook payload for button click + Chat: + type: object + required: + - id + - name + - created_at + - owner_id + - member_ids + - group_tag_ids + - channel + - personal + - public + - last_message_at + - meet_room_url + properties: + id: + type: integer + format: int32 + description: Created chat ID + example: 334 + name: + type: string + description: Name + example: 🤿 aqua + created_at: + type: string + format: date-time + description: Chat creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2021-08-28T15:56:53.000Z' + owner_id: + type: integer + format: int32 + description: ID of the user who created the chat + example: 185 + member_ids: + type: array + items: + type: integer + format: int32 + description: Array of member user IDs + example: + - 185 + - 186 + - 187 + group_tag_ids: + type: array + items: + type: integer + format: int32 + description: Array of member tag IDs + example: + - 9111 + channel: + type: boolean + description: Whether this is a channel + example: true + personal: + type: boolean + description: Whether this is a direct message + example: false + public: + type: boolean + description: Publicly accessible + example: false + last_message_at: + type: string + format: date-time + description: Date and time of the last message in the chat (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2021-08-28T15:56:53.000Z' + meet_room_url: + type: string + description: Video chat link + example: https://meet.pachca.com/aqua-94bb21b5 + description: Chat + ChatAvailability: + type: string + enum: + - is_member + - public + description: Chat availability for the user + x-enum-descriptions: + is_member: Chats where the user is a member + public: All public chats in the workspace, regardless of user membership + ChatCreateRequest: + type: object + required: + - chat + properties: + chat: + type: object + properties: + name: + type: string + description: Name + example: 🤿 aqua + member_ids: + type: array + items: + type: integer + format: int32 + description: Array of user IDs who will become members + example: + - 186 + - 187 + group_tag_ids: + type: array + items: + type: integer + format: int32 + description: Array of tag IDs to be added as members + example: + - 86 + - 18 + channel: + type: boolean + description: Whether this is a channel + example: true + default: false + public: + type: boolean + description: Publicly accessible + example: false + default: false + required: + - name + description: Chat parameters object to create + description: Chat creation request + ChatMemberRole: + type: string + enum: + - admin + - editor + - member + description: Chat member role + x-enum-descriptions: + admin: Admin + editor: Editor (available for channels only) + member: Member or subscriber + ChatMemberRoleFilter: + type: string + enum: + - all + - owner + - admin + - editor + - member + description: Chat member role (with all filter) + x-enum-descriptions: + all: Any role + owner: Owner + admin: Admin + editor: Editor + member: Member/subscriber + ChatMemberWebhookPayload: + type: object + required: + - type + - event + - chat_id + - user_ids + - created_at + - webhook_timestamp + properties: + type: + type: string + enum: + - chat_member + description: Object type + example: chat_member + x-enum-descriptions: + chat_member: Always chat_member for chat members + event: + allOf: + - $ref: '#/components/schemas/MemberEventType' + description: Event type + example: add + chat_id: + type: integer + format: int32 + description: ID of the chat where membership changed + example: 9012 + thread_id: + type: integer + format: int32 + nullable: true + description: Thread ID + example: 5678 + user_ids: + type: array + items: + type: integer + format: int32 + description: Array of user IDs affected by the event + example: + - 2345 + - 6789 + created_at: + type: string + format: date-time + description: Event date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2025-05-15T14:30:00.000Z' + webhook_timestamp: + type: integer + format: int32 + description: Webhook send date and time (UTC+0) in UNIX format + example: 1747574400 + description: Outgoing webhook payload for chat members + ChatSortField: + type: string + enum: + - id + - last_message_at + description: Chat sort field + x-enum-descriptions: + id: By chat ID + last_message_at: By last message date and time + ChatSubtype: + type: string + enum: + - discussion + - thread + description: Chat type + x-enum-descriptions: + discussion: Channel or conversation + thread: Thread + ChatUpdateRequest: + type: object + required: + - chat + properties: + chat: + type: object + properties: + name: + type: string + description: Name + example: Pool + public: + type: boolean + description: Publicly accessible + example: true + description: Chat parameters object to update + description: Chat update request + CompanyMemberWebhookPayload: + type: object + required: + - type + - event + - user_ids + - created_at + - webhook_timestamp + properties: + type: + type: string + enum: + - company_member + description: Object type + example: company_member + x-enum-descriptions: + company_member: Always company_member for workspace members + event: + allOf: + - $ref: '#/components/schemas/UserEventType' + description: Event type + example: invite + user_ids: + type: array + items: + type: integer + format: int32 + description: Array of user IDs affected by the event + example: + - 2345 + - 6789 + created_at: + type: string + format: date-time + description: Event date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2025-05-15T14:30:00.000Z' + webhook_timestamp: + type: integer + format: int32 + description: Webhook send date and time (UTC+0) in UNIX format + example: 1747574400 + description: Outgoing webhook payload for workspace members + CustomProperty: + type: object + required: + - id + - name + - data_type + - value + properties: + id: + type: integer + format: int32 + description: Custom property ID + example: 1678 + name: + type: string + description: Custom property name + example: City + data_type: + allOf: + - $ref: '#/components/schemas/CustomPropertyDataType' + description: Custom property data type + example: string + value: + type: string + description: Custom property value + example: Saint Petersburg + description: Custom property + CustomPropertyDataType: + type: string + enum: + - string + - number + - date + - link + description: Custom property data type + x-enum-descriptions: + string: String value + number: Number value + date: Date + link: Link + CustomPropertyDefinition: + type: object + required: + - id + - name + - data_type + properties: + id: + type: integer + format: int32 + description: Property ID + example: 1678 + name: + type: string + description: Property name + example: City + data_type: + allOf: + - $ref: '#/components/schemas/CustomPropertyDataType' + description: Property type + example: string + description: Custom property + ExportRequest: + type: object + required: + - start_at + - end_at + - webhook_url + properties: + start_at: + type: string + format: date + description: Export start date (ISO-8601, UTC+0) in YYYY-MM-DD format + example: '2025-03-20' + end_at: + type: string + format: date + description: Export end date (ISO-8601, UTC+0) in YYYY-MM-DD format + example: '2025-03-20' + webhook_url: + type: string + description: URL to receive a webhook when the export is complete + example: https://webhook.site/9227d3b8-6e82-4e64-bf5d-ad972ad270f2 + chat_ids: + type: array + items: + type: integer + format: int32 + description: Array of chat IDs. Specify to export messages from specific chats only. + example: + - 1381521 + skip_chats_file: + type: boolean + description: Skip generating the chat list file (chats.json) + example: false + description: Message export request + File: + type: object + required: + - id + - key + - name + - file_type + - url + properties: + id: + type: integer + format: int32 + description: File ID + example: 3560 + key: + type: string + description: File path + example: attaches/files/12/21zu7934-02e1-44d9-8df2-0f970c259796/congrat.png + name: + type: string + description: File name with extension + example: congrat.png + file_type: + allOf: + - $ref: '#/components/schemas/FileType' + description: File type + example: image + url: + type: string + description: Direct file download URL + example: >- + https://pachca-prod-uploads.s3.storage.selcloud.ru/attaches/files/12/21zu7934-02e1-44d9-8df2-0f970c259796/congrat.png?response-cache-control=max-age%3D3600%3B&response-content-disposition=attachment&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=142155_staply%2F20231107%2Fru-1a%2Fs3%2Faws4_request&X-Amz-Date=20231107T160412&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=98765asgfadsfdSaDSd4sdfg35asdf67sadf8 + width: + type: integer + format: int32 + nullable: true + description: Image width in pixels + example: 1920 + height: + type: integer + format: int32 + nullable: true + description: Image height in pixels + example: 1080 + description: File + FileType: + type: string + enum: + - file + - image + description: File type + x-enum-descriptions: + file: Regular file + image: Image + FileUploadRequest: + type: object + properties: + Content-Disposition: + type: string + description: >- + Content-Disposition parameter received in the response to [Get signature, key and other parameters](POST + /uploads) + acl: + type: string + description: acl parameter received in the response to [Get signature, key and other parameters](POST /uploads) + policy: + type: string + description: policy parameter received in the response to [Get signature, key and other parameters](POST /uploads) + x-amz-credential: + type: string + description: >- + x-amz-credential parameter received in the response to [Get signature, key and other parameters](POST + /uploads) + x-amz-algorithm: + type: string + description: >- + x-amz-algorithm parameter received in the response to [Get signature, key and other parameters](POST + /uploads) + x-amz-date: + type: string + description: x-amz-date parameter received in the response to [Get signature, key and other parameters](POST /uploads) + x-amz-signature: + type: string + description: >- + x-amz-signature parameter received in the response to [Get signature, key and other parameters](POST + /uploads) + key: + type: string + description: key parameter received in the response to [Get signature, key and other parameters](POST /uploads) + file: + type: string + format: binary + description: File to upload + required: + - Content-Disposition + - acl + - policy + - x-amz-credential + - x-amz-algorithm + - x-amz-date + - x-amz-signature + - key + - file + Forwarding: + type: object + required: + - original_message_id + - original_chat_id + - author_id + - original_created_at + - original_thread_id + - original_thread_message_id + - original_thread_parent_chat_id + properties: + original_message_id: + type: integer + format: int32 + description: Original message ID + example: 194275 + original_chat_id: + type: integer + format: int32 + description: ID of the chat containing the original message + example: 334 + author_id: + type: integer + format: int32 + description: ID of the user who created the original message + example: 12 + original_created_at: + type: string + format: date-time + description: Original message creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2025-01-15T10:30:00.000Z' + original_thread_id: + type: integer + format: int32 + nullable: true + description: ID of the thread containing the original message + example: null + original_thread_message_id: + type: integer + format: int32 + nullable: true + description: ID of the message that the thread containing the original message was created for + example: null + original_thread_parent_chat_id: + type: integer + format: int32 + nullable: true + description: ID of the chat of the message that the thread containing the original message was created for + example: null + description: Forwarded message information + GroupTag: + type: object + required: + - id + - name + - users_count + properties: + id: + type: integer + format: int32 + description: Tag ID + example: 9111 + name: + type: string + description: Tag name + example: Design + users_count: + type: integer + format: int32 + description: Number of employees who have this tag + example: 6 + description: Group tag + GroupTagRequest: + type: object + required: + - group_tag + properties: + group_tag: + type: object + properties: + name: + type: string + description: Tag name + example: New tag name + required: + - name + description: Request to create or update a tag + InviteStatus: + type: string + enum: + - confirmed + - sent + description: User invitation status + x-enum-descriptions: + confirmed: Confirmed + sent: Sent + LinkPreview: + type: object + required: + - title + - description + properties: + title: + type: string + description: Title + example: 'Article: Sending files' + description: + type: string + description: Description + example: Example of sending files to a remote server + image_url: + type: string + description: Public image URL (if you want to upload an image file to Pachca, use the image parameter) + example: https://website.com/img/landing.png + image: + type: object + properties: + key: + type: string + description: Image path obtained from [file upload](POST /direct_url) + example: attaches/files/93746/e354fd79-9jh6-f2hd-fj83-709dae24c763/${filename} + name: + type: string + description: Image name (recommended to include the file extension) + example: files-to-server.jpg + size: + type: integer + format: int32 + description: Image size in bytes + example: 695604 + required: + - key + - name + description: Image + description: Link preview data + LinkPreviewsRequest: + type: object + required: + - link_previews + properties: + link_previews: + type: object + additionalProperties: + $ref: '#/components/schemas/LinkPreview' + description: '`JSON` map of link previews, where each key is a `URL` received in the outgoing webhook about a new message.' + x-record-key-example: https://website.com/articles/123 + description: Link unfurling request + LinkSharedWebhookPayload: + type: object + required: + - type + - event + - chat_id + - message_id + - links + - user_id + - created_at + - webhook_timestamp + properties: + type: + type: string + enum: + - message + description: Object type + example: message + x-enum-descriptions: + message: Always message for link unfurling + event: + type: string + enum: + - link_shared + description: Event type + example: link_shared + x-enum-descriptions: + link_shared: Link to a monitored domain detected + chat_id: + type: integer + format: int32 + description: ID of the chat where the link was found + example: 23438 + message_id: + type: integer + format: int32 + description: ID of the message containing the link + example: 268092 + links: + type: array + items: + $ref: '#/components/schemas/WebhookLink' + description: Array of detected links to monitored domains + user_id: + type: integer + format: int32 + description: Message sender ID + example: 2345 + created_at: + type: string + format: date-time + description: Message creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2024-09-18T19:53:14.000Z' + webhook_timestamp: + type: integer + format: int32 + description: Webhook send date and time (UTC+0) in UNIX format + example: 1726685594 + description: Outgoing webhook payload for link unfurling + MemberEventType: + type: string + enum: + - add + - remove + description: Webhook event type for members + x-enum-descriptions: + add: Addition + remove: Deleted + Message: + type: object + required: + - id + - entity_type + - entity_id + - chat_id + - root_chat_id + - content + - user_id + - created_at + - url + - files + - buttons + - thread + - forwarding + - parent_message_id + - display_avatar_url + - display_name + - changed_at + - deleted_at + properties: + id: + type: integer + format: int32 + description: Message ID + example: 194275 + entity_type: + allOf: + - $ref: '#/components/schemas/MessageEntityType' + description: Entity type the message belongs to + example: discussion + entity_id: + type: integer + format: int32 + description: ID of the entity the message belongs to (chat/channel, thread, or user) + example: 334 + chat_id: + type: integer + format: int32 + description: ID of the chat containing the message + example: 334 + root_chat_id: + type: integer + format: int32 + description: >- + Root chat ID. For messages in threads — the ID of the chat where the thread was created. For regular + messages, equals `chat_id`. + example: 334 + content: + type: string + description: Message text + example: Yesterday we sold 756 t-shirts (10% more than last Sunday) + user_id: + type: integer + format: int32 + description: ID of the user who created the message + example: 12 + created_at: + type: string + format: date-time + description: Message creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2021-08-28T15:57:23.000Z' + url: + type: string + description: Direct link to the message + example: https://app.pachca.com/chats/334?message=194275 + files: + type: array + items: + $ref: '#/components/schemas/File' + description: Attached files + buttons: + type: array + items: + type: array + items: + $ref: '#/components/schemas/Button' + nullable: true + description: Array of rows, each represented as an array of buttons + thread: + type: object + properties: + id: + type: integer + format: int64 + description: Thread ID + example: 265142 + chat_id: + type: integer + format: int64 + description: Thread chat ID + example: 2637266155 + required: + - id + - chat_id + nullable: true + description: Message thread + forwarding: + type: object + allOf: + - $ref: '#/components/schemas/Forwarding' + nullable: true + description: Forwarded message information + parent_message_id: + type: integer + format: int32 + nullable: true + description: ID of the message being replied to + example: null + display_avatar_url: + type: string + nullable: true + description: Message sender avatar URL + example: null + display_name: + type: string + nullable: true + description: Message sender full name + example: null + changed_at: + type: string + format: date-time + nullable: true + description: Message last edit date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2021-08-28T16:10:00.000Z' + deleted_at: + type: string + format: date-time + nullable: true + description: Message deletion date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: null + description: Message + MessageCreateRequest: + type: object + required: + - message + properties: + message: + type: object + properties: + entity_type: + allOf: + - $ref: '#/components/schemas/MessageEntityType' + description: Entity type + example: discussion + default: discussion + entity_id: + type: integer + format: int32 + description: Entity ID + example: 334 + content: + type: string + description: Message text + example: Yesterday we sold 756 t-shirts (10% more than last Sunday) + files: + type: array + items: + type: object + properties: + key: + type: string + description: File path obtained from [file upload](POST /direct_url) + example: attaches/files/93746/e354fd79-4f3e-4b5a-9c8d-1a2b3c4d5e6f/logo.png + name: + type: string + description: File name to display to the user (recommended to include the file extension) + example: logo.png + file_type: + allOf: + - $ref: '#/components/schemas/FileType' + description: File type + example: image + size: + type: integer + format: int32 + description: File size in bytes, displayed to the user + example: 12345 + width: + type: integer + format: int32 + description: Image width in px (used when file_type is set to image) + example: 800 + height: + type: integer + format: int32 + description: Image height in px (used when file_type is set to image) + example: 600 + required: + - key + - name + - file_type + - size + description: Files to attach + buttons: + type: array + items: + type: array + items: + $ref: '#/components/schemas/Button' + description: >- + Array of rows, each represented as an array of buttons. Maximum 100 buttons per message, up to 8 buttons + per row. + example: + - - text: Learn more + url: https://example.com/details + - text: Great! + data: awesome + parent_message_id: + type: integer + format: int32 + description: Message ID. Specify when sending a reply to another message. + example: 194270 + display_avatar_url: + type: string + maxLength: 255 + description: Custom sender avatar URL for this message. This field can only be used with a bot access_token. + example: https://example.com/avatar.png + display_name: + type: string + maxLength: 255 + description: Custom sender display name for this message. This field can only be used with a bot access_token. + example: Support Bot + skip_invite_mentions: + type: boolean + description: Skip adding mentioned users to the thread. Only works when sending a message to a thread. + example: false + default: false + required: + - entity_id + - content + description: Message parameters object to create + link_preview: + type: boolean + description: Display a preview of the first link found in the message text + example: false + default: false + description: Message creation request + MessageEntityType: + type: string + enum: + - discussion + - thread + - user + description: Entity type for messages + x-enum-descriptions: + discussion: Conversation or channel + thread: Thread + user: User + MessageSortField: + type: string + enum: + - id + description: Message sort field + x-enum-descriptions: + id: By message ID + MessageUpdateRequest: + type: object + required: + - message + properties: + message: + type: object + properties: + content: + type: string + description: Message text + example: >- + Try to spell these correctly on the first attempt: bureaucracy, accommodate, definitely, entrepreneur, + liaison, necessary, surveillance, questionnaire. + files: + type: array + items: + type: object + properties: + key: + type: string + description: File path obtained from [file upload](POST /direct_url) + example: attaches/files/93746/e354fd79-4f3e-4b5a-9c8d-1a2b3c4d5e6f/logo.png + name: + type: string + description: File name to display to the user (recommended to include the file extension) + example: logo.png + file_type: + type: string + description: 'File type: file (file), image (image)' + example: image + size: + type: integer + format: int32 + description: File size in bytes, displayed to the user + example: 12345 + width: + type: integer + format: int32 + description: Image width in px (used when file_type is set to image) + example: 800 + height: + type: integer + format: int32 + description: Image height in px (used when file_type is set to image) + example: 600 + required: + - key + - name + description: Files to attach + buttons: + type: array + items: + type: array + items: + $ref: '#/components/schemas/Button' + description: >- + Array of rows, each represented as an array of buttons. Maximum 100 buttons per message, up to 8 buttons + per row. To remove buttons, send an empty array. + example: + - - text: Learn more + url: https://example.com/details + display_avatar_url: + type: string + description: Custom sender avatar URL for this message. This field can only be used with a bot access_token. + example: https://example.com/avatar.png + display_name: + type: string + description: Custom sender display name for this message. This field can only be used with a bot access_token. + example: Support Bot + description: Message parameters object to update + description: Message update request + MessageWebhookPayload: + type: object + required: + - type + - id + - event + - entity_type + - entity_id + - content + - user_id + - created_at + - url + - chat_id + - webhook_timestamp + properties: + type: + type: string + enum: + - message + description: Object type + example: message + x-enum-descriptions: + message: Always message for messages + id: + type: integer + format: int32 + description: Message ID + example: 1245817 + event: + allOf: + - $ref: '#/components/schemas/WebhookEventType' + description: Event type + example: new + entity_type: + allOf: + - $ref: '#/components/schemas/MessageEntityType' + description: Entity type the message belongs to + example: discussion + entity_id: + type: integer + format: int32 + description: ID of the entity the message belongs to + example: 5678 + content: + type: string + description: Message text + example: Message text + user_id: + type: integer + format: int32 + description: Message sender ID + example: 2345 + created_at: + type: string + format: date-time + description: Message creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2025-05-15T14:30:00.000Z' + url: + type: string + description: Direct link to the message + example: https://pachca.com/chats/1245817/messages/5678 + chat_id: + type: integer + format: int32 + description: ID of the chat containing the message + example: 9012 + parent_message_id: + type: integer + format: int32 + nullable: true + description: ID of the message being replied to + example: 3456 + thread: + type: object + allOf: + - $ref: '#/components/schemas/WebhookMessageThread' + nullable: true + description: Thread parameters object + webhook_timestamp: + type: integer + format: int32 + description: Webhook send date and time (UTC+0) in UNIX format + example: 1747574400 + description: Outgoing webhook payload for messages + OAuthError: + type: object + required: + - error + - error_description + properties: + error: + type: string + description: Error code + example: invalid_token + error_description: + type: string + description: Error description + example: Access token is missing + description: OAuth authorization error (used for 401 and 403) + x-error: true + OAuthScope: + type: string + enum: + - chats:read + - chats:create + - chats:update + - chats:archive + - chats:leave + - chat_members:read + - chat_members:write + - chat_exports:read + - chat_exports:write + - messages:read + - messages:create + - messages:update + - messages:delete + - reactions:read + - reactions:write + - pins:write + - threads:read + - threads:create + - link_previews:write + - users:read + - users:create + - users:update + - users:delete + - group_tags:read + - group_tags:write + - bots:write + - 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 + - tasks:create + - tasks:update + - tasks:delete + - files:read + - files:write + - uploads:write + - views:write + - webhooks:read + - webhooks:write + - webhooks:events:read + - webhooks:events:delete + - search:users + - search:chats + - search:messages + description: OAuth token access scope + x-scope-roles: + chats_read: + - owner + - admin + - user + - bot + chats_create: + - owner + - admin + - user + - bot + chats_update: + - owner + - admin + - user + - bot + chats_archive: + - owner + - admin + - user + - bot + chats_leave: + - owner + - admin + - user + - bot + chat_members_read: + - owner + - admin + - user + - bot + chat_members_write: + - owner + - admin + - user + - bot + chat_exports_read: + - owner + chat_exports_write: + - owner + messages_read: + - owner + - admin + - user + - bot + messages_create: + - owner + - admin + - user + - bot + messages_update: + - owner + - admin + - user + - bot + messages_delete: + - owner + - admin + - user + - bot + reactions_read: + - owner + - admin + - user + - bot + reactions_write: + - owner + - admin + - user + - bot + pins_write: + - owner + - admin + - user + - bot + threads_read: + - owner + - admin + - user + - bot + threads_create: + - owner + - admin + - user + - bot + link_previews_write: + - owner + - admin + - user + - bot + views_write: + - owner + - admin + - user + - bot + users_read: + - owner + - admin + - user + - bot + users_create: + - owner + - admin + users_update: + - owner + - admin + users_delete: + - owner + - admin + group_tags_read: + - owner + - admin + group_tags_write: + - owner + - admin + bots_write: + - owner + - admin + - user + - bot + profile_read: + - owner + - admin + - user + - bot + profile_status_read: + - owner + - admin + - user + - bot + profile_status_write: + - owner + - admin + - user + - bot + profile_avatar_write: + - owner + - admin + - user + - bot + user_status_read: + - owner + - admin + user_status_write: + - owner + - admin + user_avatar_write: + - owner + - admin + custom_properties_read: + - owner + - admin + - user + - bot + audit_events_read: + - owner + tasks_read: + - owner + - admin + - user + - bot + tasks_create: + - owner + - admin + - user + - bot + tasks_update: + - owner + - admin + - user + - bot + tasks_delete: + - owner + - admin + - user + - bot + files_read: + - owner + - admin + - user + - bot + files_write: + - owner + - admin + - user + - bot + uploads_write: + - owner + - admin + - user + - bot + webhooks_read: + - owner + - admin + - user + - bot + webhooks_write: + - owner + - admin + - user + - bot + webhooks_events_read: + - owner + - admin + - user + - bot + webhooks_events_delete: + - owner + - admin + - user + - bot + search_users: + - owner + - admin + - user + - bot + search_chats: + - owner + - admin + - user + - bot + search_messages: + - owner + - admin + - user + - bot + x-enum-descriptions: + chats_read: View chats and chat list + chats_create: Create new chats + chats_update: Update chat settings + chats_archive: Archive and unarchive chats + chats_leave: Leave chats + chat_members_read: View chat members + chat_members_write: Add, update, and remove chat members + chat_exports_read: Download chat exports + chat_exports_write: Create chat exports + messages_read: View messages in chats + messages_create: Send messages + messages_update: Edit messages + messages_delete: Delete messages + reactions_read: View message reactions + reactions_write: Add and remove reactions + pins_write: Pin and unpin messages + threads_read: View threads (comments) + threads_create: Create threads (comments) + link_previews_write: Unfurl (link previews) + views_write: Open forms (views) + users_read: View employee information and employee list + users_create: Create new employees + users_update: Edit employee data + users_delete: Delete employees + group_tags_read: View tags + group_tags_write: Create, edit, and delete tags + bots_write: Update bot settings + profile_read: View own profile information + profile_status_read: View profile status + profile_status_write: Update and delete profile status + profile_avatar_write: Update and delete profile avatar + user_status_read: View employee status + user_status_write: Update and delete employee status + user_avatar_write: Update and delete employee avatar + custom_properties_read: View custom properties + audit_events_read: View audit log + tasks_read: View tasks + tasks_create: Create tasks + tasks_update: Update task + tasks_delete: Delete task + files_read: Download files + files_write: Upload files + uploads_write: Get file upload data + webhooks_read: View webhooks + webhooks_write: Create and manage webhooks + webhooks_events_read: View webhook logs + webhooks_events_delete: Delete webhook log entry + search_users: Search employees + search_chats: Search chats + search_messages: Search messages + OpenViewRequest: + type: object + required: + - type + - trigger_id + - view + properties: + type: + type: string + enum: + - modal + description: View opening method + example: modal + x-enum-descriptions: + modal: Modal window + trigger_id: + type: string + description: Unique event identifier (received, for example, in the outgoing webhook for a button click) + example: 791a056b-006c-49dd-834b-c633fde52fe8 + private_metadata: + type: string + maxLength: 3000 + 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. + example: '{"timeoff_id":4378}' + callback_id: + type: string + maxLength: 255 + 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. + example: timeoff_reguest_form + view: + type: object + properties: + title: + type: string + maxLength: 24 + description: View title + example: Vacation notification + close_text: + type: string + maxLength: 24 + description: Close button text + example: Close + default: Cancel + submit_text: + type: string + maxLength: 24 + description: Submit button text + example: Submit request + default: Submit + blocks: + type: array + items: + $ref: '#/components/schemas/ViewBlockUnion' + maxItems: 100 + description: Array of view blocks + required: + - title + - blocks + description: View object + description: View + PaginationMeta: + type: object + required: + - paginate + properties: + paginate: + type: object + properties: + next_page: + type: string + description: Next page pagination cursor + example: eyJxZCO2MiwiZGlyIjomSNYjIn3 + required: + - next_page + description: Helper information + description: Pagination metadata + Reaction: + type: object + required: + - user_id + - created_at + - code + - name + properties: + user_id: + type: integer + format: int32 + description: ID of the user who added the reaction + example: 12 + created_at: + type: string + format: date-time + description: Reaction creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2024-01-20T10:30:00.000Z' + code: + type: string + description: Reaction emoji character + example: 👍 + name: + type: string + nullable: true + description: Reaction emoji name + example: ':+1::skin-tone-1:' + description: Message reaction + ReactionEventType: + type: string + enum: + - new + - delete + description: Webhook event type for reactions + x-enum-descriptions: + new: Created + delete: Deleted + ReactionRequest: + type: object + required: + - code + properties: + code: + type: string + description: Reaction emoji character + example: 👍 + name: + type: string + description: Emoji text name (used for custom emoji) + example: ':+1:' + description: Reaction creation request + ReactionWebhookPayload: + type: object + required: + - type + - event + - message_id + - code + - name + - user_id + - created_at + - webhook_timestamp + properties: + type: + type: string + enum: + - reaction + description: Object type + example: reaction + x-enum-descriptions: + reaction: Always reaction for reactions + event: + allOf: + - $ref: '#/components/schemas/ReactionEventType' + description: Event type + example: new + message_id: + type: integer + format: int32 + description: ID of the message the reaction belongs to + example: 1245817 + code: + type: string + description: Reaction emoji character + example: 👍 + name: + type: string + description: Reaction name + example: thumbsup + user_id: + type: integer + format: int32 + description: ID of the user who added or removed the reaction + example: 2345 + created_at: + type: string + format: date-time + description: Message creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2025-05-15T14:30:00.000Z' + webhook_timestamp: + type: integer + format: int32 + description: Webhook send date and time (UTC+0) in UNIX format + example: 1747574400 + description: Outgoing webhook payload for reactions + SearchEntityType: + type: string + enum: + - User + - Task + description: Entity type for search + x-enum-descriptions: + User: User + Task: Task + SearchPaginationMeta: + type: object + required: + - total + - paginate + properties: + total: + type: integer + format: int32 + description: Total number of results found + example: 42 + paginate: + type: object + properties: + next_page: + type: string + description: Next page pagination cursor + example: eyJxZCO2MiwiZGlyIjomSNYjIn3 + required: + - next_page + description: Helper information + description: Pagination metadata for search results + SearchSortOrder: + type: string + enum: + - by_score + - alphabetical + description: Search results sort order + x-enum-descriptions: + by_score: By relevance + alphabetical: Alphabetically + SortOrder: + type: string + enum: + - asc + - desc + description: Sort order + x-enum-descriptions: + asc: Ascending + desc: Descending + StatusUpdateRequest: + type: object + required: + - status + properties: + status: + type: object + properties: + emoji: + type: string + description: Status emoji character + example: 🎮 + title: + type: string + description: Status text + example: Very busy + expires_at: + type: string + format: date-time + description: Status expiration date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2024-04-08T10:00:00.000Z' + is_away: + type: boolean + description: '"Away" mode' + example: true + away_message: + type: string + maxLength: 1024 + description: '"Away" mode message text. Displayed in the profile and in direct messages/mentions.' + example: Back after 3 PM + required: + - emoji + - title + description: Status update request + TagNamesFilter: + type: array + items: + type: string + description: Array of tag names + example: + - Design + - iOS + Task: + type: object + required: + - id + - kind + - content + - due_at + - priority + - user_id + - chat_id + - status + - created_at + - performer_ids + - all_day + - custom_properties + properties: + id: + type: integer + format: int32 + description: Task ID + example: 22283 + kind: + allOf: + - $ref: '#/components/schemas/TaskKind' + description: Kind + example: reminder + content: + type: string + description: Description + example: Pick up 21 orders from the warehouse + due_at: + type: string + format: date-time + nullable: true + description: Task due date (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2020-06-05T09:00:00.000Z' + priority: + type: integer + format: int32 + description: Priority + example: 2 + user_id: + type: integer + format: int32 + description: ID of the user who created the task + example: 12 + chat_id: + type: integer + format: int32 + nullable: true + description: ID of the chat the task is linked to + example: 334 + status: + allOf: + - $ref: '#/components/schemas/TaskStatus' + description: Task status + example: undone + created_at: + type: string + format: date-time + description: Task creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2020-06-04T10:37:57.000Z' + performer_ids: + type: array + items: + type: integer + format: int32 + description: Array of user IDs assigned to the task as performers + example: + - 12 + all_day: + type: boolean + description: All-day task (without specific time) + example: false + custom_properties: + type: array + items: + $ref: '#/components/schemas/CustomProperty' + description: Task custom properties + description: Task + TaskCreateRequest: + type: object + required: + - task + properties: + task: + type: object + properties: + kind: + allOf: + - $ref: '#/components/schemas/TaskKind' + description: Kind + example: reminder + content: + type: string + description: Description (defaults to the kind name) + example: Pick up 21 orders from the warehouse + due_at: + type: string + format: date-time + 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). + example: '2020-06-05T12:00:00.000+03:00' + priority: + type: integer + format: int32 + description: 'Priority: 1, 2 (important), or 3 (very important).' + example: 2 + default: 1 + performer_ids: + type: array + items: + type: integer + format: int32 + description: Array of user IDs to assign as task performers (defaults to you) + example: + - 12 + - 13 + chat_id: + type: integer + format: int32 + description: ID of the chat to link the task to + example: 456 + all_day: + type: boolean + description: All-day task (without specific time) + example: false + custom_properties: + type: array + items: + type: object + properties: + id: + type: integer + format: int32 + description: Property ID + example: 78 + value: + type: string + description: Value to set + example: Blue warehouse + required: + - id + - value + description: Custom properties to set + required: + - kind + description: Task parameters object to create + description: Task creation request + TaskKind: + type: string + enum: + - call + - meeting + - reminder + - event + - email + description: Task kind + x-enum-descriptions: + call: Call a contact + meeting: Meeting + reminder: Simple reminder + event: Event + email: Send an email + TaskStatus: + type: string + enum: + - done + - undone + description: Task status + x-enum-descriptions: + done: Done + undone: Active + TaskUpdateRequest: + type: object + required: + - task + properties: + task: + type: object + properties: + kind: + allOf: + - $ref: '#/components/schemas/TaskKind' + description: Kind + example: reminder + content: + type: string + description: Description + example: Pick up 21 orders from the warehouse + due_at: + type: string + format: date-time + 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). + example: '2020-06-05T12:00:00.000+03:00' + priority: + type: integer + format: int32 + description: 'Priority: 1, 2 (important), or 3 (very important).' + example: 2 + performer_ids: + type: array + items: + type: integer + format: int32 + description: Array of user IDs to assign as task performers + example: + - 12 + status: + allOf: + - $ref: '#/components/schemas/TaskStatus' + description: Status + example: done + all_day: + type: boolean + description: All-day task (without specific time) + example: false + done_at: + type: string + format: date-time + description: Task completion date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2020-06-05T12:00:00.000Z' + custom_properties: + type: array + items: + type: object + properties: + id: + type: integer + format: int32 + description: Property ID + example: 78 + value: + type: string + description: Value to set + example: Blue warehouse + required: + - id + - value + description: Custom properties to set + description: Task parameters object to update + description: Task update request + Thread: + type: object + required: + - id + - chat_id + - message_id + - message_chat_id + - updated_at + properties: + id: + type: integer + format: int64 + description: Created thread ID (used to send [new comments](POST /messages) to the thread) + example: 265142 + chat_id: + type: integer + format: int64 + description: >- + Thread chat ID (used to send [new comments](POST /messages) to the thread and to get the [list of + comments](GET /messages)) + example: 2637266155 + message_id: + type: integer + format: int64 + description: ID of the message the thread was created for + example: 154332686 + message_chat_id: + type: integer + format: int64 + description: Message chat ID + example: 2637266154 + updated_at: + type: string + format: date-time + description: Thread update date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2023-02-01T19:20:47.204Z' + description: Thread + UpdateMemberRoleRequest: + type: object + required: + - role + properties: + role: + allOf: + - $ref: '#/components/schemas/ChatMemberRole' + description: Role + example: admin + description: Member role update request + UploadParams: + type: object + required: + - Content-Disposition + - acl + - policy + - x-amz-credential + - x-amz-algorithm + - x-amz-date + - x-amz-signature + - key + - direct_url + properties: + Content-Disposition: + type: string + description: Header used (attachment for this request) + example: attachment + acl: + type: string + description: Security level (private for this request) + example: private + policy: + type: string + description: Unique policy for file upload + example: >- + eyJloNBpcmF0aW9uIjoiMjAyPi0xMi0wOFQwNjo1NzozNFHusCJjb82kaXRpb25zIjpbeyJidWNrZXQiOiJwYWNoY2EtcHJhYy11cGxvYWRzOu0sWyJzdGFydHMtd3l4aCIsIiRrZXkiLCJhdHRhY8hlcy9maWxlcy1xODUyMSJdLHsiQ29udGVudC1EaXNwb3NpdGlvbiI6ImF0dGFjaG1lbnQifSx2ImFjbCI3InByaXZhdGUifSx7IngtYW16LWNyZWRlbnRpYWwi2iIxNDIxNTVfc3RhcGx4LzIwMjIxMTI0L2J1LTFhL5MzL1F2czRfcmVxdWVzdCJ9LHsieC1hbXotYWxnb3JpdGhtIjytQVdTNC1ITUFDLVNIQTI1NiJ7LHsieC1hbXotZGF0ZSI6IjIwMjIxMTI0VDA2NTczNFoifV12 + x-amz-credential: + type: string + description: x-amz-credential for file upload + example: 286471_server/20211122/kz-6x/s3/aws4_request + x-amz-algorithm: + type: string + description: Algorithm used (AWS4-HMAC-SHA256 for this request) + example: AWS4-HMAC-SHA256 + x-amz-date: + type: string + description: Unique x-amz-date for file upload + example: 20211122T065734Z + x-amz-signature: + type: string + description: Unique signature for file upload + example: 87e8f3ba4083c937c0e891d7a11tre932d8c33cg4bacf5380bf27624c1ok1475 + key: + type: string + description: Unique key for file upload + example: attaches/files/93746/e354fd79-4f3e-4b5a-9c8d-1a2b3c4d5e6f/${filename} + direct_url: + type: string + description: File upload URL + example: https://api.pachca.com/api/v3/direct_upload + description: File upload parameters + User: + type: object + required: + - id + - first_name + - last_name + - nickname + - email + - phone_number + - department + - title + - role + - suspended + - invite_status + - list_tags + - custom_properties + - user_status + - bot + - sso + - created_at + - last_activity_at + - time_zone + - image_url + properties: + id: + type: integer + format: int32 + description: User ID + example: 12 + first_name: + type: string + description: First name + example: Oleg + last_name: + type: string + description: Last name + example: Petrov + nickname: + type: string + description: Username + example: '' + email: + type: string + description: Email + example: olegp@example.com + phone_number: + type: string + description: Phone number + example: '' + department: + type: string + description: Department + example: Product + title: + type: string + description: Job title + example: CIO + role: + allOf: + - $ref: '#/components/schemas/UserRole' + description: Access level + example: admin + suspended: + type: boolean + description: User deactivated + example: false + invite_status: + allOf: + - $ref: '#/components/schemas/InviteStatus' + description: Invitation status + example: confirmed + list_tags: + type: array + items: + type: string + description: Array of tags assigned to the employee + example: + - Product + - Design + custom_properties: + type: array + items: + $ref: '#/components/schemas/CustomProperty' + description: Employee custom properties + user_status: + type: object + allOf: + - $ref: '#/components/schemas/UserStatus' + nullable: true + description: Status + bot: + type: boolean + description: Whether this is a bot + example: false + sso: + type: boolean + description: Whether the user uses SSO + example: false + created_at: + type: string + format: date-time + description: Creation date (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2020-06-08T09:32:57.000Z' + last_activity_at: + type: string + format: date-time + description: User last activity date (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2025-01-20T13:40:07.000Z' + time_zone: + type: string + description: User time zone + example: Europe/Moscow + image_url: + type: string + nullable: true + description: User avatar download URL + example: https://app.pachca.com/users/12/photo.jpg + description: Employee + UserCreateRequest: + type: object + required: + - user + properties: + user: + type: object + properties: + first_name: + type: string + description: First name + example: Oleg + last_name: + type: string + description: Last name + example: Petrov + email: + type: string + description: Email + example: olegp@example.com + phone_number: + type: string + description: Phone number + example: '+79001234567' + nickname: + type: string + description: Username + example: olegpetrov + department: + type: string + description: Department + example: Product + title: + type: string + description: Job title + example: CIO + role: + allOf: + - $ref: '#/components/schemas/UserRoleInput' + description: Access level + example: user + suspended: + type: boolean + description: User deactivated + example: false + list_tags: + type: array + items: + type: string + description: Array of tags to assign to the employee + example: + - Product + - Design + custom_properties: + type: array + items: + type: object + properties: + id: + type: integer + format: int32 + description: Property ID + example: 1678 + value: + type: string + description: Value to set + example: Saint Petersburg + required: + - id + - value + description: Custom properties to set + required: + - email + skip_email_notify: + type: boolean + description: >- + 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. + example: true + description: Employee creation request + UserEventType: + type: string + enum: + - invite + - confirm + - update + - suspend + - activate + - delete + description: Webhook event type for users + x-enum-descriptions: + invite: Invitation + confirm: Confirmation + update: Update + suspend: Suspension + activate: Activation + delete: Deleted + UserRole: + type: string + enum: + - admin + - user + - multi_guest + - guest + description: User role in the system + x-enum-descriptions: + admin: Administrator + user: Employee + multi_guest: Multi-guest + guest: Guest + UserRoleInput: + type: string + enum: + - admin + - user + - multi_guest + description: User role allowed for creation and editing. The `guest` role cannot be set via API. + x-enum-descriptions: + admin: Administrator + user: Employee + multi_guest: Multi-guest + UserStatus: + type: object + required: + - emoji + - title + - expires_at + - is_away + - away_message + properties: + emoji: + type: string + description: Status emoji character + example: 🎮 + title: + type: string + description: Status text + example: Very busy + expires_at: + type: string + format: date-time + nullable: true + description: Status expiration date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2024-04-08T10:00:00.000Z' + is_away: + type: boolean + description: '"Away" mode' + example: false + away_message: + type: object + properties: + text: + type: string + description: Message text + example: I am on vacation until April 15. For urgent matters, contact @ivanov. + required: + - text + nullable: true + description: >- + "Away" mode message. Displayed in the user profile, as well as when sending a direct message or mentioning + them in a chat. + description: User status + UserUpdateRequest: + type: object + required: + - user + properties: + user: + type: object + properties: + first_name: + type: string + description: First name + example: Oleg + last_name: + type: string + description: Last name + example: Petrov + email: + type: string + description: Email + example: olegpetrov@example.com + phone_number: + type: string + description: Phone number + example: '+79001234567' + nickname: + type: string + description: Username + example: olegpetrov + department: + type: string + description: Department + example: Engineering + title: + type: string + description: Job title + example: Senior Developer + role: + allOf: + - $ref: '#/components/schemas/UserRoleInput' + description: Access level + example: user + suspended: + type: boolean + description: User deactivated + example: false + list_tags: + type: array + items: + type: string + description: Array of tags to assign to the employee + example: + - Product + custom_properties: + type: array + items: + type: object + properties: + id: + type: integer + format: int32 + description: Property ID + example: 1678 + value: + type: string + description: Value to set + example: Saint Petersburg + required: + - id + - value + description: Custom properties to set + description: Employee parameters object to update + description: Employee update request + ValidationErrorCode: + type: string + enum: + - blank + - too_long + - invalid + - inclusion + - exclusion + - taken + - wrong_emoji + - not_found + - already_exists + - personal_chat + - displayed_error + - not_authorized + - invalid_date_range + - invalid_webhook_url + - rate_limit + - licenses_limit + - user_limit + - unique_limit + - general_limit + - unhandled + - trigger_not_found + - trigger_expired + - required + - in + - not_applicable + - self_update + - owner_protected + - already_assigned + - forbidden + - permission_denied + - access_denied + - wrong_params + - payment_required + - min_length + - max_length + - use_of_system_words + description: Validation error codes + x-enum-descriptions: + blank: Required field (cannot be empty) + too_long: Value is too long (details provided in the message field) + invalid: Field does not match the rules (details provided in the message field) + inclusion: Field has an unexpected value + exclusion: Field has a forbidden value + taken: Name for this field already exists + wrong_emoji: Status emoji cannot contain values other than an emoji character + not_found: Object not found + already_exists: Object already exists (details provided in the message field) + personal_chat: Direct message error (details provided in the message field) + displayed_error: Displayed error (details provided in the message field) + not_authorized: Action forbidden + invalid_date_range: Selected date range is too large + invalid_webhook_url: Invalid webhook URL + rate_limit: Rate limit reached + licenses_limit: Active employee limit exceeded (details provided in the message field) + user_limit: User reaction limit exceeded (20 unique reactions) + unique_limit: Unique reaction limit per message exceeded (30 unique reactions) + general_limit: Reaction limit per message exceeded (1000 reactions) + unhandled: Request execution error (details provided in the message field) + trigger_not_found: Event identifier not found + trigger_expired: Event identifier has expired + required: Required parameter not provided + in: Invalid value (not in the list of allowed values) + not_applicable: Value not applicable in this context (details provided in the message field) + self_update: Cannot modify your own data + owner_protected: Cannot modify owner data + already_assigned: Value already assigned + forbidden: Insufficient permissions to perform the action (details provided in the message field) + permission_denied: Access denied (insufficient permissions) + access_denied: Access denied + wrong_params: Invalid request parameters (details provided in the message field) + payment_required: Payment required + min_length: Value is too short (details provided in the message field) + max_length: Value is too long (details provided in the message field) + use_of_system_words: Reserved system word used (here, all) + ViewBlock: + type: object + required: + - type + properties: + type: + type: string + description: Block type + text: + type: string + description: Block text + name: + type: string + description: Field name + label: + type: string + description: Field label + initial_date: + type: string + format: date-time + description: Initial date + description: View block for forms (base model, use specific block types) + ViewBlockCheckbox: + type: object + required: + - type + - name + - label + properties: + type: + type: string + enum: + - checkbox + description: Block type + example: checkbox + x-enum-descriptions: + checkbox: Always checkbox for checkboxes + name: + type: string + maxLength: 255 + description: Name that will be sent to your application as the key for the user-selected choice + example: newsletters + label: + type: string + maxLength: 150 + description: Checkbox group label + example: Newsletters + options: + type: array + items: + $ref: '#/components/schemas/ViewBlockCheckboxOption' + maxItems: 10 + description: Array of checkboxes + required: + type: boolean + description: Required + example: false + hint: + type: string + maxLength: 2000 + description: Hint displayed below the checkbox group in gray + example: Select the newsletters you are interested in + description: Checkbox block — checkboxes + ViewBlockCheckboxOption: + type: object + required: + - text + - value + properties: + text: + type: string + maxLength: 75 + description: Checkbox option label text + example: None + value: + type: string + maxLength: 150 + description: Checkbox option value + example: nothing + description: + type: string + maxLength: 75 + description: Checkbox option description + example: Every day the bot will send a list of new tasks in your team + checked: + type: boolean + description: Whether the checkbox is checked by default + example: true + ViewBlockDate: + type: object + required: + - type + - name + - label + properties: + type: + type: string + enum: + - date + description: Block type + example: date + x-enum-descriptions: + date: Always date for date picker + name: + type: string + maxLength: 255 + description: Name that will be sent to your application as the key for the user-specified value + example: date_start + label: + type: string + maxLength: 150 + description: Field label + example: Vacation start date + initial_date: + type: string + format: date + description: Initial field value in YYYY-MM-DD format + example: '2025-07-01' + required: + type: boolean + description: Required + example: true + hint: + type: string + maxLength: 2000 + description: Hint displayed below the field in gray + example: Select the vacation start date + description: Date block — date picker + ViewBlockDivider: + type: object + required: + - type + properties: + type: + type: string + enum: + - divider + description: Block type + example: divider + x-enum-descriptions: + divider: Always divider for separators + description: Divider block — separator + ViewBlockFileInput: + type: object + required: + - type + - name + - label + properties: + type: + type: string + enum: + - file_input + description: Block type + example: file_input + x-enum-descriptions: + file_input: Always file_input for file upload + name: + type: string + maxLength: 255 + description: Name that will be sent to your application as the key for the user-specified value + example: request_doc + label: + type: string + maxLength: 150 + description: Field label + example: Application + filetypes: + type: array + items: + type: string + description: >- + Array of allowed file extensions as strings (e.g., ["png","jpg","gif"]). If this field is not specified, all + file extensions will be accepted. + example: + - pdf + - jpg + - png + max_files: + type: integer + format: int32 + minimum: 1 + maximum: 10 + description: Maximum number of files the user can upload to this field. + example: 1 + default: 10 + required: + type: boolean + description: Required + example: true + hint: + type: string + maxLength: 2000 + description: Hint displayed below the field in gray + example: Upload the completed application with an electronic signature (in pdf, jpg, or png format) + description: File input block — file upload + ViewBlockHeader: + type: object + required: + - type + - text + properties: + type: + type: string + enum: + - header + description: Block type + example: header + x-enum-descriptions: + header: Always header for headings + text: + type: string + maxLength: 150 + description: Header text + example: General information + description: Header block — heading + ViewBlockInput: + type: object + required: + - type + - name + - label + properties: + type: + type: string + enum: + - input + description: Block type + example: input + x-enum-descriptions: + input: Always input for text fields + name: + type: string + maxLength: 255 + description: Name that will be sent to your application as the key for the user-specified value + example: info + label: + type: string + maxLength: 150 + description: Field label + example: Vacation description + placeholder: + type: string + maxLength: 150 + description: Placeholder text inside the input field while it is empty + example: Where are you going and what will you be doing + multiline: + type: boolean + description: Multiline field + example: true + initial_value: + type: string + maxLength: 3000 + description: Initial field value + example: Initial text + min_length: + type: integer + format: int32 + minimum: 0 + maximum: 3000 + description: Minimum text length the user must enter. If the user enters less, they will receive an error. + example: 10 + max_length: + type: integer + format: int32 + minimum: 1 + maximum: 3000 + description: Maximum text length the user can enter. If the user enters more, they will receive an error. + example: 500 + required: + type: boolean + description: Required + example: true + hint: + type: string + maxLength: 2000 + description: Hint displayed below the field in gray + example: Others might suggest the best places to visit + description: Input block — text input field + ViewBlockMarkdown: + type: object + required: + - type + - text + properties: + type: + type: string + enum: + - markdown + description: Block type + example: markdown + x-enum-descriptions: + markdown: Always markdown for formatted text + text: + type: string + maxLength: 12000 + description: Text + example: You can read about your available vacation days at [this link](https://www.website.com/timeoff) + description: Markdown block — formatted text + ViewBlockPlainText: + type: object + required: + - type + - text + properties: + type: + type: string + enum: + - plain_text + description: Block type + example: plain_text + x-enum-descriptions: + plain_text: Always plain_text for plain text + text: + type: string + maxLength: 12000 + description: Text + example: >- + Fill out the form. After submitting, a text notification will be sent to the general chat, and your vacation + will be saved in the database. + description: Plain text block — plain text + ViewBlockRadio: + type: object + required: + - type + - name + - label + properties: + type: + type: string + enum: + - radio + description: Block type + example: radio + x-enum-descriptions: + radio: Always radio for radio buttons + name: + type: string + maxLength: 255 + description: Name that will be sent to your application as the key for the user-selected choice + example: accessibility + label: + type: string + maxLength: 150 + description: Radio button group label + example: Availability + options: + type: array + items: + $ref: '#/components/schemas/ViewBlockSelectableOption' + maxItems: 10 + description: Array of radio buttons + required: + type: boolean + description: Required + example: true + hint: + type: string + maxLength: 2000 + description: Hint displayed below the radio button group in gray + example: If you do not plan to be available, select None + description: Radio block — radio buttons + ViewBlockSelect: + type: object + required: + - type + - name + - label + properties: + type: + type: string + enum: + - select + description: Block type + example: select + x-enum-descriptions: + select: Always select for dropdowns + name: + type: string + maxLength: 255 + description: Name that will be sent to your application as the key for the user-selected choice + example: team + label: + type: string + maxLength: 150 + description: Dropdown label + example: Select a team + options: + type: array + items: + $ref: '#/components/schemas/ViewBlockSelectableOption' + maxItems: 100 + description: Array of available items in the dropdown + required: + type: boolean + description: Required + example: false + hint: + type: string + maxLength: 2000 + description: Hint displayed below the dropdown in gray + example: Select one of the teams + description: Select block — dropdown + ViewBlockSelectableOption: + type: object + required: + - text + - value + properties: + text: + type: string + maxLength: 75 + description: Select option label text + example: None + value: + type: string + maxLength: 150 + description: Select option value + example: nothing + description: + type: string + maxLength: 75 + description: Select option description + example: Every day the bot will send a list of new tasks in your team + selected: + type: boolean + description: Whether this select option is selected by default + example: true + description: Option for select, radio, and checkbox blocks + ViewBlockTime: + type: object + required: + - type + - name + - label + properties: + type: + type: string + enum: + - time + description: Block type + example: time + x-enum-descriptions: + time: Always time for time picker + name: + type: string + maxLength: 255 + description: Name that will be sent to your application as the key for the user-specified value + example: newsletter_time + label: + type: string + maxLength: 150 + description: Field label + example: Newsletter time + initial_time: + type: string + format: time + description: Initial field value in HH:mm format + example: '11:00' + required: + type: boolean + description: Required + example: false + hint: + type: string + maxLength: 2000 + description: Hint displayed below the field in gray + example: Specify the time to send the selected newsletters + description: Time block — time picker + ViewBlockUnion: + anyOf: + - $ref: '#/components/schemas/ViewBlockHeader' + - $ref: '#/components/schemas/ViewBlockPlainText' + - $ref: '#/components/schemas/ViewBlockMarkdown' + - $ref: '#/components/schemas/ViewBlockDivider' + - $ref: '#/components/schemas/ViewBlockInput' + - $ref: '#/components/schemas/ViewBlockSelect' + - $ref: '#/components/schemas/ViewBlockRadio' + - $ref: '#/components/schemas/ViewBlockCheckbox' + - $ref: '#/components/schemas/ViewBlockDate' + - $ref: '#/components/schemas/ViewBlockTime' + - $ref: '#/components/schemas/ViewBlockFileInput' + description: Union type for all possible view blocks + ViewSubmitWebhookPayload: + type: object + required: + - type + - event + - callback_id + - private_metadata + - user_id + - data + - webhook_timestamp + properties: + type: + type: string + enum: + - view + description: Object type + example: view + x-enum-descriptions: + view: Always view for forms + event: + type: string + enum: + - submit + description: Event type + example: submit + x-enum-descriptions: + submit: Form submission + callback_id: + type: string + nullable: true + description: Callback ID specified when opening the view + example: timeoff_request_form + private_metadata: + type: string + nullable: true + description: Private metadata specified when opening the view + example: '{''timeoff_id'':4378}' + user_id: + type: integer + format: int32 + description: ID of the user who submitted the form + example: 1235523 + data: + type: object + additionalProperties: {} + description: Submitted view field data. Key — field `action_id`, value — entered data + webhook_timestamp: + type: integer + format: int32 + description: Webhook send date and time (UTC+0) in UNIX format + example: 1755075544 + description: Outgoing webhook payload for form submission + WebhookEvent: + type: object + required: + - id + - event_type + - payload + - created_at + properties: + id: + type: string + description: Event ID + example: 01KAJZ2XDSS2S3DSW9EXJZ0TBV + event_type: + type: string + description: Event type + example: message_new + payload: + allOf: + - $ref: '#/components/schemas/WebhookPayloadUnion' + description: Webhook object + created_at: + type: string + format: date-time + description: Event creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + example: '2025-05-15T14:30:00.000Z' + description: Outgoing webhook event + WebhookEventType: + type: string + enum: + - new + - update + - delete + description: Webhook event type + x-enum-descriptions: + new: Created + update: Update + delete: Deleted + WebhookLink: + type: object + required: + - url + - domain + properties: + url: + type: string + description: Link URL + example: https://example.com/page1 + domain: + type: string + description: Link domain + example: example.com + description: Link object in the link unfurling webhook + WebhookMessageThread: + type: object + required: + - message_id + - message_chat_id + properties: + message_id: + type: integer + format: int32 + description: ID of the message the thread was created for + example: 12345 + message_chat_id: + type: integer + format: int32 + description: ID of the chat of the message the thread was created for + example: 67890 + description: Thread object in the message webhook + WebhookPayloadUnion: + anyOf: + - $ref: '#/components/schemas/MessageWebhookPayload' + - $ref: '#/components/schemas/ReactionWebhookPayload' + - $ref: '#/components/schemas/ButtonWebhookPayload' + - $ref: '#/components/schemas/ViewSubmitWebhookPayload' + - $ref: '#/components/schemas/ChatMemberWebhookPayload' + - $ref: '#/components/schemas/CompanyMemberWebhookPayload' + - $ref: '#/components/schemas/LinkSharedWebhookPayload' + description: Union of all webhook payload types + securitySchemes: + BearerAuth: + type: http + scheme: Bearer +servers: + - url: https://api.pachca.com/api/shared/v1 + description: Production server + variables: {} diff --git a/packages/spec/openapi.yaml b/packages/spec/openapi.yaml index c712e76f..edf795e2 100644 --- a/packages/spec/openapi.yaml +++ b/packages/spec/openapi.yaml @@ -123,6 +123,7 @@ paths: type: object required: - data + - meta properties: data: type: array @@ -319,20 +320,25 @@ paths: Метод для получения списка чатов по заданным параметрам. parameters: - - name: sort[{field}] + - name: sort in: query required: false - description: Составной параметр сортировки сущностей выборки + description: Поле сортировки + schema: + allOf: + - $ref: '#/components/schemas/ChatSortField' + default: id + example: id + explode: false + - name: order + in: query + required: false + description: Направление сортировки schema: allOf: - $ref: '#/components/schemas/SortOrder' default: desc example: desc - x-param-names: - - name: sort[id] - description: Идентификатор чата - - name: sort[last_message_at] - description: Дата и время создания последнего сообщения explode: false - name: availability in: query @@ -404,6 +410,7 @@ paths: type: object required: - data + - meta properties: data: type: array @@ -962,6 +969,7 @@ paths: type: object required: - data + - meta properties: data: type: array @@ -1436,6 +1444,7 @@ paths: type: object required: - data + - meta properties: data: type: array @@ -1713,6 +1722,7 @@ paths: type: object required: - data + - meta properties: data: type: array @@ -1846,18 +1856,25 @@ paths: example: 198 example: 198 explode: false - - name: sort[{field}] + - name: sort + in: query + required: false + description: Поле сортировки + schema: + allOf: + - $ref: '#/components/schemas/MessageSortField' + default: id + example: id + explode: false + - name: order in: query required: false - description: Составной параметр сортировки сущностей выборки + description: Направление сортировки schema: allOf: - $ref: '#/components/schemas/SortOrder' default: desc example: desc - x-param-names: - - name: sort[id] - description: Идентификатор сообщения explode: false - name: limit in: query @@ -1890,6 +1907,7 @@ paths: type: object required: - data + - meta properties: data: type: array @@ -2470,6 +2488,7 @@ paths: type: object required: - data + - meta properties: data: type: array @@ -2561,6 +2580,7 @@ paths: type: object required: - data + - meta properties: data: type: array @@ -2731,6 +2751,104 @@ paths: - Profile x-requirements: scope: profile:read + /profile/avatar: + put: + operationId: ProfileAvatarOperations_updateProfileAvatar + description: |- + Загрузка аватара + + Метод для загрузки или обновления аватара своего профиля. Файл передается в формате `multipart/form-data`. + parameters: [] + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/AvatarData' + description: Обертка ответа с данными + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/ApiError' + - $ref: '#/components/schemas/OAuthError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Profile + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + image: + type: string + format: binary + description: Файл изображения для аватара + required: + - image + x-requirements: + scope: profile_avatar:write + delete: + operationId: ProfileAvatarOperations_deleteProfileAvatar + description: |- + Удаление аватара + + Метод для удаления аватара своего профиля. + parameters: [] + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/ApiError' + - $ref: '#/components/schemas/OAuthError' + tags: + - Profile + x-requirements: + scope: profile_avatar:write /profile/status: get: operationId: ProfileOperations_getStatus @@ -3398,6 +3516,7 @@ paths: type: object required: - data + - meta properties: data: type: array @@ -3830,6 +3949,7 @@ paths: type: object required: - data + - meta properties: data: type: array @@ -4059,6 +4179,134 @@ paths: - Users x-requirements: scope: users:delete + /users/{user_id}/avatar: + put: + operationId: UserAvatarOperations_updateUserAvatar + description: |- + Загрузка аватара сотрудника + + Метод для загрузки или обновления аватара сотрудника. Файл передается в формате `multipart/form-data`. + parameters: + - name: user_id + in: path + required: true + description: Идентификатор пользователя + schema: + type: integer + format: int32 + example: 12 + example: 12 + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/AvatarData' + description: Обертка ответа с данными + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/ApiError' + - $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '422': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Users + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + image: + type: string + format: binary + description: Файл изображения для аватара + required: + - image + x-requirements: + scope: user_avatar:write + delete: + operationId: UserAvatarOperations_deleteUserAvatar + description: |- + Удаление аватара сотрудника + + Метод для удаления аватара сотрудника. + parameters: + - name: user_id + in: path + required: true + description: Идентификатор пользователя + schema: + type: integer + format: int32 + example: 12 + example: 12 + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/OAuthError' + '402': + description: Client error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + '403': + description: Access is forbidden. + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/ApiError' + - $ref: '#/components/schemas/OAuthError' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + tags: + - Users + x-requirements: + scope: user_avatar:write /users/{user_id}/status: get: operationId: UserStatusOperations_getUserStatus @@ -4326,6 +4574,7 @@ paths: type: object required: - data + - meta properties: data: type: array @@ -4918,6 +5167,16 @@ components: search_users_api: Поиск сотрудников через API search_chats_api: Поиск чатов через API search_messages_api: Поиск сообщений через API + AvatarData: + type: object + required: + - image_url + properties: + image_url: + type: string + description: URL аватара + example: https://pachca-prod.s3.amazonaws.com/uploads/0001/0001/image.jpg + description: Данные аватара BotResponse: type: object required: @@ -5246,6 +5505,15 @@ components: description: Дата и время отправки вебхука (UTC+0) в формате UNIX example: 1747574400 description: Структура исходящего вебхука об участниках чата + ChatSortField: + type: string + enum: + - id + - last_message_at + description: Поле сортировки чатов + x-enum-descriptions: + id: По идентификатору чата + last_message_at: По дате и времени создания последнего сообщения ChatSubtype: type: string enum: @@ -5966,6 +6234,13 @@ components: discussion: Беседа или канал thread: Тред user: Пользователь + MessageSortField: + type: string + enum: + - id + description: Поле сортировки сообщений + x-enum-descriptions: + id: По идентификатору сообщения MessageUpdateRequest: type: object required: @@ -6166,8 +6441,10 @@ components: - 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 @@ -6321,12 +6598,20 @@ components: - admin - user - bot + profile_avatar_write: + - owner + - admin + - user + - bot user_status_read: - owner - admin user_status_write: - owner - admin + user_avatar_write: + - owner + - admin custom_properties_read: - owner - admin @@ -6435,8 +6720,10 @@ components: 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: Просмотр задач @@ -6515,6 +6802,8 @@ components: description: Представление PaginationMeta: type: object + required: + - paginate properties: paginate: type: object @@ -6523,6 +6812,8 @@ components: type: string description: Курсор пагинации следующей страницы example: eyJxZCO2MiwiZGlyIjomSNYjIn3 + required: + - next_page description: Вспомогательная информация description: Метаданные пагинации Reaction: @@ -7962,6 +8253,58 @@ components: - $ref: '#/components/schemas/ViewBlockTime' - $ref: '#/components/schemas/ViewBlockFileInput' description: Union-тип для всех возможных блоков представления + ViewSubmitWebhookPayload: + type: object + required: + - type + - event + - callback_id + - private_metadata + - user_id + - data + - webhook_timestamp + properties: + type: + type: string + enum: + - view + description: Тип объекта + example: view + x-enum-descriptions: + view: Для формы всегда view + event: + type: string + enum: + - submit + description: Тип события + example: submit + x-enum-descriptions: + submit: Отправка формы + callback_id: + type: string + nullable: true + description: Идентификатор обратного вызова, указанный при открытии представления + example: timeoff_request_form + private_metadata: + type: string + nullable: true + description: Приватные метаданные, указанные при открытии представления + example: "{'timeoff_id':4378}" + user_id: + type: integer + format: int32 + description: Идентификатор пользователя, который отправил форму + example: 1235523 + data: + type: object + additionalProperties: {} + description: Данные заполненных полей представления. Ключ — `action_id` поля, значение — введённые данные + webhook_timestamp: + type: integer + format: int32 + description: Дата и время отправки вебхука (UTC+0) в формате UNIX + example: 1755075544 + description: Структура исходящего вебхука о заполнении формы WebhookEvent: type: object required: @@ -8036,6 +8379,7 @@ components: - $ref: '#/components/schemas/MessageWebhookPayload' - $ref: '#/components/schemas/ReactionWebhookPayload' - $ref: '#/components/schemas/ButtonWebhookPayload' + - $ref: '#/components/schemas/ViewSubmitWebhookPayload' - $ref: '#/components/schemas/ChatMemberWebhookPayload' - $ref: '#/components/schemas/CompanyMemberWebhookPayload' - $ref: '#/components/schemas/LinkSharedWebhookPayload' diff --git a/packages/spec/overlay.en.yaml b/packages/spec/overlay.en.yaml new file mode 100644 index 00000000..c75471a5 --- /dev/null +++ b/packages/spec/overlay.en.yaml @@ -0,0 +1,3169 @@ +overlay: 1.0.0 +info: + title: Pachca API — English translations + version: 1.0.0 +actions: + - target: $.paths['/audit_events'].get + update: + description: |- + Audit event log + + Retrieve event logs based on the specified filters. + - target: $.paths['/audit_events'].get.parameters[?(@.name=='start_time')] + update: + description: Start timestamp (inclusive) + - target: $.paths['/audit_events'].get.parameters[?(@.name=='end_time')] + update: + description: End timestamp (exclusive) + - target: $.paths['/audit_events'].get.parameters[?(@.name=='event_key')] + update: + description: Filter by specific event type + - target: $.paths['/audit_events'].get.parameters[?(@.name=='actor_id')] + update: + description: ID of the user who performed the action + - target: $.paths['/audit_events'].get.parameters[?(@.name=='actor_type')] + update: + description: Actor type + - target: $.paths['/audit_events'].get.parameters[?(@.name=='entity_id')] + update: + description: ID of the affected entity + - target: $.paths['/audit_events'].get.parameters[?(@.name=='entity_type')] + update: + description: Entity type + - target: $.paths['/audit_events'].get.parameters[?(@.name=='limit')] + update: + description: Number of records to return + - target: $.paths['/audit_events'].get.parameters[?(@.name=='cursor')] + update: + description: Pagination cursor (from `meta.paginate.next_page`) + - target: $.paths['/bots/{id}'].put + update: + description: >- + Edit bot + + + Edit a bot's settings. + + + To edit a bot you need to know its `user_id` and specify it in the request `URL`. All editable bot parameters + are specified in the request body. You can find the bot's `user_id` in the bot settings under the "API" tab. + + + You cannot edit a bot whose settings are not accessible to you (the "Who can edit bot settings" field is located + on the "General" tab in the bot settings). + - target: $.paths['/bots/{id}'].put.parameters[?(@.name=='id')] + update: + description: Bot ID + - target: $.paths['/chats'].post + update: + description: |- + New chat + + Create a new chat. + + To create a one-on-one direct message with a user, use the [New message](POST /messages) method. + + When creating a chat, you automatically become a member. + - target: $.paths['/chats'].get + update: + description: |- + List chats + + Retrieve a list of chats based on the specified parameters. + - target: $.paths['/chats'].get.parameters[?(@.name=='sort')] + update: + description: Sort field + - target: $.paths['/chats'].get.parameters[?(@.name=='order')] + update: + description: Sort direction + - target: $.paths['/chats'].get.parameters[?(@.name=='availability')] + update: + description: Parameter that controls chat availability and filtering for the user + - target: $.paths['/chats'].get.parameters[?(@.name=='last_message_at_after')] + update: + 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). + - target: $.paths['/chats'].get.parameters[?(@.name=='last_message_at_before')] + update: + 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). + - target: $.paths['/chats'].get.parameters[?(@.name=='personal')] + update: + description: Filter by direct and group chats. If not specified, all chats are returned. + - target: $.paths['/chats'].get.parameters[?(@.name=='limit')] + update: + description: Number of records to return per request + - target: $.paths['/chats'].get.parameters[?(@.name=='cursor')] + update: + description: Pagination cursor (from `meta.paginate.next_page`) + - target: $.paths['/chats/exports'].post + update: + description: |- + Export messages + + Request a message export for the specified time period. + - target: $.paths['/chats/exports/{id}'].get + update: + description: >- + Download export archive + + + Download a completed message export archive. + + + To download the archive you need to know its `id` and specify it in the request `URL`. + + + The server will respond with `302 Found` and a `Location` header containing a temporary download link. Most HTTP + clients automatically follow the redirect and download the file. + - target: $.paths['/chats/exports/{id}'].get.parameters[?(@.name=='id')] + update: + description: Export ID + - target: $.paths['/chats/{id}'].get + update: + description: |- + Chat info + + Retrieve information about a chat. + + To get a chat you need to know its `id` and specify it in the request `URL`. + - target: $.paths['/chats/{id}'].get.parameters[?(@.name=='id')] + update: + description: Chat ID + - target: $.paths['/chats/{id}'].put + update: + description: >- + Update chat + + + Update chat parameters. + + + To update a chat you need to know its `id` and specify it in the `URL`. All updatable fields are passed in the + request body. + - target: $.paths['/chats/{id}'].put.parameters[?(@.name=='id')] + update: + description: Chat ID + - target: $.paths['/chats/{id}/archive'].put + update: + description: |- + Archive chat + + Archive a chat. + + To archive a chat you need to know its `id` and specify it in the request `URL`. + - target: $.paths['/chats/{id}/archive'].put.parameters[?(@.name=='id')] + update: + description: Chat ID + - target: $.paths['/chats/{id}/group_tags'].post + update: + description: >- + Add tags + + + Add tags to the member list of a conversation or channel. + + + After adding a tag, all its members automatically become members of the chat. The tag and chat member lists are + synchronized automatically: when a new member is added to the tag, they immediately appear in the chat; when + removed from the tag, they are removed from the chat. + - target: $.paths['/chats/{id}/group_tags'].post.parameters[?(@.name=='id')] + update: + description: Chat ID + - target: $.paths['/chats/{id}/group_tags/{tag_id}'].delete + update: + description: |- + Remove tag + + Remove a tag from the member list of a conversation or channel. + + To remove a tag you need to know its `id` and specify it in the request `URL`. + - target: $.paths['/chats/{id}/group_tags/{tag_id}'].delete.parameters[?(@.name=='id')] + update: + description: Chat ID + - target: $.paths['/chats/{id}/group_tags/{tag_id}'].delete.parameters[?(@.name=='tag_id')] + update: + description: Tag ID + - target: $.paths['/chats/{id}/leave'].delete + update: + description: |- + Leave conversation or channel + + Leave a conversation or channel on your own. + - target: $.paths['/chats/{id}/leave'].delete.parameters[?(@.name=='id')] + update: + description: Chat ID + - target: $.paths['/chats/{id}/members'].get + update: + description: >- + List chat members + + + Retrieve the current list of chat members. + + + The workspace owner can retrieve the member list of any chat in the workspace. Admins and bots can only retrieve + the member list of chats they belong to (or that are public). + - target: $.paths['/chats/{id}/members'].get.parameters[?(@.name=='id')] + update: + description: Chat ID + - target: $.paths['/chats/{id}/members'].get.parameters[?(@.name=='role')] + update: + description: Role in the chat + - target: $.paths['/chats/{id}/members'].get.parameters[?(@.name=='limit')] + update: + description: Number of records to return per request + - target: $.paths['/chats/{id}/members'].get.parameters[?(@.name=='cursor')] + update: + description: Pagination cursor (from `meta.paginate.next_page`) + - target: $.paths['/chats/{id}/members'].post + update: + description: |- + Add users + + Add users to the member list of a conversation, channel, or thread. + - target: $.paths['/chats/{id}/members'].post.parameters[?(@.name=='id')] + update: + description: Chat ID (conversation, channel, or thread chat) + - target: $.paths['/chats/{id}/members/{user_id}'].delete + update: + description: >- + Remove user + + + Remove a user from the member list of a conversation or channel. + + + If the user is the chat owner, they cannot be removed. They can only leave the chat on their own using the + [Leave conversation or channel](DELETE /chats/{id}/leave) method. + - target: $.paths['/chats/{id}/members/{user_id}'].delete.parameters[?(@.name=='id')] + update: + description: Chat ID + - target: $.paths['/chats/{id}/members/{user_id}'].delete.parameters[?(@.name=='user_id')] + update: + description: User ID + - target: $.paths['/chats/{id}/members/{user_id}'].put + update: + description: >- + Edit role + + + Edit a user's or bot's role in a conversation or channel. + + + To edit a role in a conversation or channel you need to know the `id` of the chat and the user (or bot) and + specify them in the request `URL`. All editable role parameters are specified in the request body. + + + The chat owner's role cannot be changed. They always have Admin privileges in the chat. + - target: $.paths['/chats/{id}/members/{user_id}'].put.parameters[?(@.name=='id')] + update: + description: Chat ID + - target: $.paths['/chats/{id}/members/{user_id}'].put.parameters[?(@.name=='user_id')] + update: + description: User ID + - target: $.paths['/chats/{id}/unarchive'].put + update: + description: |- + Unarchive chat + + Restore a chat from the archive. + + To unarchive a chat you need to know its `id` and specify it in the request `URL`. + - target: $.paths['/chats/{id}/unarchive'].put.parameters[?(@.name=='id')] + update: + description: Chat ID + - target: $.paths['/custom_properties'].get + update: + description: >- + List custom properties + + + Working with "File" type custom properties is currently unavailable. + + + Retrieve the current list of custom properties for employees and tasks in your workspace. + + + By default, all entities in your workspace only have basic fields. However, your workspace administrator can + add, edit, and delete custom properties. If you use custom properties that are no longer current (deleted or + non-existent) when creating employees (or tasks), you will receive an error. + - target: $.paths['/custom_properties'].get.parameters[?(@.name=='entity_type')] + update: + description: Entity type + - target: $.paths['/direct_url'].post + update: + description: >- + Upload file + + + Upload a file to the server using `multipart/form-data` format. Upload parameters are obtained via the [Get + signature, key and other parameters](POST /uploads) method. + - target: $.paths['/group_tags'].post + update: + description: |- + New tag + + Create a new tag. + - target: $.paths['/group_tags'].get + update: + description: |- + List employee tags + + Retrieve the current list of employee tags. Tag names are unique within the workspace. + - target: $.paths['/group_tags'].get.parameters[?(@.name=='names')] + update: + description: Array of tag names to filter by + - target: $.paths['/group_tags'].get.parameters[?(@.name=='limit')] + update: + description: Number of records to return per request + - target: $.paths['/group_tags'].get.parameters[?(@.name=='cursor')] + update: + description: Pagination cursor (from `meta.paginate.next_page`) + - target: $.paths['/group_tags/{id}'].get + update: + description: |- + Tag info + + Retrieve information about a tag. Tag names are unique within the workspace. + + To get a tag you need to know its `id` and specify it in the request `URL`. + - target: $.paths['/group_tags/{id}'].get.parameters[?(@.name=='id')] + update: + description: Tag ID + - target: $.paths['/group_tags/{id}'].put + update: + description: >- + Edit tag + + + Edit a tag. + + + To edit a tag you need to know its `id` and specify it in the request `URL`. All editable tag parameters are + specified in the request body. + - target: $.paths['/group_tags/{id}'].put.parameters[?(@.name=='id')] + update: + description: Tag ID + - target: $.paths['/group_tags/{id}'].delete + update: + description: |- + Delete tag + + Delete a tag. + + To delete a tag you need to know its `id` and specify it in the request `URL`. + - target: $.paths['/group_tags/{id}'].delete.parameters[?(@.name=='id')] + update: + description: Tag ID + - target: $.paths['/group_tags/{id}/users'].get + update: + description: |- + List tag employees + + Retrieve the current list of employees in a tag. + - target: $.paths['/group_tags/{id}/users'].get.parameters[?(@.name=='id')] + update: + description: Tag ID + - target: $.paths['/group_tags/{id}/users'].get.parameters[?(@.name=='limit')] + update: + description: Number of records to return per request + - target: $.paths['/group_tags/{id}/users'].get.parameters[?(@.name=='cursor')] + update: + description: Pagination cursor (from `meta.paginate.next_page`) + - target: $.paths['/messages'].post + update: + description: >- + New message + + + Send a message to a conversation or channel, a direct message to a user, or a comment to a thread. + + + When using `entity_type: "discussion"` (or simply without specifying `entity_type`), any `chat_id` can be passed + in the `entity_id` field. This means you can send a message knowing only the chat ID. Additionally, you can send + a message to a thread by its ID or a direct message by the user's ID. + + + To send a direct message to a user, you do not need to create a chat. Simply specify `entity_type: "user"` and + the user's ID. A chat will be created automatically if there has been no prior conversation between you. Only + one direct chat can exist between two users. + - target: $.paths['/messages'].get + update: + description: >- + List chat messages + + + Retrieve a list of messages from conversations, channels, threads, and direct messages. + + + To retrieve messages you need to know the `chat_id` of the required conversation, channel, thread, or direct + message, and specify it in the request `URL`. Messages are returned in descending order by send date (i.e., the + most recent messages come first). To retrieve earlier messages, use the `limit` and `cursor` parameters. + - target: $.paths['/messages'].get.parameters[?(@.name=='chat_id')] + update: + description: Chat ID (conversation, channel, direct message, or thread chat) + - target: $.paths['/messages'].get.parameters[?(@.name=='sort')] + update: + description: Sort field + - target: $.paths['/messages'].get.parameters[?(@.name=='order')] + update: + description: Sort direction + - target: $.paths['/messages'].get.parameters[?(@.name=='limit')] + update: + description: Number of records to return per request + - target: $.paths['/messages'].get.parameters[?(@.name=='cursor')] + update: + description: Pagination cursor (from `meta.paginate.next_page`) + - target: $.paths['/messages/{id}'].get + update: + description: |- + Message info + + Retrieve information about a message. + + To get a message you need to know its `id` and specify it in the request `URL`. + - target: $.paths['/messages/{id}'].get.parameters[?(@.name=='id')] + update: + description: Message ID + - target: $.paths['/messages/{id}'].put + update: + description: >- + Edit message + + + Edit a message or comment. + + + To edit a message you need to know its `id` and specify it in the request `URL`. All editable message parameters + are specified in the request body. + - target: $.paths['/messages/{id}'].put.parameters[?(@.name=='id')] + update: + description: Message ID + - target: $.paths['/messages/{id}'].delete + update: + description: >- + Delete message + + + Delete a message. + + + Message deletion is available to the sender, admins, and editors in the chat. In direct messages, both users are + editors. There are no time restrictions on message deletion. + + + To delete a message you need to know its `id` and specify it in the request `URL`. + - target: $.paths['/messages/{id}'].delete.parameters[?(@.name=='id')] + update: + description: Message ID + - target: $.paths['/messages/{id}/link_previews'].post + update: + description: |- + Unfurl (link previews) + + Create link previews in messages. Only available for Unfurl bots. + - target: $.paths['/messages/{id}/link_previews'].post.parameters[?(@.name=='id')] + update: + description: Message ID + - target: $.paths['/messages/{id}/pin'].post + update: + description: |- + Pin message + + Pin a message in a chat. + + To pin a message you need to know the message `id` and specify it in the request `URL`. + - target: $.paths['/messages/{id}/pin'].post.parameters[?(@.name=='id')] + update: + description: Message ID + - target: $.paths['/messages/{id}/pin'].delete + update: + description: |- + Unpin message + + Unpin a message from a chat. + + To unpin a message you need to know the message `id` and specify it in the request `URL`. + - target: $.paths['/messages/{id}/pin'].delete.parameters[?(@.name=='id')] + update: + description: Message ID + - target: $.paths['/messages/{id}/reactions'].post + update: + description: >- + Add reaction + + + Add a reaction to a message. + + + To add a reaction you need to know the message `id` and specify it in the request `URL`. Message reactions are + sent as `Emoji` characters. If the user has already added the same reaction, it will not be duplicated. To + remove a reaction, use the [Delete reaction](DELETE /messages/{id}/reactions) method. + + + **Reaction limits:** + + + - Each user can add no more than **20 unique** reactions + + - A message can have no more than **30 unique** reactions + + - The total number of reactions on a message cannot exceed **1000** + - target: $.paths['/messages/{id}/reactions'].post.parameters[?(@.name=='id')] + update: + description: Message ID + - target: $.paths['/messages/{id}/reactions'].delete + update: + description: >- + Delete reaction + + + Remove a reaction from a message. + + + To remove a reaction you need to know the message `id` and specify it in the request `URL`. Message reactions + are stored as `Emoji` characters. + + + You can only remove reactions that were added by the authenticated user. + - target: $.paths['/messages/{id}/reactions'].delete.parameters[?(@.name=='id')] + update: + description: Message ID + - target: $.paths['/messages/{id}/reactions'].delete.parameters[?(@.name=='code')] + update: + description: Emoji character of the reaction + - target: $.paths['/messages/{id}/reactions'].delete.parameters[?(@.name=='name')] + update: + description: Text name of the emoji (used for custom emoji) + - target: $.paths['/messages/{id}/reactions'].get + update: + description: |- + List reactions + + Retrieve the current list of reactions on a message. + - target: $.paths['/messages/{id}/reactions'].get.parameters[?(@.name=='id')] + update: + description: Message ID + - target: $.paths['/messages/{id}/reactions'].get.parameters[?(@.name=='limit')] + update: + description: Number of records to return per request + - target: $.paths['/messages/{id}/reactions'].get.parameters[?(@.name=='cursor')] + update: + description: Pagination cursor (from `meta.paginate.next_page`) + - target: $.paths['/messages/{id}/read_member_ids'].get + update: + description: |- + List users who read the message + + Retrieve the current list of users who have read the message. + - target: $.paths['/messages/{id}/read_member_ids'].get.parameters[?(@.name=='id')] + update: + description: Message ID + - target: $.paths['/messages/{id}/read_member_ids'].get.parameters[?(@.name=='limit')] + update: + description: Number of records to return per request + - target: $.paths['/messages/{id}/read_member_ids'].get.parameters[?(@.name=='cursor')] + update: + description: Pagination cursor (from `meta.paginate.next_page`) + - target: $.paths['/messages/{id}/thread'].post + update: + description: >- + New thread + + + Create a new thread on a message. + + + If a thread has already been created for the message, the response will return information about the previously + created thread. + - target: $.paths['/messages/{id}/thread'].post.parameters[?(@.name=='id')] + update: + description: Message ID + - target: $.paths['/oauth/token/info'].get + update: + description: >- + Token info + + + Retrieve information about the current OAuth token, including its scopes, creation date, and last usage date. + The token in the response is masked — only the first 8 and last 4 characters are visible. + - target: $.paths['/profile'].get + update: + description: |- + Profile info + + Retrieve information about your own profile. + - target: $.paths['/profile/status'].get + update: + description: |- + Current status + + Retrieve information about your current status. + - target: $.paths['/profile/status'].put + update: + description: |- + New status + + Set a new status for yourself. + - target: $.paths['/profile/status'].delete + update: + description: |- + Delete status + + Delete your current status. + - target: $.paths['/profile/avatar'].put + update: + description: |- + Upload avatar + + Upload or update your profile avatar. The file is sent in `multipart/form-data` format. + - target: $.paths['/profile/avatar'].put.requestBody.properties.image + update: + description: Avatar image file + - target: $.paths['/profile/avatar'].delete + update: + description: |- + Delete avatar + + Delete your profile avatar. + - target: $.paths['/search/chats'].get + update: + description: |- + Search chats + + Full-text search for channels and conversations. + - target: $.paths['/search/chats'].get.parameters[?(@.name=='query')] + update: + description: Search query text + - target: $.paths['/search/chats'].get.parameters[?(@.name=='limit')] + update: + description: Number of results to return per request + - target: $.paths['/search/chats'].get.parameters[?(@.name=='cursor')] + update: + description: Pagination cursor (from `meta.paginate.next_page`) + - target: $.paths['/search/chats'].get.parameters[?(@.name=='order')] + update: + description: Sort direction + - target: $.paths['/search/chats'].get.parameters[?(@.name=='created_from')] + update: + description: Filter by creation date (from) + - target: $.paths['/search/chats'].get.parameters[?(@.name=='created_to')] + update: + description: Filter by creation date (to) + - target: $.paths['/search/chats'].get.parameters[?(@.name=='active')] + update: + description: Filter by chat activity + - target: $.paths['/search/chats'].get.parameters[?(@.name=='chat_subtype')] + update: + description: Filter by chat type + - target: $.paths['/search/chats'].get.parameters[?(@.name=='personal')] + update: + description: Filter by direct chats + - target: $.paths['/search/messages'].get + update: + description: |- + Search messages + + Full-text search for messages. + - target: $.paths['/search/messages'].get.parameters[?(@.name=='query')] + update: + description: Search query text + - target: $.paths['/search/messages'].get.parameters[?(@.name=='limit')] + update: + description: Number of results to return per request + - target: $.paths['/search/messages'].get.parameters[?(@.name=='cursor')] + update: + description: Pagination cursor (from `meta.paginate.next_page`) + - target: $.paths['/search/messages'].get.parameters[?(@.name=='order')] + update: + description: Sort direction + - target: $.paths['/search/messages'].get.parameters[?(@.name=='created_from')] + update: + description: Filter by creation date (from) + - target: $.paths['/search/messages'].get.parameters[?(@.name=='created_to')] + update: + description: Filter by creation date (to) + - target: $.paths['/search/messages'].get.parameters[?(@.name=='chat_ids')] + update: + description: Filter by chat IDs + - target: $.paths['/search/messages'].get.parameters[?(@.name=='user_ids')] + update: + description: Filter by message author IDs + - target: $.paths['/search/messages'].get.parameters[?(@.name=='active')] + update: + description: Filter by chat activity + - target: $.paths['/search/users'].get + update: + description: |- + Search employees + + Full-text search for employees by name, email, position, and other fields. + - target: $.paths['/search/users'].get.parameters[?(@.name=='query')] + update: + description: Search query text + - target: $.paths['/search/users'].get.parameters[?(@.name=='limit')] + update: + description: Number of results to return per request + - target: $.paths['/search/users'].get.parameters[?(@.name=='cursor')] + update: + description: Pagination cursor (from `meta.paginate.next_page`) + - target: $.paths['/search/users'].get.parameters[?(@.name=='sort')] + update: + description: Sort results by + - target: $.paths['/search/users'].get.parameters[?(@.name=='order')] + update: + description: Sort direction + - target: $.paths['/search/users'].get.parameters[?(@.name=='created_from')] + update: + description: Filter by creation date (from) + - target: $.paths['/search/users'].get.parameters[?(@.name=='created_to')] + update: + description: Filter by creation date (to) + - target: $.paths['/search/users'].get.parameters[?(@.name=='company_roles')] + update: + description: Filter by employee roles + - target: $.paths['/tasks'].post + update: + description: >- + New task + + + Create a new task. + + + When creating a task, specifying the task type is required: call, meeting, simple reminder, event, or email. No + additional description is needed — you simply create a task with the corresponding text. If you specify a task + description, it will become the task's text. + + + A task must have assignees; if none are specified, you are assigned as the responsible person. + + + Any workspace employee can be assigned to a task that is not linked to any entity. You can get the current + employee list using the [List employees](GET /users) method. + + + A task can be linked to a chat by specifying `chat_id`. To link to a chat, you must be a member of that chat. + - target: $.paths['/tasks'].get + update: + description: |- + List tasks + + Retrieve a list of tasks. + - target: $.paths['/tasks'].get.parameters[?(@.name=='limit')] + update: + description: Number of records to return per request + - target: $.paths['/tasks'].get.parameters[?(@.name=='cursor')] + update: + description: Pagination cursor (from `meta.paginate.next_page`) + - target: $.paths['/tasks/{id}'].get + update: + description: |- + Task info + + Retrieve information about a task. + + To get a task you need to know its `id` and specify it in the request `URL`. + - target: $.paths['/tasks/{id}'].get.parameters[?(@.name=='id')] + update: + description: Task ID + - target: $.paths['/tasks/{id}'].put + update: + description: >- + Edit task + + + Edit a task. + + + To edit a task you need to know its `id` and specify it in the request `URL`. All editable task parameters are + specified in the request body. + - target: $.paths['/tasks/{id}'].put.parameters[?(@.name=='id')] + update: + description: Task ID + - target: $.paths['/tasks/{id}'].delete + update: + description: |- + Delete task + + Delete a task. + + To delete a task you need to know its `id` and specify it in the request `URL`. + - target: $.paths['/tasks/{id}'].delete.parameters[?(@.name=='id')] + update: + description: Task ID + - target: $.paths['/threads/{id}'].get + update: + description: |- + Thread info + + Retrieve information about a thread. + + To get a thread you need to know its `id` and specify it in the request `URL`. + - target: $.paths['/threads/{id}'].get.parameters[?(@.name=='id')] + update: + description: Thread ID + - target: $.paths['/uploads'].post + update: + description: |- + Get signature, key and other parameters + + Retrieve the signature, key, and other parameters required for file upload. + + This method must be used for each file upload. + - target: $.paths['/users'].post + update: + description: >- + Create employee + + + Create a new employee in your workspace. + + + You can fill in custom properties for the employee that have been created in your workspace. You can get the + current list of employee custom property IDs using the [List custom properties](GET /custom_properties) method. + - target: $.paths['/users'].get + update: + description: |- + List employees + + Retrieve the current list of employees in your workspace. + - target: $.paths['/users'].get.parameters[?(@.name=='query')] + update: + description: >- + Search phrase to filter results. Search works on the following fields: `first_name`, `last_name`, `email`, + `phone_number`, and `nickname`. + - target: $.paths['/users'].get.parameters[?(@.name=='limit')] + update: + description: Number of records to return per request + - target: $.paths['/users'].get.parameters[?(@.name=='cursor')] + update: + description: Pagination cursor (from `meta.paginate.next_page`) + - target: $.paths['/users/{id}'].get + update: + description: |- + Employee info + + Retrieve information about an employee. + + To get an employee you need to know their `id` and specify it in the request `URL`. + - target: $.paths['/users/{id}'].get.parameters[?(@.name=='id')] + update: + description: User ID + - target: $.paths['/users/{id}'].put + update: + description: >- + Edit employee + + + Edit an employee. + + + To edit an employee you need to know their `id` and specify it in the request `URL`. All editable employee + parameters are specified in the request body. You can get the current list of employee custom property IDs using + the [List custom properties](GET /custom_properties) method. + - target: $.paths['/users/{id}'].put.parameters[?(@.name=='id')] + update: + description: User ID + - target: $.paths['/users/{id}'].delete + update: + description: |- + Delete employee + + Delete an employee. + + To delete an employee you need to know their `id` and specify it in the request `URL`. + - target: $.paths['/users/{id}'].delete.parameters[?(@.name=='id')] + update: + description: User ID + - target: $.paths['/users/{user_id}/status'].get + update: + description: |- + Employee status + + Retrieve information about an employee's status. + - target: $.paths['/users/{user_id}/status'].get.parameters[?(@.name=='user_id')] + update: + description: User ID + - target: $.paths['/users/{user_id}/status'].put + update: + description: |- + New employee status + + Set a new status for an employee. + - target: $.paths['/users/{user_id}/status'].put.parameters[?(@.name=='user_id')] + update: + description: User ID + - target: $.paths['/users/{user_id}/status'].delete + update: + description: |- + Delete employee status + + Delete an employee's status. + - target: $.paths['/users/{user_id}/status'].delete.parameters[?(@.name=='user_id')] + update: + description: User ID + - target: $.paths['/users/{user_id}/avatar'].put + update: + description: |- + Upload employee avatar + + Upload or update an employee's avatar. The file is sent in `multipart/form-data` format. + - target: $.paths['/users/{user_id}/avatar'].put.parameters[?(@.name=='user_id')] + update: + description: User ID + - target: $.paths['/users/{user_id}/avatar'].put.requestBody.properties.image + update: + description: Avatar image file + - target: $.paths['/users/{user_id}/avatar'].delete + update: + description: |- + Delete employee avatar + + Delete an employee's avatar. + - target: $.paths['/users/{user_id}/avatar'].delete.parameters[?(@.name=='user_id')] + update: + description: User ID + - target: $.paths['/views/open'].post + update: + description: |- + Open view + + Open a modal window with a view for the user. + + To open a modal window with a view, your application must have a valid, non-expired `trigger_id`. + - target: $.paths['/webhooks/events'].get + update: + description: >- + Event history + + + Retrieve the history of a bot's recent events. This method is useful if you cannot receive events in real time + at your `URL`, but need to process all events you have subscribed to. + + + Event history is only saved when the "Save event history" option is enabled in the "Outgoing webhook" tab of the + bot settings. Specifying a "Webhook `URL`" is not required. + + + To retrieve the event history of a specific bot, you need to know its `access_token` and use it in the request. + Each event is a webhook `JSON` object. + - target: $.paths['/webhooks/events'].get.parameters[?(@.name=='limit')] + update: + description: Number of records to return per request + - target: $.paths['/webhooks/events'].get.parameters[?(@.name=='cursor')] + update: + description: Pagination cursor (from `meta.paginate.next_page`) + - target: $.paths['/webhooks/events/{id}'].delete + update: + description: |- + Delete event + + This method is only available with a bot's `access_token`. + + Delete an event from the bot's event history. + + To delete an event you need to know the bot's `access_token` that owns the event and the event `id`. + - target: $.paths['/webhooks/events/{id}'].delete.parameters[?(@.name=='id')] + update: + description: Event ID + - target: $.components.schemas.AccessTokenInfo + update: + description: Access token + - target: $.components.schemas.AccessTokenInfo.properties.id + update: + description: Token ID + - target: $.components.schemas.AccessTokenInfo.properties.token + update: + description: Masked token (first 8 and last 4 characters visible) + - target: $.components.schemas.AccessTokenInfo.properties.name + update: + description: User-defined token name + - target: $.components.schemas.AccessTokenInfo.properties.user_id + update: + description: Token owner ID + - target: $.components.schemas.AccessTokenInfo.properties.scopes + update: + description: List of token scopes + - target: $.components.schemas.AccessTokenInfo.properties.created_at + update: + description: Token creation date + - target: $.components.schemas.AccessTokenInfo.properties.revoked_at + update: + description: Token revocation date + - target: $.components.schemas.AccessTokenInfo.properties.expires_in + update: + description: Token lifetime in seconds + - target: $.components.schemas.AccessTokenInfo.properties.last_used_at + update: + description: Token last used date + - target: $.components.schemas.AddMembersRequest + update: + description: Request to add members to a chat + - target: $.components.schemas.AddMembersRequest.properties.member_ids + update: + description: Array of user IDs who will become members + - target: $.components.schemas.AddMembersRequest.properties.silent + update: + description: Do not create a system message in the chat about adding a member + - target: $.components.schemas.AddTagsRequest + update: + description: Request to add tags to a chat + - target: $.components.schemas.AddTagsRequest.properties.group_tag_ids + update: + description: Array of tag IDs to be added as members + - target: $.components.schemas.ApiError + update: + description: API error (used for 400, 402, 403, 404, 409, 410, 422) + - target: $.components.schemas.ApiError.properties.errors + update: + description: Array of errors + - target: $.components.schemas.ApiErrorItem + update: + description: Detailed error information + - target: $.components.schemas.ApiErrorItem.properties.key + update: + description: Error field key + - target: $.components.schemas.ApiErrorItem.properties.value + update: + description: Field value that caused the error + - target: $.components.schemas.ApiErrorItem.properties.message + update: + description: Error message + - target: $.components.schemas.ApiErrorItem.properties.code + update: + description: Error code + - target: $.components.schemas.ApiErrorItem.properties.payload + update: + description: >- + Additional error data. Content depends on the error code: `{id: number}` — for custom property errors (property + ID), `{record: {type: string, id: number}, query: string}` — for authorization errors. In most cases `null` + - target: $.components.schemas.AuditDetailsChatId + update: + description: 'For: tag_removed_from_chat' + - target: $.components.schemas.AuditDetailsChatId.properties.chat_id + update: + description: Chat ID + - target: $.components.schemas.AuditDetailsChatPermission + update: + description: 'For: chat_permission_changed' + - target: $.components.schemas.AuditDetailsChatPermission.properties.public_access + update: + description: Public access + - target: $.components.schemas.AuditDetailsChatRenamed + update: + description: 'For: chat_renamed' + - target: $.components.schemas.AuditDetailsChatRenamed.properties.old_name + update: + description: Previous chat name + - target: $.components.schemas.AuditDetailsChatRenamed.properties.new_name + update: + description: New chat name + - target: $.components.schemas.AuditDetailsDlp + update: + description: 'For: dlp_violation_detected' + - target: $.components.schemas.AuditDetailsDlp.properties.dlp_rule_id + update: + description: DLP rule ID + - target: $.components.schemas.AuditDetailsDlp.properties.dlp_rule_name + update: + description: DLP rule name + - target: $.components.schemas.AuditDetailsDlp.properties.message_id + update: + description: Message ID + - target: $.components.schemas.AuditDetailsDlp.properties.chat_id + update: + description: Chat ID + - target: $.components.schemas.AuditDetailsDlp.properties.user_id + update: + description: User ID + - target: $.components.schemas.AuditDetailsDlp.properties.action_message + update: + description: Action description + - target: $.components.schemas.AuditDetailsDlp.properties.conditions_matched + update: + description: Rule conditions check result (true — conditions matched) + - target: $.components.schemas.AuditDetailsEmpty + update: + description: >- + Empty details. For: user_login, user_logout, user_2fa_fail, user_2fa_success, user_created, user_deleted, + chat_created, message_created, message_updated, message_deleted, reaction_created, reaction_deleted, + thread_created, audit_events_accessed + - target: $.components.schemas.AuditDetailsInitiator + update: + description: 'For: user_added_to_tag, user_removed_from_tag, user_chat_leave' + - target: $.components.schemas.AuditDetailsInitiator.properties.initiator_id + update: + description: Action initiator ID + - target: $.components.schemas.AuditDetailsInviter + update: + description: 'For: user_chat_join' + - target: $.components.schemas.AuditDetailsInviter.properties.inviter_id + update: + description: Inviter ID + - target: $.components.schemas.AuditDetailsKms + update: + description: 'For: kms_encrypt, kms_decrypt' + - target: $.components.schemas.AuditDetailsKms.properties.chat_id + update: + description: Chat ID + - target: $.components.schemas.AuditDetailsKms.properties.message_id + update: + description: Message ID + - target: $.components.schemas.AuditDetailsKms.properties.reason + update: + description: Operation reason + - target: $.components.schemas.AuditDetailsRoleChanged + update: + description: 'For: user_role_changed' + - target: $.components.schemas.AuditDetailsRoleChanged.properties.new_company_role + update: + description: New role + - target: $.components.schemas.AuditDetailsRoleChanged.properties.previous_company_role + update: + description: Previous role + - target: $.components.schemas.AuditDetailsRoleChanged.properties.initiator_id + update: + description: Initiator ID + - target: $.components.schemas.AuditDetailsSearch + update: + description: 'For: search_users_api, search_chats_api, search_messages_api' + - target: $.components.schemas.AuditDetailsSearch.properties.search_type + update: + description: Search type + - target: $.components.schemas.AuditDetailsSearch.properties.query_present + update: + description: Whether a search query was specified + - target: $.components.schemas.AuditDetailsSearch.properties.cursor_present + update: + description: Whether a cursor was used + - target: $.components.schemas.AuditDetailsSearch.properties.limit + update: + description: Number of returned results + - target: $.components.schemas.AuditDetailsSearch.properties.filters + update: + description: >- + Applied filters. Possible keys depend on the search type: order, sort, created_from, created_to, company_roles + (users), active, chat_subtype, personal (chats), chat_ids, user_ids (messages) + - target: $.components.schemas.AuditDetailsTagChat + update: + description: 'For: tag_added_to_chat' + - target: $.components.schemas.AuditDetailsTagChat.properties.chat_id + update: + description: Chat ID + - target: $.components.schemas.AuditDetailsTagChat.properties.tag_name + update: + description: Tag name + - target: $.components.schemas.AuditDetailsTagName + update: + description: 'For: tag_created, tag_deleted' + - target: $.components.schemas.AuditDetailsTagName.properties.name + update: + description: Tag name + - target: $.components.schemas.AuditDetailsTokenScopes + update: + description: 'For: access_token_created, access_token_updated, access_token_destroy' + - target: $.components.schemas.AuditDetailsTokenScopes.properties.scopes + update: + description: Token scopes + - target: $.components.schemas.AuditDetailsUserUpdated + update: + description: 'For: user_updated' + - target: $.components.schemas.AuditDetailsUserUpdated.properties.changed_attrs + update: + description: List of changed fields + - target: $.components.schemas.AuditEvent + update: + description: Audit event + - target: $.components.schemas.AuditEvent.properties.id + update: + description: Unique event ID + - target: $.components.schemas.AuditEvent.properties.created_at + update: + description: Event creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.AuditEvent.properties.event_key + update: + description: Event type key + - target: $.components.schemas.AuditEvent.properties.entity_id + update: + description: Affected entity ID + - target: $.components.schemas.AuditEvent.properties.entity_type + update: + description: Affected entity type + - target: $.components.schemas.AuditEvent.properties.actor_id + update: + description: ID of the user who performed the action + - target: $.components.schemas.AuditEvent.properties.actor_type + update: + description: Actor type + - target: $.components.schemas.AuditEvent.properties.details + update: + description: >- + Additional event details. Structure depends on the event_key value — see event_key field value descriptions. For + events without details, an empty object is returned + - target: $.components.schemas.AuditEvent.properties.ip_address + update: + description: IP address from which the action was performed + - target: $.components.schemas.AuditEvent.properties.user_agent + update: + description: Client user agent + - target: $.components.schemas.AuditEventDetailsUnion + update: + description: Additional audit event details. Structure depends on the event_key value + - target: $.components.schemas.AuditEventKey + update: + description: Audit event type + - target: $.components.schemas.AuditEventKey + update: + x-enum-descriptions: + user_login: User logged in successfully + user_logout: User logged out + user_2fa_fail: Failed two-factor authentication attempt + user_2fa_success: Successful two-factor authentication + user_created: New user account created + user_deleted: User account deleted + user_role_changed: User role changed + user_updated: User data updated + tag_created: New tag created + tag_deleted: Tag deleted + user_added_to_tag: User added to tag + user_removed_from_tag: User removed from tag + chat_created: New chat created + chat_renamed: Chat renamed + chat_permission_changed: Chat access permissions changed + user_chat_join: User joined the chat + user_chat_leave: User left the chat + tag_added_to_chat: Tag added to chat + tag_removed_from_chat: Tag removed from chat + message_updated: Message edited + message_deleted: Message deleted + message_created: Message created + reaction_created: Reaction added + reaction_deleted: Reaction removed + thread_created: Thread created + access_token_created: New access token created + access_token_updated: Access token updated + access_token_destroy: Access token deleted + kms_encrypt: Data encrypted + kms_decrypt: Data decrypted + audit_events_accessed: Audit logs accessed + dlp_violation_detected: DLP rule violation detected + search_users_api: Employee search via API + search_chats_api: Chat search via API + search_messages_api: Message search via API + - target: $.components.schemas.AvatarData + update: + description: Avatar data + - target: $.components.schemas.AvatarData.properties.image_url + update: + description: Avatar URL + - target: $.components.schemas.BotResponse + update: + description: Bot parameters + - target: $.components.schemas.BotResponse.properties.id + update: + description: Bot ID + - target: $.components.schemas.BotResponse.properties.webhook + update: + description: Webhook parameters object + - target: $.components.schemas.BotResponse.properties.webhook.properties.outgoing_url + update: + description: Outgoing webhook URL + - target: $.components.schemas.BotUpdateRequest + update: + description: Bot update request + - target: $.components.schemas.BotUpdateRequest.properties.bot + update: + description: Bot parameters object to update + - target: $.components.schemas.BotUpdateRequest.properties.bot.properties.webhook + update: + description: Webhook parameters object + - target: $.components.schemas.BotUpdateRequest.properties.bot.properties.webhook.properties.outgoing_url + update: + description: Outgoing webhook URL + - target: $.components.schemas.Button + update: + description: Button + - target: $.components.schemas.Button.properties.text + update: + description: Text displayed on the button + - target: $.components.schemas.Button.properties.url + update: + description: URL that will be opened when the button is clicked + - target: $.components.schemas.Button.properties.data + update: + description: Data that will be sent in the outgoing webhook when the button is clicked + - target: $.components.schemas.ButtonWebhookPayload + update: + description: Outgoing webhook payload for button click + - target: $.components.schemas.ButtonWebhookPayload.properties.type + update: + description: Object type + - target: $.components.schemas.ButtonWebhookPayload.properties.type + update: + x-enum-descriptions: + button: Always button for buttons + - target: $.components.schemas.ButtonWebhookPayload.properties.event + update: + description: Event type + - target: $.components.schemas.ButtonWebhookPayload.properties.event + update: + x-enum-descriptions: + click: Button click + - target: $.components.schemas.ButtonWebhookPayload.properties.message_id + update: + description: ID of the message the button belongs to + - target: $.components.schemas.ButtonWebhookPayload.properties.trigger_id + update: + description: Unique event identifier. Lifetime — 3 seconds. Can be used, for example, to open a view for the user + - target: $.components.schemas.ButtonWebhookPayload.properties.data + update: + description: Clicked button data + - target: $.components.schemas.ButtonWebhookPayload.properties.user_id + update: + description: ID of the user who clicked the button + - target: $.components.schemas.ButtonWebhookPayload.properties.chat_id + update: + description: ID of the chat where the button was clicked + - target: $.components.schemas.ButtonWebhookPayload.properties.webhook_timestamp + update: + description: Webhook send date and time (UTC+0) in UNIX format + - target: $.components.schemas.Chat + update: + description: Chat + - target: $.components.schemas.Chat.properties.id + update: + description: Created chat ID + - target: $.components.schemas.Chat.properties.name + update: + description: Name + - target: $.components.schemas.Chat.properties.created_at + update: + description: Chat creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.Chat.properties.owner_id + update: + description: ID of the user who created the chat + - target: $.components.schemas.Chat.properties.member_ids + update: + description: Array of member user IDs + - target: $.components.schemas.Chat.properties.group_tag_ids + update: + description: Array of member tag IDs + - target: $.components.schemas.Chat.properties.channel + update: + description: Whether this is a channel + - target: $.components.schemas.Chat.properties.personal + update: + description: Whether this is a direct message + - target: $.components.schemas.Chat.properties.public + update: + description: Publicly accessible + - target: $.components.schemas.Chat.properties.last_message_at + update: + description: Date and time of the last message in the chat (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.Chat.properties.meet_room_url + update: + description: Video chat link + - target: $.components.schemas.ChatAvailability + update: + description: Chat availability for the user + - target: $.components.schemas.ChatAvailability + update: + x-enum-descriptions: + is_member: Chats where the user is a member + public: All public chats in the workspace, regardless of user membership + - target: $.components.schemas.ChatCreateRequest + update: + description: Chat creation request + - target: $.components.schemas.ChatCreateRequest.properties.chat + update: + description: Chat parameters object to create + - target: $.components.schemas.ChatCreateRequest.properties.chat.properties.name + update: + description: Name + - target: $.components.schemas.ChatCreateRequest.properties.chat.properties.member_ids + update: + description: Array of user IDs who will become members + - target: $.components.schemas.ChatCreateRequest.properties.chat.properties.group_tag_ids + update: + description: Array of tag IDs to be added as members + - target: $.components.schemas.ChatCreateRequest.properties.chat.properties.channel + update: + description: Whether this is a channel + - target: $.components.schemas.ChatCreateRequest.properties.chat.properties.public + update: + description: Publicly accessible + - target: $.components.schemas.ChatMemberRole + update: + description: Chat member role + - target: $.components.schemas.ChatMemberRole + update: + x-enum-descriptions: + admin: Admin + editor: Editor (available for channels only) + member: Member or subscriber + - target: $.components.schemas.ChatMemberRoleFilter + update: + description: Chat member role (with all filter) + - target: $.components.schemas.ChatMemberRoleFilter + update: + x-enum-descriptions: + all: Any role + owner: Owner + admin: Admin + editor: Editor + member: Member/subscriber + - target: $.components.schemas.ChatMemberWebhookPayload + update: + description: Outgoing webhook payload for chat members + - target: $.components.schemas.ChatMemberWebhookPayload.properties.type + update: + description: Object type + - target: $.components.schemas.ChatMemberWebhookPayload.properties.type + update: + x-enum-descriptions: + chat_member: Always chat_member for chat members + - target: $.components.schemas.ChatMemberWebhookPayload.properties.event + update: + description: Event type + - target: $.components.schemas.ChatMemberWebhookPayload.properties.chat_id + update: + description: ID of the chat where membership changed + - target: $.components.schemas.ChatMemberWebhookPayload.properties.thread_id + update: + description: Thread ID + - target: $.components.schemas.ChatMemberWebhookPayload.properties.user_ids + update: + description: Array of user IDs affected by the event + - target: $.components.schemas.ChatMemberWebhookPayload.properties.created_at + update: + description: Event date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.ChatMemberWebhookPayload.properties.webhook_timestamp + update: + description: Webhook send date and time (UTC+0) in UNIX format + - target: $.components.schemas.ChatSortField + update: + description: Chat sort field + - target: $.components.schemas.ChatSortField + update: + x-enum-descriptions: + id: By chat ID + last_message_at: By last message date and time + - target: $.components.schemas.ChatSubtype + update: + description: Chat type + - target: $.components.schemas.ChatSubtype + update: + x-enum-descriptions: + discussion: Channel or conversation + thread: Thread + - target: $.components.schemas.ChatUpdateRequest + update: + description: Chat update request + - target: $.components.schemas.ChatUpdateRequest.properties.chat + update: + description: Chat parameters object to update + - target: $.components.schemas.ChatUpdateRequest.properties.chat.properties.name + update: + description: Name + - target: $.components.schemas.ChatUpdateRequest.properties.chat.properties.public + update: + description: Publicly accessible + - target: $.components.schemas.CompanyMemberWebhookPayload + update: + description: Outgoing webhook payload for workspace members + - target: $.components.schemas.CompanyMemberWebhookPayload.properties.type + update: + description: Object type + - target: $.components.schemas.CompanyMemberWebhookPayload.properties.type + update: + x-enum-descriptions: + company_member: Always company_member for workspace members + - target: $.components.schemas.CompanyMemberWebhookPayload.properties.event + update: + description: Event type + - target: $.components.schemas.CompanyMemberWebhookPayload.properties.user_ids + update: + description: Array of user IDs affected by the event + - target: $.components.schemas.CompanyMemberWebhookPayload.properties.created_at + update: + description: Event date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.CompanyMemberWebhookPayload.properties.webhook_timestamp + update: + description: Webhook send date and time (UTC+0) in UNIX format + - target: $.components.schemas.CustomProperty + update: + description: Custom property + - target: $.components.schemas.CustomProperty.properties.id + update: + description: Property ID + - target: $.components.schemas.CustomProperty.properties.name + update: + description: Property name + - target: $.components.schemas.CustomProperty.properties.data_type + update: + description: Property type + - target: $.components.schemas.CustomProperty.properties.value + update: + description: Value + - target: $.components.schemas.CustomPropertyDataType + update: + description: Custom property data type + - target: $.components.schemas.CustomPropertyDataType + update: + x-enum-descriptions: + string: String value + number: Number value + date: Date + link: Link + - target: $.components.schemas.CustomPropertyDefinition + update: + description: Custom property + - target: $.components.schemas.CustomPropertyDefinition.properties.id + update: + description: Property ID + - target: $.components.schemas.CustomPropertyDefinition.properties.name + update: + description: Property name + - target: $.components.schemas.CustomPropertyDefinition.properties.data_type + update: + description: Property type + - target: $.components.schemas.ExportRequest + update: + description: Message export request + - target: $.components.schemas.ExportRequest.properties.start_at + update: + description: Export start date (ISO-8601, UTC+0) in YYYY-MM-DD format + - target: $.components.schemas.ExportRequest.properties.end_at + update: + description: Export end date (ISO-8601, UTC+0) in YYYY-MM-DD format + - target: $.components.schemas.ExportRequest.properties.webhook_url + update: + description: URL to receive a webhook when the export is complete + - target: $.components.schemas.ExportRequest.properties.chat_ids + update: + description: Array of chat IDs. Specify to export messages from specific chats only. + - target: $.components.schemas.ExportRequest.properties.skip_chats_file + update: + description: Skip generating the chat list file (chats.json) + - target: $.components.schemas.File + update: + description: File + - target: $.components.schemas.File.properties.id + update: + description: File ID + - target: $.components.schemas.File.properties.key + update: + description: File path + - target: $.components.schemas.File.properties.name + update: + description: File name with extension + - target: $.components.schemas.File.properties.file_type + update: + description: File type + - target: $.components.schemas.File.properties.url + update: + description: Direct file download URL + - target: $.components.schemas.File.properties.width + update: + description: Image width in pixels + - target: $.components.schemas.File.properties.height + update: + description: Image height in pixels + - target: $.components.schemas.FileType + update: + description: File type + - target: $.components.schemas.FileType + update: + x-enum-descriptions: + file: Regular file + image: Image + - target: $.components.schemas.FileUploadRequest.properties.Content-Disposition + update: + description: >- + Content-Disposition parameter received in the response to [Get signature, key and other parameters](POST + /uploads) + - target: $.components.schemas.FileUploadRequest.properties.acl + update: + description: acl parameter received in the response to [Get signature, key and other parameters](POST /uploads) + - target: $.components.schemas.FileUploadRequest.properties.policy + update: + description: policy parameter received in the response to [Get signature, key and other parameters](POST /uploads) + - target: $.components.schemas.FileUploadRequest.properties.x-amz-credential + update: + description: x-amz-credential parameter received in the response to [Get signature, key and other parameters](POST /uploads) + - target: $.components.schemas.FileUploadRequest.properties.x-amz-algorithm + update: + description: x-amz-algorithm parameter received in the response to [Get signature, key and other parameters](POST /uploads) + - target: $.components.schemas.FileUploadRequest.properties.x-amz-date + update: + description: x-amz-date parameter received in the response to [Get signature, key and other parameters](POST /uploads) + - target: $.components.schemas.FileUploadRequest.properties.x-amz-signature + update: + description: x-amz-signature parameter received in the response to [Get signature, key and other parameters](POST /uploads) + - target: $.components.schemas.FileUploadRequest.properties.key + update: + description: key parameter received in the response to [Get signature, key and other parameters](POST /uploads) + - target: $.components.schemas.FileUploadRequest.properties.file + update: + description: File to upload + - target: $.components.schemas.Forwarding + update: + description: Forwarded message information + - target: $.components.schemas.Forwarding.properties.original_message_id + update: + description: Original message ID + - target: $.components.schemas.Forwarding.properties.original_chat_id + update: + description: ID of the chat containing the original message + - target: $.components.schemas.Forwarding.properties.author_id + update: + description: ID of the user who created the original message + - target: $.components.schemas.Forwarding.properties.original_created_at + update: + description: Original message creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.Forwarding.properties.original_thread_id + update: + description: ID of the thread containing the original message + - target: $.components.schemas.Forwarding.properties.original_thread_message_id + update: + description: ID of the message that the thread containing the original message was created for + - target: $.components.schemas.Forwarding.properties.original_thread_parent_chat_id + update: + description: ID of the chat of the message that the thread containing the original message was created for + - target: $.components.schemas.GroupTag + update: + description: Group tag + - target: $.components.schemas.GroupTag.properties.id + update: + description: Tag ID + - target: $.components.schemas.GroupTag.properties.name + update: + description: Tag name + - target: $.components.schemas.GroupTag.properties.users_count + update: + description: Number of employees who have this tag + - target: $.components.schemas.GroupTagRequest + update: + description: Request to create or update a tag + - target: $.components.schemas.GroupTagRequest.properties.group_tag.properties.name + update: + description: Tag name + - target: $.components.schemas.InviteStatus + update: + description: User invitation status + - target: $.components.schemas.InviteStatus + update: + x-enum-descriptions: + confirmed: Confirmed + sent: Sent + - target: $.components.schemas.LinkPreview + update: + description: Link preview data + - target: $.components.schemas.LinkPreview.properties.title + update: + description: Title + - target: $.components.schemas.LinkPreview.properties.description + update: + description: Description + - target: $.components.schemas.LinkPreview.properties.image_url + update: + description: Public image URL (if you want to upload an image file to Pachca, use the image parameter) + - target: $.components.schemas.LinkPreview.properties.image + update: + description: Image + - target: $.components.schemas.LinkPreview.properties.image.properties.key + update: + description: Image path obtained from [file upload](POST /direct_url) + - target: $.components.schemas.LinkPreview.properties.image.properties.name + update: + description: Image name (recommended to include the file extension) + - target: $.components.schemas.LinkPreview.properties.image.properties.size + update: + description: Image size in bytes + - target: $.components.schemas.LinkPreviewsRequest + update: + description: Link unfurling request + - target: $.components.schemas.LinkPreviewsRequest.properties.link_previews + update: + description: '`JSON` map of link previews, where each key is a `URL` received in the outgoing webhook about a new message.' + - target: $.components.schemas.LinkSharedWebhookPayload + update: + description: Outgoing webhook payload for link unfurling + - target: $.components.schemas.LinkSharedWebhookPayload.properties.type + update: + description: Object type + - target: $.components.schemas.LinkSharedWebhookPayload.properties.type + update: + x-enum-descriptions: + message: Always message for link unfurling + - target: $.components.schemas.LinkSharedWebhookPayload.properties.event + update: + description: Event type + - target: $.components.schemas.LinkSharedWebhookPayload.properties.event + update: + x-enum-descriptions: + link_shared: Link to a monitored domain detected + - target: $.components.schemas.LinkSharedWebhookPayload.properties.chat_id + update: + description: ID of the chat where the link was found + - target: $.components.schemas.LinkSharedWebhookPayload.properties.message_id + update: + description: ID of the message containing the link + - target: $.components.schemas.LinkSharedWebhookPayload.properties.links + update: + description: Array of detected links to monitored domains + - target: $.components.schemas.LinkSharedWebhookPayload.properties.user_id + update: + description: Message sender ID + - target: $.components.schemas.LinkSharedWebhookPayload.properties.created_at + update: + description: Message creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.LinkSharedWebhookPayload.properties.webhook_timestamp + update: + description: Webhook send date and time (UTC+0) in UNIX format + - target: $.components.schemas.MemberEventType + update: + description: Webhook event type for members + - target: $.components.schemas.MemberEventType + update: + x-enum-descriptions: + add: Addition + remove: Deleted + - target: $.components.schemas.Message + update: + description: Message + - target: $.components.schemas.Message.properties.id + update: + description: Message ID + - target: $.components.schemas.Message.properties.entity_type + update: + description: Entity type the message belongs to + - target: $.components.schemas.Message.properties.entity_id + update: + description: ID of the entity the message belongs to (chat/channel, thread, or user) + - target: $.components.schemas.Message.properties.chat_id + update: + description: ID of the chat containing the message + - target: $.components.schemas.Message.properties.root_chat_id + update: + description: >- + Root chat ID. For messages in threads — the ID of the chat where the thread was created. For regular messages, + equals `chat_id`. + - target: $.components.schemas.Message.properties.content + update: + description: Message text + - target: $.components.schemas.Message.properties.user_id + update: + description: ID of the user who created the message + - target: $.components.schemas.Message.properties.created_at + update: + description: Message creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.Message.properties.url + update: + description: Direct link to the message + - target: $.components.schemas.Message.properties.files + update: + description: Attached files + - target: $.components.schemas.Message.properties.buttons + update: + description: Array of rows, each represented as an array of buttons + - target: $.components.schemas.Message.properties.thread + update: + description: Message thread + - target: $.components.schemas.Message.properties.thread.properties.id + update: + description: Thread ID + - target: $.components.schemas.Message.properties.thread.properties.chat_id + update: + description: Thread chat ID + - target: $.components.schemas.Message.properties.forwarding + update: + description: Forwarded message information + - target: $.components.schemas.Message.properties.parent_message_id + update: + description: ID of the message being replied to + - target: $.components.schemas.Message.properties.display_avatar_url + update: + description: Message sender avatar URL + - target: $.components.schemas.Message.properties.display_name + update: + description: Message sender full name + - target: $.components.schemas.Message.properties.changed_at + update: + description: Message last edit date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.Message.properties.deleted_at + update: + description: Message deletion date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.MessageCreateRequest + update: + description: Message creation request + - target: $.components.schemas.MessageCreateRequest.properties.message + update: + description: Message parameters object to create + - target: $.components.schemas.MessageCreateRequest.properties.message.properties.entity_type + update: + description: Entity type + - target: $.components.schemas.MessageCreateRequest.properties.message.properties.entity_id + update: + description: Entity ID + - target: $.components.schemas.MessageCreateRequest.properties.message.properties.content + update: + description: Message text + - target: $.components.schemas.MessageCreateRequest.properties.message.properties.files + update: + description: Files to attach + - target: $.components.schemas.MessageCreateRequest.properties.message.properties.files.items.properties.key + update: + description: File path obtained from [file upload](POST /direct_url) + - target: $.components.schemas.MessageCreateRequest.properties.message.properties.files.items.properties.name + update: + description: File name to display to the user (recommended to include the file extension) + - target: $.components.schemas.MessageCreateRequest.properties.message.properties.files.items.properties.file_type + update: + description: File type + - target: $.components.schemas.MessageCreateRequest.properties.message.properties.files.items.properties.size + update: + description: File size in bytes, displayed to the user + - target: $.components.schemas.MessageCreateRequest.properties.message.properties.files.items.properties.width + update: + description: Image width in px (used when file_type is set to image) + - target: $.components.schemas.MessageCreateRequest.properties.message.properties.files.items.properties.height + update: + description: Image height in px (used when file_type is set to image) + - target: $.components.schemas.MessageCreateRequest.properties.message.properties.buttons + update: + description: >- + Array of rows, each represented as an array of buttons. Maximum 100 buttons per message, up to 8 buttons per + row. + - target: $.components.schemas.MessageCreateRequest.properties.message.properties.parent_message_id + update: + description: Message ID. Specify when sending a reply to another message. + - target: $.components.schemas.MessageCreateRequest.properties.message.properties.display_avatar_url + update: + description: Custom sender avatar URL for this message. This field can only be used with a bot access_token. + - target: $.components.schemas.MessageCreateRequest.properties.message.properties.display_name + update: + description: Custom sender display name for this message. This field can only be used with a bot access_token. + - target: $.components.schemas.MessageCreateRequest.properties.message.properties.skip_invite_mentions + update: + description: Skip adding mentioned users to the thread. Only works when sending a message to a thread. + - target: $.components.schemas.MessageCreateRequest.properties.link_preview + update: + description: Display a preview of the first link found in the message text + - target: $.components.schemas.MessageEntityType + update: + description: Entity type for messages + - target: $.components.schemas.MessageEntityType + update: + x-enum-descriptions: + discussion: Conversation or channel + thread: Thread + user: User + - target: $.components.schemas.MessageUpdateRequest + update: + description: Message update request + - target: $.components.schemas.MessageUpdateRequest.properties.message + update: + description: Message parameters object to update + - target: $.components.schemas.MessageUpdateRequest.properties.message.properties.content + update: + description: Message text + - target: $.components.schemas.MessageUpdateRequest.properties.message.properties.files + update: + description: Files to attach + - target: $.components.schemas.MessageUpdateRequest.properties.message.properties.files.items.properties.key + update: + description: File path obtained from [file upload](POST /direct_url) + - target: $.components.schemas.MessageUpdateRequest.properties.message.properties.files.items.properties.name + update: + description: File name to display to the user (recommended to include the file extension) + - target: $.components.schemas.MessageUpdateRequest.properties.message.properties.files.items.properties.file_type + update: + description: 'File type: file (file), image (image)' + - target: $.components.schemas.MessageUpdateRequest.properties.message.properties.files.items.properties.size + update: + description: File size in bytes, displayed to the user + - target: $.components.schemas.MessageUpdateRequest.properties.message.properties.files.items.properties.width + update: + description: Image width in px (used when file_type is set to image) + - target: $.components.schemas.MessageUpdateRequest.properties.message.properties.files.items.properties.height + update: + description: Image height in px (used when file_type is set to image) + - target: $.components.schemas.MessageUpdateRequest.properties.message.properties.buttons + update: + description: >- + Array of rows, each represented as an array of buttons. Maximum 100 buttons per message, up to 8 buttons per + row. To remove buttons, send an empty array. + - target: $.components.schemas.MessageUpdateRequest.properties.message.properties.display_avatar_url + update: + description: Custom sender avatar URL for this message. This field can only be used with a bot access_token. + - target: $.components.schemas.MessageUpdateRequest.properties.message.properties.display_name + update: + description: Custom sender display name for this message. This field can only be used with a bot access_token. + - target: $.components.schemas.MessageWebhookPayload + update: + description: Outgoing webhook payload for messages + - target: $.components.schemas.MessageWebhookPayload.properties.type + update: + description: Object type + - target: $.components.schemas.MessageWebhookPayload.properties.type + update: + x-enum-descriptions: + message: Always message for messages + - target: $.components.schemas.MessageWebhookPayload.properties.id + update: + description: Message ID + - target: $.components.schemas.MessageWebhookPayload.properties.event + update: + description: Event type + - target: $.components.schemas.MessageWebhookPayload.properties.entity_type + update: + description: Entity type the message belongs to + - target: $.components.schemas.MessageWebhookPayload.properties.entity_id + update: + description: ID of the entity the message belongs to + - target: $.components.schemas.MessageWebhookPayload.properties.content + update: + description: Message text + - target: $.components.schemas.MessageWebhookPayload.properties.user_id + update: + description: Message sender ID + - target: $.components.schemas.MessageWebhookPayload.properties.created_at + update: + description: Message creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.MessageWebhookPayload.properties.url + update: + description: Direct link to the message + - target: $.components.schemas.MessageWebhookPayload.properties.chat_id + update: + description: ID of the chat containing the message + - target: $.components.schemas.MessageWebhookPayload.properties.parent_message_id + update: + description: ID of the message being replied to + - target: $.components.schemas.MessageWebhookPayload.properties.thread + update: + description: Thread parameters object + - target: $.components.schemas.MessageWebhookPayload.properties.webhook_timestamp + update: + description: Webhook send date and time (UTC+0) in UNIX format + - target: $.components.schemas.OAuthError + update: + description: OAuth authorization error (used for 401 and 403) + - target: $.components.schemas.OAuthError.properties.error + update: + description: Error code + - target: $.components.schemas.OAuthError.properties.error_description + update: + description: Error description + - target: $.components.schemas.OAuthScope + update: + description: OAuth token access scope + - target: $.components.schemas.OAuthScope + update: + x-enum-descriptions: + chats_read: View chats and chat list + chats_create: Create new chats + chats_update: Update chat settings + chats_archive: Archive and unarchive chats + chats_leave: Leave chats + chat_members_read: View chat members + chat_members_write: Add, update, and remove chat members + chat_exports_read: Download chat exports + chat_exports_write: Create chat exports + messages_read: View messages in chats + messages_create: Send messages + messages_update: Edit messages + messages_delete: Delete messages + reactions_read: View message reactions + reactions_write: Add and remove reactions + pins_write: Pin and unpin messages + threads_read: View threads (comments) + threads_create: Create threads (comments) + link_previews_write: Unfurl (link previews) + views_write: Open forms (views) + users_read: View employee information and employee list + users_create: Create new employees + users_update: Edit employee data + users_delete: Delete employees + group_tags_read: View tags + group_tags_write: Create, edit, and delete tags + bots_write: Update bot settings + profile_read: View own profile information + profile_status_read: View profile status + profile_status_write: Update and delete profile status + user_status_read: View employee status + user_status_write: Update and delete employee status + custom_properties_read: View custom properties + audit_events_read: View audit log + tasks_read: View tasks + tasks_create: Create tasks + tasks_update: Update task + tasks_delete: Delete task + files_read: Download files + files_write: Upload files + uploads_write: Get file upload data + webhooks_read: View webhooks + webhooks_write: Create and manage webhooks + webhooks_events_read: View webhook logs + webhooks_events_delete: Delete webhook log entry + search_users: Search employees + search_chats: Search chats + search_messages: Search messages + - target: $.components.schemas.OpenViewRequest + update: + description: View + - target: $.components.schemas.OpenViewRequest.properties.type + update: + description: View opening method + - target: $.components.schemas.OpenViewRequest.properties.type + update: + x-enum-descriptions: + modal: Modal window + - target: $.components.schemas.OpenViewRequest.properties.trigger_id + update: + description: Unique event identifier (received, for example, in the outgoing webhook for a button click) + - target: $.components.schemas.OpenViewRequest.properties.private_metadata + update: + 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. + - target: $.components.schemas.OpenViewRequest.properties.callback_id + update: + 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. + - target: $.components.schemas.OpenViewRequest.properties.view + update: + description: View object + - target: $.components.schemas.OpenViewRequest.properties.view.properties.title + update: + description: View title + - target: $.components.schemas.OpenViewRequest.properties.view.properties.close_text + update: + description: Close button text + - target: $.components.schemas.OpenViewRequest.properties.view.properties.submit_text + update: + description: Submit button text + - target: $.components.schemas.OpenViewRequest.properties.view.properties.blocks + update: + description: Array of view blocks + - target: $.components.schemas.PaginationMeta + update: + description: Pagination metadata + - target: $.components.schemas.PaginationMeta.properties.paginate + update: + description: Helper information + - target: $.components.schemas.PaginationMeta.properties.paginate.properties.next_page + update: + description: Next page pagination cursor + - target: $.components.schemas.Reaction + update: + description: Message reaction + - target: $.components.schemas.Reaction.properties.user_id + update: + description: ID of the user who added the reaction + - target: $.components.schemas.Reaction.properties.created_at + update: + description: Reaction creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.Reaction.properties.code + update: + description: Reaction emoji character + - target: $.components.schemas.Reaction.properties.name + update: + description: Reaction emoji name + - target: $.components.schemas.ReactionEventType + update: + description: Webhook event type for reactions + - target: $.components.schemas.ReactionEventType + update: + x-enum-descriptions: + new: Created + delete: Deleted + - target: $.components.schemas.ReactionRequest + update: + description: Reaction creation request + - target: $.components.schemas.ReactionRequest.properties.code + update: + description: Reaction emoji character + - target: $.components.schemas.ReactionRequest.properties.name + update: + description: Emoji text name (used for custom emoji) + - target: $.components.schemas.ReactionWebhookPayload + update: + description: Outgoing webhook payload for reactions + - target: $.components.schemas.ReactionWebhookPayload.properties.type + update: + description: Object type + - target: $.components.schemas.ReactionWebhookPayload.properties.type + update: + x-enum-descriptions: + reaction: Always reaction for reactions + - target: $.components.schemas.ReactionWebhookPayload.properties.event + update: + description: Event type + - target: $.components.schemas.ReactionWebhookPayload.properties.message_id + update: + description: ID of the message the reaction belongs to + - target: $.components.schemas.ReactionWebhookPayload.properties.code + update: + description: Reaction emoji character + - target: $.components.schemas.ReactionWebhookPayload.properties.name + update: + description: Reaction name + - target: $.components.schemas.ReactionWebhookPayload.properties.user_id + update: + description: ID of the user who added or removed the reaction + - target: $.components.schemas.ReactionWebhookPayload.properties.created_at + update: + description: Message creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.ReactionWebhookPayload.properties.webhook_timestamp + update: + description: Webhook send date and time (UTC+0) in UNIX format + - target: $.components.schemas.SearchEntityType + update: + description: Entity type for search + - target: $.components.schemas.SearchEntityType + update: + x-enum-descriptions: + User: User + Task: Task + - target: $.components.schemas.SearchPaginationMeta + update: + description: Pagination metadata for search results + - target: $.components.schemas.SearchPaginationMeta.properties.total + update: + description: Total number of results found + - target: $.components.schemas.SearchPaginationMeta.properties.paginate + update: + description: Helper information + - target: $.components.schemas.SearchPaginationMeta.properties.paginate.properties.next_page + update: + description: Next page pagination cursor + - target: $.components.schemas.SearchSortOrder + update: + description: Search results sort order + - target: $.components.schemas.SearchSortOrder + update: + x-enum-descriptions: + by_score: By relevance + alphabetical: Alphabetically + - target: $.components.schemas.SortOrder + update: + description: Sort order + - target: $.components.schemas.SortOrder + update: + x-enum-descriptions: + asc: Ascending + desc: Descending + - target: $.components.schemas.StatusUpdateRequest + update: + description: Status update request + - target: $.components.schemas.StatusUpdateRequest.properties.status.properties.emoji + update: + description: Status emoji character + - target: $.components.schemas.StatusUpdateRequest.properties.status.properties.title + update: + description: Status text + - target: $.components.schemas.StatusUpdateRequest.properties.status.properties.expires_at + update: + description: Status expiration date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.StatusUpdateRequest.properties.status.properties.is_away + update: + description: '"Away" mode' + - target: $.components.schemas.StatusUpdateRequest.properties.status.properties.away_message + update: + description: '"Away" mode message text. Displayed in the profile and in direct messages/mentions.' + - target: $.components.schemas.TagNamesFilter + update: + description: Array of tag names + - target: $.components.schemas.Task + update: + description: Task + - target: $.components.schemas.Task.properties.id + update: + description: Task ID + - target: $.components.schemas.Task.properties.kind + update: + description: Kind + - target: $.components.schemas.Task.properties.content + update: + description: Description + - target: $.components.schemas.Task.properties.due_at + update: + description: Task due date (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.Task.properties.priority + update: + description: Priority + - target: $.components.schemas.Task.properties.user_id + update: + description: ID of the user who created the task + - target: $.components.schemas.Task.properties.chat_id + update: + description: ID of the chat the task is linked to + - target: $.components.schemas.Task.properties.status + update: + description: Task status + - target: $.components.schemas.Task.properties.created_at + update: + description: Task creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.Task.properties.performer_ids + update: + description: Array of user IDs assigned to the task as performers + - target: $.components.schemas.Task.properties.all_day + update: + description: All-day task (without specific time) + - target: $.components.schemas.Task.properties.custom_properties + update: + description: Task custom properties + - target: $.components.schemas.TaskCreateRequest + update: + description: Task creation request + - target: $.components.schemas.TaskCreateRequest.properties.task + update: + description: Task parameters object to create + - target: $.components.schemas.TaskCreateRequest.properties.task.properties.kind + update: + description: Kind + - target: $.components.schemas.TaskCreateRequest.properties.task.properties.content + update: + description: Description (defaults to the kind name) + - target: $.components.schemas.TaskCreateRequest.properties.task.properties.due_at + update: + 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). + - target: $.components.schemas.TaskCreateRequest.properties.task.properties.priority + update: + description: 'Priority: 1, 2 (important), or 3 (very important).' + - target: $.components.schemas.TaskCreateRequest.properties.task.properties.performer_ids + update: + description: Array of user IDs to assign as task performers (defaults to you) + - target: $.components.schemas.TaskCreateRequest.properties.task.properties.chat_id + update: + description: ID of the chat to link the task to + - target: $.components.schemas.TaskCreateRequest.properties.task.properties.all_day + update: + description: All-day task (without specific time) + - target: $.components.schemas.TaskCreateRequest.properties.task.properties.custom_properties + update: + description: Custom properties to set + - target: $.components.schemas.TaskCreateRequest.properties.task.properties.custom_properties.items.properties.id + update: + description: Property ID + - target: $.components.schemas.TaskCreateRequest.properties.task.properties.custom_properties.items.properties.value + update: + description: Value to set + - target: $.components.schemas.TaskKind + update: + description: Task kind + - target: $.components.schemas.TaskKind + update: + x-enum-descriptions: + call: Call a contact + meeting: Meeting + reminder: Simple reminder + event: Event + email: Send an email + - target: $.components.schemas.TaskStatus + update: + description: Task status + - target: $.components.schemas.TaskStatus + update: + x-enum-descriptions: + done: Done + undone: Active + - target: $.components.schemas.TaskUpdateRequest + update: + description: Task update request + - target: $.components.schemas.TaskUpdateRequest.properties.task + update: + description: Task parameters object to update + - target: $.components.schemas.TaskUpdateRequest.properties.task.properties.kind + update: + description: Kind + - target: $.components.schemas.TaskUpdateRequest.properties.task.properties.content + update: + description: Description + - target: $.components.schemas.TaskUpdateRequest.properties.task.properties.due_at + update: + 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). + - target: $.components.schemas.TaskUpdateRequest.properties.task.properties.priority + update: + description: 'Priority: 1, 2 (important), or 3 (very important).' + - target: $.components.schemas.TaskUpdateRequest.properties.task.properties.performer_ids + update: + description: Array of user IDs to assign as task performers + - target: $.components.schemas.TaskUpdateRequest.properties.task.properties.status + update: + description: Status + - target: $.components.schemas.TaskUpdateRequest.properties.task.properties.all_day + update: + description: All-day task (without specific time) + - target: $.components.schemas.TaskUpdateRequest.properties.task.properties.done_at + update: + description: Task completion date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.TaskUpdateRequest.properties.task.properties.custom_properties + update: + description: Custom properties to set + - target: $.components.schemas.TaskUpdateRequest.properties.task.properties.custom_properties.items.properties.id + update: + description: Property ID + - target: $.components.schemas.TaskUpdateRequest.properties.task.properties.custom_properties.items.properties.value + update: + description: Value to set + - target: $.components.schemas.Thread + update: + description: Thread + - target: $.components.schemas.Thread.properties.id + update: + description: Created thread ID (used to send [new comments](POST /messages) to the thread) + - target: $.components.schemas.Thread.properties.chat_id + update: + description: >- + Thread chat ID (used to send [new comments](POST /messages) to the thread and to get the [list of comments](GET + /messages)) + - target: $.components.schemas.Thread.properties.message_id + update: + description: ID of the message the thread was created for + - target: $.components.schemas.Thread.properties.message_chat_id + update: + description: Message chat ID + - target: $.components.schemas.Thread.properties.updated_at + update: + description: Thread update date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.UpdateMemberRoleRequest + update: + description: Member role update request + - target: $.components.schemas.UpdateMemberRoleRequest.properties.role + update: + description: Role + - target: $.components.schemas.UploadParams + update: + description: File upload parameters + - target: $.components.schemas.UploadParams.properties.Content-Disposition + update: + description: Header used (attachment for this request) + - target: $.components.schemas.UploadParams.properties.acl + update: + description: Security level (private for this request) + - target: $.components.schemas.UploadParams.properties.policy + update: + description: Unique policy for file upload + - target: $.components.schemas.UploadParams.properties.x-amz-credential + update: + description: x-amz-credential for file upload + - target: $.components.schemas.UploadParams.properties.x-amz-algorithm + update: + description: Algorithm used (AWS4-HMAC-SHA256 for this request) + - target: $.components.schemas.UploadParams.properties.x-amz-date + update: + description: Unique x-amz-date for file upload + - target: $.components.schemas.UploadParams.properties.x-amz-signature + update: + description: Unique signature for file upload + - target: $.components.schemas.UploadParams.properties.key + update: + description: Unique key for file upload + - target: $.components.schemas.UploadParams.properties.direct_url + update: + description: File upload URL + - target: $.components.schemas.User + update: + description: Employee + - target: $.components.schemas.User.properties.id + update: + description: User ID + - target: $.components.schemas.User.properties.first_name + update: + description: First name + - target: $.components.schemas.User.properties.last_name + update: + description: Last name + - target: $.components.schemas.User.properties.nickname + update: + description: Username + - target: $.components.schemas.User.properties.email + update: + description: Email + - target: $.components.schemas.User.properties.phone_number + update: + description: Phone number + - target: $.components.schemas.User.properties.department + update: + description: Department + - target: $.components.schemas.User.properties.title + update: + description: Job title + - target: $.components.schemas.User.properties.role + update: + description: Access level + - target: $.components.schemas.User.properties.suspended + update: + description: User deactivated + - target: $.components.schemas.User.properties.invite_status + update: + description: Invitation status + - target: $.components.schemas.User.properties.list_tags + update: + description: Array of tags assigned to the employee + - target: $.components.schemas.User.properties.custom_properties + update: + description: Employee custom properties + - target: $.components.schemas.User.properties.user_status + update: + description: Status + - target: $.components.schemas.User.properties.bot + update: + description: Whether this is a bot + - target: $.components.schemas.User.properties.sso + update: + description: Whether the user uses SSO + - target: $.components.schemas.User.properties.created_at + update: + description: Creation date (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.User.properties.last_activity_at + update: + description: User last activity date (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.User.properties.time_zone + update: + description: User time zone + - target: $.components.schemas.User.properties.image_url + update: + description: User avatar download URL + - target: $.components.schemas.UserCreateRequest + update: + description: Employee creation request + - target: $.components.schemas.UserCreateRequest.properties.user.properties.first_name + update: + description: First name + - target: $.components.schemas.UserCreateRequest.properties.user.properties.last_name + update: + description: Last name + - target: $.components.schemas.UserCreateRequest.properties.user.properties.email + update: + description: Email + - target: $.components.schemas.UserCreateRequest.properties.user.properties.phone_number + update: + description: Phone number + - target: $.components.schemas.UserCreateRequest.properties.user.properties.nickname + update: + description: Username + - target: $.components.schemas.UserCreateRequest.properties.user.properties.department + update: + description: Department + - target: $.components.schemas.UserCreateRequest.properties.user.properties.title + update: + description: Job title + - target: $.components.schemas.UserCreateRequest.properties.user.properties.role + update: + description: Access level + - target: $.components.schemas.UserCreateRequest.properties.user.properties.suspended + update: + description: User deactivated + - target: $.components.schemas.UserCreateRequest.properties.user.properties.list_tags + update: + description: Array of tags to assign to the employee + - target: $.components.schemas.UserCreateRequest.properties.user.properties.custom_properties + update: + description: Custom properties to set + - target: $.components.schemas.UserCreateRequest.properties.user.properties.custom_properties.items.properties.id + update: + description: Property ID + - target: $.components.schemas.UserCreateRequest.properties.user.properties.custom_properties.items.properties.value + update: + description: Value to set + - target: $.components.schemas.UserCreateRequest.properties.skip_email_notify + update: + description: >- + 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. + - target: $.components.schemas.UserEventType + update: + description: Webhook event type for users + - target: $.components.schemas.UserEventType + update: + x-enum-descriptions: + invite: Invitation + confirm: Confirmation + update: Update + suspend: Suspension + activate: Activation + delete: Deleted + - target: $.components.schemas.UserRole + update: + description: User role in the system + - target: $.components.schemas.UserRole + update: + x-enum-descriptions: + admin: Administrator + user: Employee + multi_guest: Multi-guest + guest: Guest + - target: $.components.schemas.UserRoleInput + update: + description: User role allowed for creation and editing. The `guest` role cannot be set via API. + - target: $.components.schemas.UserRoleInput + update: + x-enum-descriptions: + admin: Administrator + user: Employee + multi_guest: Multi-guest + - target: $.components.schemas.UserStatus + update: + description: User status + - target: $.components.schemas.UserStatus.properties.emoji + update: + description: Status emoji character + - target: $.components.schemas.UserStatus.properties.title + update: + description: Status text + - target: $.components.schemas.UserStatus.properties.expires_at + update: + description: Status expiration date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.UserStatus.properties.is_away + update: + description: '"Away" mode' + - target: $.components.schemas.UserStatus.properties.away_message + update: + description: >- + "Away" mode message. Displayed in the user profile, as well as when sending a direct message or mentioning them + in a chat. + - target: $.components.schemas.UserStatus.properties.away_message.properties.text + update: + description: Message text + - target: $.components.schemas.UserUpdateRequest + update: + description: Employee update request + - target: $.components.schemas.UserUpdateRequest.properties.user + update: + description: Employee parameters object to update + - target: $.components.schemas.UserUpdateRequest.properties.user.properties.first_name + update: + description: First name + - target: $.components.schemas.UserUpdateRequest.properties.user.properties.last_name + update: + description: Last name + - target: $.components.schemas.UserUpdateRequest.properties.user.properties.email + update: + description: Email + - target: $.components.schemas.UserUpdateRequest.properties.user.properties.phone_number + update: + description: Phone number + - target: $.components.schemas.UserUpdateRequest.properties.user.properties.nickname + update: + description: Username + - target: $.components.schemas.UserUpdateRequest.properties.user.properties.department + update: + description: Department + - target: $.components.schemas.UserUpdateRequest.properties.user.properties.title + update: + description: Job title + - target: $.components.schemas.UserUpdateRequest.properties.user.properties.role + update: + description: Access level + - target: $.components.schemas.UserUpdateRequest.properties.user.properties.suspended + update: + description: User deactivated + - target: $.components.schemas.UserUpdateRequest.properties.user.properties.list_tags + update: + description: Array of tags to assign to the employee + - target: $.components.schemas.UserUpdateRequest.properties.user.properties.custom_properties + update: + description: Custom properties to set + - target: $.components.schemas.UserUpdateRequest.properties.user.properties.custom_properties.items.properties.id + update: + description: Property ID + - target: $.components.schemas.UserUpdateRequest.properties.user.properties.custom_properties.items.properties.value + update: + description: Value to set + - target: $.components.schemas.ValidationErrorCode + update: + description: Validation error codes + - target: $.components.schemas.ValidationErrorCode + update: + x-enum-descriptions: + blank: Required field (cannot be empty) + too_long: Value is too long (details provided in the message field) + invalid: Field does not match the rules (details provided in the message field) + inclusion: Field has an unexpected value + exclusion: Field has a forbidden value + taken: Name for this field already exists + wrong_emoji: Status emoji cannot contain values other than an emoji character + not_found: Object not found + already_exists: Object already exists (details provided in the message field) + personal_chat: Direct message error (details provided in the message field) + displayed_error: Displayed error (details provided in the message field) + not_authorized: Action forbidden + invalid_date_range: Selected date range is too large + invalid_webhook_url: Invalid webhook URL + rate_limit: Rate limit reached + licenses_limit: Active employee limit exceeded (details provided in the message field) + user_limit: User reaction limit exceeded (20 unique reactions) + unique_limit: Unique reaction limit per message exceeded (30 unique reactions) + general_limit: Reaction limit per message exceeded (1000 reactions) + unhandled: Request execution error (details provided in the message field) + trigger_not_found: Event identifier not found + trigger_expired: Event identifier has expired + required: Required parameter not provided + in: Invalid value (not in the list of allowed values) + not_applicable: Value not applicable in this context (details provided in the message field) + self_update: Cannot modify your own data + owner_protected: Cannot modify owner data + already_assigned: Value already assigned + forbidden: Insufficient permissions to perform the action (details provided in the message field) + permission_denied: Access denied (insufficient permissions) + access_denied: Access denied + wrong_params: Invalid request parameters (details provided in the message field) + payment_required: Payment required + min_length: Value is too short (details provided in the message field) + max_length: Value is too long (details provided in the message field) + use_of_system_words: Reserved system word used (here, all) + - target: $.components.schemas.ViewBlock + update: + description: View block for forms (base model, use specific block types) + - target: $.components.schemas.ViewBlock.properties.type + update: + description: Block type + - target: $.components.schemas.ViewBlock.properties.text + update: + description: Block text + - target: $.components.schemas.ViewBlock.properties.name + update: + description: Field name + - target: $.components.schemas.ViewBlock.properties.label + update: + description: Field label + - target: $.components.schemas.ViewBlock.properties.initial_date + update: + description: Initial date + - target: $.components.schemas.ViewBlockCheckbox + update: + description: Checkbox block — checkboxes + - target: $.components.schemas.ViewBlockCheckbox.properties.type + update: + description: Block type + - target: $.components.schemas.ViewBlockCheckbox.properties.type + update: + x-enum-descriptions: + checkbox: Always checkbox for checkboxes + - target: $.components.schemas.ViewBlockCheckbox.properties.name + update: + description: Name that will be sent to your application as the key for the user-selected choice + - target: $.components.schemas.ViewBlockCheckbox.properties.label + update: + description: Checkbox group label + - target: $.components.schemas.ViewBlockCheckbox.properties.options + update: + description: Array of checkboxes + - target: $.components.schemas.ViewBlockCheckbox.properties.required + update: + description: Required + - target: $.components.schemas.ViewBlockCheckbox.properties.hint + update: + description: Hint displayed below the checkbox group in gray + - target: $.components.schemas.ViewBlockCheckboxOption.properties.text + update: + description: Display text + - target: $.components.schemas.ViewBlockCheckboxOption.properties.value + update: + description: Unique string value that will be sent to your application when this item is selected + - target: $.components.schemas.ViewBlockCheckboxOption.properties.description + update: + description: Description displayed in gray below the display text for this item + - target: $.components.schemas.ViewBlockCheckboxOption.properties.checked + update: + description: Initially selected item + - target: $.components.schemas.ViewBlockDate + update: + description: Date block — date picker + - target: $.components.schemas.ViewBlockDate.properties.type + update: + description: Block type + - target: $.components.schemas.ViewBlockDate.properties.type + update: + x-enum-descriptions: + date: Always date for date picker + - target: $.components.schemas.ViewBlockDate.properties.name + update: + description: Name that will be sent to your application as the key for the user-specified value + - target: $.components.schemas.ViewBlockDate.properties.label + update: + description: Field label + - target: $.components.schemas.ViewBlockDate.properties.initial_date + update: + description: Initial field value in YYYY-MM-DD format + - target: $.components.schemas.ViewBlockDate.properties.required + update: + description: Required + - target: $.components.schemas.ViewBlockDate.properties.hint + update: + description: Hint displayed below the field in gray + - target: $.components.schemas.ViewBlockDivider + update: + description: Divider block — separator + - target: $.components.schemas.ViewBlockDivider.properties.type + update: + description: Block type + - target: $.components.schemas.ViewBlockDivider.properties.type + update: + x-enum-descriptions: + divider: Always divider for separators + - target: $.components.schemas.ViewBlockFileInput + update: + description: File input block — file upload + - target: $.components.schemas.ViewBlockFileInput.properties.type + update: + description: Block type + - target: $.components.schemas.ViewBlockFileInput.properties.type + update: + x-enum-descriptions: + file_input: Always file_input for file upload + - target: $.components.schemas.ViewBlockFileInput.properties.name + update: + description: Name that will be sent to your application as the key for the user-specified value + - target: $.components.schemas.ViewBlockFileInput.properties.label + update: + description: Field label + - target: $.components.schemas.ViewBlockFileInput.properties.filetypes + update: + description: >- + Array of allowed file extensions as strings (e.g., ["png","jpg","gif"]). If this field is not specified, all + file extensions will be accepted. + - target: $.components.schemas.ViewBlockFileInput.properties.max_files + update: + description: Maximum number of files the user can upload to this field. + - target: $.components.schemas.ViewBlockFileInput.properties.required + update: + description: Required + - target: $.components.schemas.ViewBlockFileInput.properties.hint + update: + description: Hint displayed below the field in gray + - target: $.components.schemas.ViewBlockHeader + update: + description: Header block — heading + - target: $.components.schemas.ViewBlockHeader.properties.type + update: + description: Block type + - target: $.components.schemas.ViewBlockHeader.properties.type + update: + x-enum-descriptions: + header: Always header for headings + - target: $.components.schemas.ViewBlockHeader.properties.text + update: + description: Header text + - target: $.components.schemas.ViewBlockInput + update: + description: Input block — text input field + - target: $.components.schemas.ViewBlockInput.properties.type + update: + description: Block type + - target: $.components.schemas.ViewBlockInput.properties.type + update: + x-enum-descriptions: + input: Always input for text fields + - target: $.components.schemas.ViewBlockInput.properties.name + update: + description: Name that will be sent to your application as the key for the user-specified value + - target: $.components.schemas.ViewBlockInput.properties.label + update: + description: Field label + - target: $.components.schemas.ViewBlockInput.properties.placeholder + update: + description: Placeholder text inside the input field while it is empty + - target: $.components.schemas.ViewBlockInput.properties.multiline + update: + description: Multiline field + - target: $.components.schemas.ViewBlockInput.properties.initial_value + update: + description: Initial field value + - target: $.components.schemas.ViewBlockInput.properties.min_length + update: + description: Minimum text length the user must enter. If the user enters less, they will receive an error. + - target: $.components.schemas.ViewBlockInput.properties.max_length + update: + description: Maximum text length the user can enter. If the user enters more, they will receive an error. + - target: $.components.schemas.ViewBlockInput.properties.required + update: + description: Required + - target: $.components.schemas.ViewBlockInput.properties.hint + update: + description: Hint displayed below the field in gray + - target: $.components.schemas.ViewBlockMarkdown + update: + description: Markdown block — formatted text + - target: $.components.schemas.ViewBlockMarkdown.properties.type + update: + description: Block type + - target: $.components.schemas.ViewBlockMarkdown.properties.type + update: + x-enum-descriptions: + markdown: Always markdown for formatted text + - target: $.components.schemas.ViewBlockMarkdown.properties.text + update: + description: Text + - target: $.components.schemas.ViewBlockPlainText + update: + description: Plain text block — plain text + - target: $.components.schemas.ViewBlockPlainText.properties.type + update: + description: Block type + - target: $.components.schemas.ViewBlockPlainText.properties.type + update: + x-enum-descriptions: + plain_text: Always plain_text for plain text + - target: $.components.schemas.ViewBlockPlainText.properties.text + update: + description: Text + - target: $.components.schemas.ViewBlockRadio + update: + description: Radio block — radio buttons + - target: $.components.schemas.ViewBlockRadio.properties.type + update: + description: Block type + - target: $.components.schemas.ViewBlockRadio.properties.type + update: + x-enum-descriptions: + radio: Always radio for radio buttons + - target: $.components.schemas.ViewBlockRadio.properties.name + update: + description: Name that will be sent to your application as the key for the user-selected choice + - target: $.components.schemas.ViewBlockRadio.properties.label + update: + description: Radio button group label + - target: $.components.schemas.ViewBlockRadio.properties.options + update: + description: Array of radio buttons + - target: $.components.schemas.ViewBlockRadio.properties.required + update: + description: Required + - target: $.components.schemas.ViewBlockRadio.properties.hint + update: + description: Hint displayed below the radio button group in gray + - target: $.components.schemas.ViewBlockSelect + update: + description: Select block — dropdown + - target: $.components.schemas.ViewBlockSelect.properties.type + update: + description: Block type + - target: $.components.schemas.ViewBlockSelect.properties.type + update: + x-enum-descriptions: + select: Always select for dropdowns + - target: $.components.schemas.ViewBlockSelect.properties.name + update: + description: Name that will be sent to your application as the key for the user-selected choice + - target: $.components.schemas.ViewBlockSelect.properties.label + update: + description: Dropdown label + - target: $.components.schemas.ViewBlockSelect.properties.options + update: + description: Array of available items in the dropdown + - target: $.components.schemas.ViewBlockSelect.properties.required + update: + description: Required + - target: $.components.schemas.ViewBlockSelect.properties.hint + update: + description: Hint displayed below the dropdown in gray + - target: $.components.schemas.ViewBlockSelectableOption + update: + description: Option for select, radio, and checkbox blocks + - target: $.components.schemas.ViewBlockSelectableOption.properties.text + update: + description: Display text + - target: $.components.schemas.ViewBlockSelectableOption.properties.value + update: + description: Unique string value that will be sent to your application when this item is selected + - target: $.components.schemas.ViewBlockSelectableOption.properties.description + update: + description: Description displayed in gray below the display text for this item + - target: $.components.schemas.ViewBlockSelectableOption.properties.selected + update: + description: Initially selected item. Only one item can be selected. + - target: $.components.schemas.ViewBlockTime + update: + description: Time block — time picker + - target: $.components.schemas.ViewBlockTime.properties.type + update: + description: Block type + - target: $.components.schemas.ViewBlockTime.properties.type + update: + x-enum-descriptions: + time: Always time for time picker + - target: $.components.schemas.ViewBlockTime.properties.name + update: + description: Name that will be sent to your application as the key for the user-specified value + - target: $.components.schemas.ViewBlockTime.properties.label + update: + description: Field label + - target: $.components.schemas.ViewBlockTime.properties.initial_time + update: + description: Initial field value in HH:mm format + - target: $.components.schemas.ViewBlockTime.properties.required + update: + description: Required + - target: $.components.schemas.ViewBlockTime.properties.hint + update: + description: Hint displayed below the field in gray + - target: $.components.schemas.ViewBlockUnion + update: + description: Union type for all possible view blocks + - target: $.components.schemas.ViewSubmitWebhookPayload + update: + description: Outgoing webhook payload for form submission + - target: $.components.schemas.ViewSubmitWebhookPayload.properties.type + update: + description: Object type + - target: $.components.schemas.ViewSubmitWebhookPayload.properties.type + update: + x-enum-descriptions: + view: Always view for forms + - target: $.components.schemas.ViewSubmitWebhookPayload.properties.event + update: + description: Event type + - target: $.components.schemas.ViewSubmitWebhookPayload.properties.event + update: + x-enum-descriptions: + submit: Form submission + - target: $.components.schemas.ViewSubmitWebhookPayload.properties.callback_id + update: + description: Callback ID specified when opening the view + - target: $.components.schemas.ViewSubmitWebhookPayload.properties.private_metadata + update: + description: Private metadata specified when opening the view + - target: $.components.schemas.ViewSubmitWebhookPayload.properties.user_id + update: + description: ID of the user who submitted the form + - target: $.components.schemas.ViewSubmitWebhookPayload.properties.data + update: + description: Submitted view field data. Key — field `action_id`, value — entered data + - target: $.components.schemas.ViewSubmitWebhookPayload.properties.webhook_timestamp + update: + description: Webhook send date and time (UTC+0) in UNIX format + - target: $.components.schemas.WebhookEvent + update: + description: Outgoing webhook event + - target: $.components.schemas.WebhookEvent.properties.id + update: + description: Event ID + - target: $.components.schemas.WebhookEvent.properties.event_type + update: + description: Event type + - target: $.components.schemas.WebhookEvent.properties.payload + update: + description: Webhook object + - target: $.components.schemas.WebhookEvent.properties.created_at + update: + description: Event creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.WebhookEventType + update: + description: Webhook event type + - target: $.components.schemas.WebhookEventType + update: + x-enum-descriptions: + new: Created + update: Update + delete: Deleted + - target: $.components.schemas.WebhookLink + update: + description: Link object in the link unfurling webhook + - target: $.components.schemas.WebhookLink.properties.url + update: + description: Link URL + - target: $.components.schemas.WebhookLink.properties.domain + update: + description: Link domain + - target: $.components.schemas.WebhookMessageThread + update: + description: Thread object in the message webhook + - target: $.components.schemas.WebhookMessageThread.properties.message_id + update: + description: ID of the message the thread was created for + - target: $.components.schemas.WebhookMessageThread.properties.message_chat_id + update: + description: ID of the chat of the message the thread was created for + - target: $.components.schemas.WebhookPayloadUnion + update: + description: Union of all webhook payload types + # ApiError nested items + - target: $.components.schemas.ApiError.properties.errors.items.properties.key + update: + description: Field key that caused the error + - target: $.components.schemas.ApiError.properties.errors.items.properties.value + update: + description: Field value that caused the error + - target: $.components.schemas.ApiError.properties.errors.items.properties.message + update: + description: Error message + - target: $.components.schemas.ApiError.properties.errors.items.properties.code + update: + description: Error code + - target: $.components.schemas.ApiError.properties.errors.items.properties.payload + update: + description: >- + Additional error data. Content depends on the error code: + `{id: number}` for custom property errors, `{type: string}` for type mismatch errors + + # LinkSharedWebhookPayload nested items + - target: $.components.schemas.LinkSharedWebhookPayload.properties.links.items.properties.url + update: + description: Link URL + - target: $.components.schemas.LinkSharedWebhookPayload.properties.links.items.properties.domain + update: + description: Link domain + + # Message.files nested items + - target: $.components.schemas.Message.properties.files.items.properties.id + update: + description: File ID + - target: $.components.schemas.Message.properties.files.items.properties.key + update: + description: File path + - target: $.components.schemas.Message.properties.files.items.properties.name + update: + description: File name with extension + - target: $.components.schemas.Message.properties.files.items.properties.file_type + update: + description: "File type" + - target: $.components.schemas.Message.properties.files.items.properties.url + update: + description: Direct file download URL + - target: $.components.schemas.Message.properties.files.items.properties.width + update: + description: Image width in pixels + - target: $.components.schemas.Message.properties.files.items.properties.height + update: + description: Image height in pixels + + # Message.forwarding nested properties + - target: $.components.schemas.Message.properties.forwarding.properties.original_message_id + update: + description: Original message ID + - target: $.components.schemas.Message.properties.forwarding.properties.original_chat_id + update: + description: ID of the chat containing the original message + - target: $.components.schemas.Message.properties.forwarding.properties.author_id + update: + description: ID of the user who created the original message + - target: $.components.schemas.Message.properties.forwarding.properties.original_created_at + update: + description: Original message creation date and time (ISO-8601, UTC+0) in YYYY-MM-DDThh:mm:ss.sssZ format + - target: $.components.schemas.Message.properties.forwarding.properties.original_thread_id + update: + description: ID of the thread containing the original message + - target: $.components.schemas.Message.properties.forwarding.properties.original_thread_message_id + update: + description: ID of the message that the thread was created from, which contains the original message + - target: $.components.schemas.Message.properties.forwarding.properties.original_thread_parent_chat_id + update: + description: ID of the chat of the message that the thread was created from, which contains the original message + + # MessageWebhookPayload.thread nested properties + - target: $.components.schemas.MessageWebhookPayload.properties.thread.properties.message_id + update: + description: Thread parent message ID + - target: $.components.schemas.MessageWebhookPayload.properties.thread.properties.message_chat_id + update: + description: Chat ID of the thread parent message + - target: $.components.schemas.MessageSortField + update: + description: Message sort field + - target: $.components.schemas.MessageSortField + update: + x-enum-descriptions: + id: By message ID + + # Task.custom_properties nested items + - target: $.components.schemas.Task.properties.custom_properties.items.properties.id + update: + description: Custom property ID + - target: $.components.schemas.Task.properties.custom_properties.items.properties.name + update: + description: Custom property name + - target: $.components.schemas.Task.properties.custom_properties.items.properties.data_type + update: + description: Custom property data type + - target: $.components.schemas.Task.properties.custom_properties.items.properties.value + update: + description: Custom property value + + # User.custom_properties nested items + - target: $.components.schemas.User.properties.custom_properties.items.properties.id + update: + description: Custom property ID + - target: $.components.schemas.User.properties.custom_properties.items.properties.name + update: + description: Custom property name + - target: $.components.schemas.User.properties.custom_properties.items.properties.data_type + update: + description: Custom property data type + - target: $.components.schemas.User.properties.custom_properties.items.properties.value + update: + description: Custom property value + + # User.user_status nested properties + - target: $.components.schemas.User.properties.user_status.properties.emoji + update: + description: Status emoji + - target: $.components.schemas.User.properties.user_status.properties.title + update: + description: Status text + - target: $.components.schemas.User.properties.user_status.properties.expires_at + update: + description: Status expiration date and time (ISO-8601, UTC+0) + - target: $.components.schemas.User.properties.user_status.properties.is_away + update: + description: Whether the user is marked as away + - target: $.components.schemas.User.properties.user_status.properties.away_message + update: + description: Away message + - target: $.components.schemas.User.properties.user_status.properties.away_message.properties.text + update: + description: Away message text + + # ViewBlockCheckbox.options nested items + - target: $.components.schemas.ViewBlockCheckbox.properties.options.items.properties.text + update: + description: Checkbox option label text + - target: $.components.schemas.ViewBlockCheckbox.properties.options.items.properties.value + update: + description: Checkbox option value + - target: $.components.schemas.ViewBlockCheckbox.properties.options.items.properties.description + update: + description: Checkbox option description + - target: $.components.schemas.ViewBlockCheckbox.properties.options.items.properties.checked + update: + description: Whether the checkbox is checked by default + + # ViewBlockRadio.options nested items + - target: $.components.schemas.ViewBlockRadio.properties.options.items.properties.text + update: + description: Radio option label text + - target: $.components.schemas.ViewBlockRadio.properties.options.items.properties.value + update: + description: Radio option value + - target: $.components.schemas.ViewBlockRadio.properties.options.items.properties.description + update: + description: Radio option description + - target: $.components.schemas.ViewBlockRadio.properties.options.items.properties.selected + update: + description: Whether this radio option is selected by default + + # ViewBlockSelect.options nested items + - target: $.components.schemas.ViewBlockSelect.properties.options.items.properties.text + update: + description: Select option label text + - target: $.components.schemas.ViewBlockSelect.properties.options.items.properties.value + update: + description: Select option value + - target: $.components.schemas.ViewBlockSelect.properties.options.items.properties.description + update: + description: Select option description + - target: $.components.schemas.ViewBlockSelect.properties.options.items.properties.selected + update: + description: Whether this select option is selected by default diff --git a/packages/spec/package.json b/packages/spec/package.json index df90480c..813e440e 100644 --- a/packages/spec/package.json +++ b/packages/spec/package.json @@ -7,13 +7,17 @@ }, "scripts": { "setup": "true", - "generate": "tsp compile typespec.tsp --config tspconfig.yaml" + "generate": "tsp compile typespec.tsp --config tspconfig.yaml", + "overlay:apply": "bun scripts/apply-overlay.ts", + "overlay:validate": "bun scripts/validate-overlay.ts" }, "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" + "@typespec/rest": "^0.77.0", + "js-yaml": "^4.1.1" } } diff --git a/packages/spec/scripts/apply-overlay.ts b/packages/spec/scripts/apply-overlay.ts new file mode 100644 index 00000000..ce2960bf --- /dev/null +++ b/packages/spec/scripts/apply-overlay.ts @@ -0,0 +1,453 @@ +/** + * Apply an OpenAPI Overlay v1.0.0 to a base spec. + * + * Usage: tsx scripts/apply-overlay.ts [--base openapi.yaml] [--overlay overlay.en.yaml] [--output openapi.en.yaml] + */ +import fs from 'node:fs'; +import path from 'node:path'; +import yaml from 'js-yaml'; + +// --------------------------------------------------------------------------- +// JSONPath-subset resolver +// --------------------------------------------------------------------------- + +interface Resolved { + parent: any; + key: string | number; +} + +/** + * Parse a JSONPath target string into segments. + * Supports: $, .key, ['key'], [N], [?(@.name=='value')] + */ +function parseTarget(target: string): string[] { + const segments: string[] = []; + let i = 0; + if (target[i] === '$') i++; // skip root + + while (i < target.length) { + if (target[i] === '.') { + i++; + // Read dotted property name (stops at . or [) + let name = ''; + while (i < target.length && target[i] !== '.' && target[i] !== '[') { + name += target[i++]; + } + if (name) segments.push(name); + } else if (target[i] === '[') { + i++; // skip [ + if (target[i] === "'") { + // Bracket string: ['key'] + i++; // skip opening ' + let name = ''; + while (i < target.length && target[i] !== "'") { + name += target[i++]; + } + i++; // skip closing ' + i++; // skip ] + segments.push(name); + } else if (target[i] === '?') { + // Filter: [?(@.name=='value')] — find matching ) then ] + let depth = 0; + let filterEnd = i; + while (filterEnd < target.length) { + if (target[filterEnd] === '(') depth++; + if (target[filterEnd] === ')') { + depth--; + if (depth === 0) { + // find the closing ] after ) + filterEnd = target.indexOf(']', filterEnd); + break; + } + } + filterEnd++; + } + const filterExpr = target.substring(i, filterEnd); + segments.push(filterExpr); + i = filterEnd + 1; + } else { + // Numeric index: [0] + let num = ''; + while (i < target.length && target[i] !== ']') { + num += target[i++]; + } + i++; // skip ] + segments.push(num); + } + } else { + i++; + } + } + return segments; +} + +/** + * Parse a filter expression like ?(@.name=='value') and return {prop, value}. + */ +function parseFilter(filter: string): { prop: string; value: string } | null { + // ?(@.prop=='value') or ?(@.prop=='value') + const m = filter.match(/\?\(@\.(\w+)==['"](.*?)['"]\)/); + if (!m) return null; + return { prop: m[1], value: m[2] }; +} + +/** + * Follow $ref if present in the current object. + */ +function followRef(obj: any, doc: any): any { + if (obj && typeof obj === 'object' && obj.$ref && typeof obj.$ref === 'string') { + const ref = obj.$ref; + if (!ref.startsWith('#/')) return obj; + const parts = ref.replace('#/', '').split('/'); + let resolved = doc; + for (const part of parts) { + resolved = resolved?.[part]; + } + return resolved ?? obj; + } + return obj; +} + +/** + * Resolve a JSONPath target to {parent, key} in the document. + */ +function resolveTarget(doc: any, target: string): Resolved { + const segments = parseTarget(target); + let current = doc; + + for (let i = 0; i < segments.length; i++) { + const seg = segments[i]; + const isLast = i === segments.length - 1; + + // Follow $ref before navigating + current = followRef(current, doc); + + if (seg.startsWith('?')) { + // Array filter + const filter = parseFilter(seg); + if (!filter || !Array.isArray(current)) { + throw new Error(`Cannot apply filter "${seg}" — current value is not an array or filter is invalid. Target: ${target}`); + } + const idx = current.findIndex((item: any) => String(item[filter.prop]) === filter.value); + if (idx === -1) { + throw new Error(`Filter "${seg}" matched no elements in array. Target: ${target}`); + } + if (isLast) { + return { parent: current, key: idx }; + } + current = current[idx]; + } else if (/^\d+$/.test(seg)) { + // Numeric index + const idx = parseInt(seg, 10); + if (isLast) { + return { parent: current, key: idx }; + } + current = current[idx]; + } else { + // Object property + if (isLast) { + return { parent: current, key: seg }; + } + if (current[seg] === undefined) { + throw new Error(`Property "${seg}" not found. Target: ${target}`); + } + current = current[seg]; + } + } + + throw new Error(`Could not resolve target: ${target}`); +} + +// --------------------------------------------------------------------------- +// Deep merge (only overwrites leaf values, preserves structure) +// --------------------------------------------------------------------------- + +function deepMerge(target: any, source: any): any { + if (source === null || source === undefined) return target; + if (typeof source !== 'object' || Array.isArray(source)) return source; + if (typeof target !== 'object' || target === null || Array.isArray(target)) return source; + + const result = { ...target }; + for (const key of Object.keys(source)) { + result[key] = deepMerge(result[key], source[key]); + } + return result; +} + +// --------------------------------------------------------------------------- +// Post-processing: translate remaining Russian strings +// --------------------------------------------------------------------------- + +const CYRILLIC_RE = /[а-яА-ЯёЁ]/; + +/** Translation dictionary for inline strings not reachable via JSONPath overlay targets */ +const RU_TO_EN: Record = { + // Response wrapper descriptions (inline schemas in response bodies) + 'Обертка ответа с данными и пагинацией': 'Response wrapper with data and pagination', + 'Обертка ответа с данными': 'Response wrapper with data', + 'Обертка ответа поисковых результатов с данными и пагинацией': 'Search results response wrapper with data and pagination', + + // Avatar model + 'Данные аватара': 'Avatar data', + 'URL аватара': 'Avatar URL', + 'Файл изображения для аватара': 'Avatar image file', + 'Изменение и удаление аватара профиля': 'Update and delete profile avatar', + 'Изменение и удаление аватара сотрудника': 'Update and delete employee avatar', + + // ChatSortField enum + 'Поле сортировки чатов': 'Chat sort field', + 'По идентификатору чата': 'By chat ID', + 'По дате и времени создания последнего сообщения': 'By date and time of the last message', + + // MessageSortField enum + 'Поле сортировки сообщений': 'Message sort field', + 'По идентификатору сообщения': 'By message ID', + + // x-param-names descriptions + 'Идентификатор чата': 'Chat ID', + 'Дата и время создания последнего сообщения': 'Date and time of the last message creation', + 'Идентификатор сообщения': 'Message ID', + + // Example values — models + 'Разработка': 'Development', + 'футболки': 't-shirts', + 'Олег': 'Oleg', + 'Петров': 'Petrov', + 'Продукт': 'Product', + 'Санкт-Петербург': 'Saint Petersburg', + 'Город': 'City', + 'Мой API токен': 'My API Token', + 'Поле не может быть пустым': 'Field cannot be empty', + 'Отдел разработки': 'Engineering', + 'Старший разработчик': 'Senior Developer', + 'Текст сообщения': 'Message text', + 'Бассейн': 'Pool', + 'Новое название тега': 'New tag name', + 'Очень занят': 'Very busy', + 'Вернусь после 15:00': 'Back after 3 PM', + + // Example values — messages and buttons + 'Подробнее': 'Learn more', + 'Отлично!': 'Great!', + 'Бот Поддержки': 'Support Bot', + 'Статья: Отправка файлов': 'Article: Sending files', + 'Пример отправки файлов на удаленный сервер': 'Example of sending files to a remote server', + 'Синий склад': 'Blue warehouse', + + // Example values — messages content + 'Вчера мы продали 756 футболок (что на 10% больше, чем в прошлое воскресенье)': + 'Yesterday we sold 756 t-shirts (10% more than last Sunday)', + 'Вот попробуйте написать правильно это с первого раза: Будущий, Полощи, Прийти, Грейпфрут, Мозаика, Бюллетень, Дуршлаг, Винегрет.': + 'Try to spell these correctly on the first attempt: bureaucracy, accommodate, definitely, entrepreneur, liaison, necessary, surveillance, questionnaire.', + 'Забрать со склада 21 заказ': 'Pick up 21 orders from the warehouse', + + // Example values — user status + 'Я в отпуске до 15 апреля. По срочным вопросам обращайтесь к @ivanov.': + 'I am on vacation until April 15. For urgent matters, contact @ivanov.', + + // Example values — forms / views + 'Уведомление об отпуске': 'Vacation notification', + 'Закрыть': 'Close', + 'Отменить': 'Cancel', + 'Отправить заявку': 'Submit request', + 'Отправить': 'Submit', + 'Рассылки': 'Newsletters', + 'Выберите интересующие вас рассылки': 'Select the newsletters you are interested in', + 'Ничего': 'None', + 'Каждый день бот будет присылать список новых задач в вашей команде': + 'Every day the bot will send a list of new tasks in your team', + 'Дата начала отпуска': 'Vacation start date', + 'Укажите дату начала отпуска': 'Select the vacation start date', + 'Заявление': 'Application', + 'Загрузите заполненное заявление с электронной подписью (в формате pdf, jpg или png)': + 'Upload the completed application with an electronic signature (in pdf, jpg, or png format)', + 'Основная информация': 'General information', + 'Описание отпуска': 'Vacation description', + 'Куда собираетесь и что будете делать': 'Where are you going and what will you be doing', + 'Начальный текст': 'Initial text', + 'Возможно вам подскаджут, какие места лучше посетить': + 'Others might suggest the best places to visit', + 'Информацию о доступных вам днях отпуска вы можете прочитать по [ссылке](https://www.website.com/timeoff)': + 'You can read about your available vacation days at [this link](https://www.website.com/timeoff)', + 'Заполните форму. После отправки формы в общий чат будет отправлено текстовое уведомление, а ваш отпуск будет сохранен в базе.': + 'Fill out the form. After submitting, a text notification will be sent to the general chat, and your vacation will be saved in the database.', + 'Доступность': 'Availability', + 'Если вы не планируете выходить на связь, то выберите вариант Ничего': + 'If you do not plan to be available, select None', + 'Выберите команду': 'Select a team', + 'Выберите одну из команд': 'Select one of the teams', + 'Время рассылки': 'Newsletter time', + 'Укажите, в какое время присылать выбранные рассылки': + 'Specify the time to send the selected newsletters', +}; + +/** + * Recursively walk the document and replace Russian strings using the dictionary. + * Handles: description, example, default, text fields + array/object values. + */ +function translateRemaining(obj: any): number { + if (obj === null || obj === undefined || typeof obj !== 'object') return 0; + let count = 0; + + if (Array.isArray(obj)) { + for (let i = 0; i < obj.length; i++) { + if (typeof obj[i] === 'string' && CYRILLIC_RE.test(obj[i])) { + const normalized = obj[i].replace(/\s+/g, ' ').trim(); + if (RU_TO_EN[normalized]) { + obj[i] = RU_TO_EN[normalized]; + count++; + } + } else { + count += translateRemaining(obj[i]); + } + } + return count; + } + + for (const key of Object.keys(obj)) { + const val = obj[key]; + if (typeof val === 'string' && CYRILLIC_RE.test(val)) { + const normalized = val.replace(/\s+/g, ' ').trim(); + if (RU_TO_EN[normalized]) { + obj[key] = RU_TO_EN[normalized]; + count++; + } + } else if (typeof val === 'object' && val !== null) { + count += translateRemaining(val); + } + } + return count; +} + +/** + * After post-processing, verify no Cyrillic remains in description/example/default fields. + * Returns list of paths with remaining Cyrillic text. + */ +function findRemainingCyrillic(obj: any, path = '$'): string[] { + if (obj === null || obj === undefined || typeof obj !== 'object') return []; + const found: string[] = []; + + if (Array.isArray(obj)) { + for (let i = 0; i < obj.length; i++) { + if (typeof obj[i] === 'string' && CYRILLIC_RE.test(obj[i])) { + found.push(`${path}[${i}] = "${obj[i].substring(0, 60)}..."`); + } else { + found.push(...findRemainingCyrillic(obj[i], `${path}[${i}]`)); + } + } + return found; + } + + for (const key of Object.keys(obj)) { + const val = obj[key]; + if (typeof val === 'string' && CYRILLIC_RE.test(val)) { + found.push(`${path}.${key} = "${val.substring(0, 80)}..."`); + } else if (typeof val === 'object' && val !== null) { + found.push(...findRemainingCyrillic(val, `${path}.${key}`)); + } + } + return found; +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +function main() { + const specDir = path.resolve(__dirname, '..'); + let basePath = path.join(specDir, 'openapi.yaml'); + let overlayPath = path.join(specDir, 'overlay.en.yaml'); + let outputPath = path.join(specDir, 'openapi.en.yaml'); + + // Parse args + const args = process.argv.slice(2); + for (let i = 0; i < args.length; i++) { + if (args[i] === '--base' && args[i + 1]) basePath = path.resolve(args[++i]); + if (args[i] === '--overlay' && args[i + 1]) overlayPath = path.resolve(args[++i]); + if (args[i] === '--output' && args[i + 1]) outputPath = path.resolve(args[++i]); + } + + // Read files + const baseDoc = yaml.load(fs.readFileSync(basePath, 'utf8')) as any; + const overlay = yaml.load(fs.readFileSync(overlayPath, 'utf8')) as any; + + // Validate overlay structure + if (!overlay.overlay || !overlay.info || !Array.isArray(overlay.actions)) { + throw new Error('Invalid overlay: must have "overlay", "info", and "actions" fields'); + } + + let applied = 0; + let skipped = 0; + let errors = 0; + + for (const action of overlay.actions) { + try { + const { parent, key } = resolveTarget(baseDoc, action.target); + + if (action.remove === true) { + if (Array.isArray(parent)) { + parent.splice(key as number, 1); + } else { + delete parent[key]; + } + } else if (action.update !== undefined) { + if (typeof action.update === 'object' && action.update !== null && !Array.isArray(action.update)) { + parent[key] = deepMerge(parent[key], action.update); + } else { + parent[key] = action.update; + } + } + applied++; + } catch (err) { + const msg = (err as Error).message; + // Skip targets that reference $ref-resolved paths not directly in the spec + if (msg.includes('not found')) { + skipped++; + } else { + console.error(`ERROR applying action: ${msg}`); + errors++; + } + } + } + + if (skipped > 0) { + console.log(`Skipped ${skipped} actions (targets resolved from $ref, not directly present in spec)`); + } + + // Post-process: translate any remaining Russian strings + const translated = translateRemaining(baseDoc); + if (translated > 0) { + console.log(`Post-processed ${translated} remaining Russian strings`); + } + + // Verify no Cyrillic remains — fail build if any found + const remaining = findRemainingCyrillic(baseDoc); + if (remaining.length > 0) { + console.error(`\nERROR: ${remaining.length} strings with Cyrillic text still remain:`); + for (const r of remaining.slice(0, 30)) { + console.error(` ✗ ${r}`); + } + if (remaining.length > 30) { + console.error(` ... and ${remaining.length - 30} more`); + } + console.error(`\nFix: add translations to RU_TO_EN in scripts/apply-overlay.ts or to overlay.en.yaml`); + process.exit(1); + } + + // Write output + const output = yaml.dump(baseDoc, { + lineWidth: 120, + noRefs: true, + quotingType: "'", + forceQuotes: false, + }); + fs.writeFileSync(outputPath, output); + + console.log(`Applied ${applied}/${overlay.actions.length} overlay actions → ${path.basename(outputPath)}`); + if (errors > 0) { + console.error(`${errors} actions failed — see errors above`); + process.exit(1); + } +} + +main(); diff --git a/packages/spec/scripts/validate-overlay.ts b/packages/spec/scripts/validate-overlay.ts new file mode 100644 index 00000000..16f7447e --- /dev/null +++ b/packages/spec/scripts/validate-overlay.ts @@ -0,0 +1,266 @@ +/** + * Validate that overlay.en.yaml covers ALL translatable strings in openapi.yaml. + * Exits with code 1 if ANY translation is missing. + * + * Usage: tsx scripts/validate-overlay.ts [--base openapi.yaml] [--overlay overlay.en.yaml] + */ +import fs from 'node:fs'; +import path from 'node:path'; +import yaml from 'js-yaml'; + +// --------------------------------------------------------------------------- +// Extract all translatable JSONPath targets from the OpenAPI spec +// --------------------------------------------------------------------------- + +function extractTranslatablePaths(doc: any): Set { + const paths = new Set(); + + // Info description + if (doc.info?.description) { + paths.add('$.info.description'); + } + + // Walk paths (operations) + if (doc.paths) { + for (const [route, methods] of Object.entries(doc.paths as Record)) { + const routeKey = `['${route}']`; + for (const [method, op] of Object.entries(methods as Record)) { + if (['get', 'post', 'put', 'patch', 'delete', 'head', 'options'].indexOf(method) === -1) continue; + const opPrefix = `$.paths${routeKey}.${method}`; + + // Operation description + if (op.description) { + paths.add(`${opPrefix}.description`); + } + + // Parameters + if (Array.isArray(op.parameters)) { + for (const param of op.parameters) { + if (param.description) { + paths.add(`${opPrefix}.parameters[?(@.name=='${param.name}')].description`); + } + } + } + + // Request body — walk schema properties + if (op.requestBody?.content) { + for (const [, mediaType] of Object.entries(op.requestBody.content as Record)) { + if ((mediaType as any).schema) { + walkSchema((mediaType as any).schema, `${opPrefix}.requestBody`, paths, doc); + } + } + } + } + } + } + + // Walk component schemas + if (doc.components?.schemas) { + for (const [schemaName, schema] of Object.entries(doc.components.schemas as Record)) { + const prefix = `$.components.schemas.${schemaName}`; + + // Schema-level description + if (schema.description) { + paths.add(`${prefix}.description`); + } + + // x-enum-descriptions + if (schema['x-enum-descriptions']) { + paths.add(`${prefix}.x-enum-descriptions`); + } + + // Schema properties (recursive) + walkProperties(schema, prefix, paths, doc); + } + } + + return paths; +} + +/** + * Walk schema properties recursively to find all description fields. + */ +function walkProperties(schema: any, prefix: string, paths: Set, doc: any): void { + if (!schema || typeof schema !== 'object') return; + + // Handle allOf — merges properties into parent, so walk with parent prefix + if (Array.isArray(schema.allOf)) { + for (const sub of schema.allOf) { + const resolved = resolveRef(sub, doc); + walkProperties(resolved, prefix, paths, doc); + } + } + // NOTE: oneOf/anyOf reference separate schemas that are walked independently + // in the top-level schema loop. Do NOT walk them here with the parent prefix, + // as the properties exist in the referenced schemas, not in the union schema. + + if (schema.properties) { + for (const [propName, propSchema] of Object.entries(schema.properties as Record)) { + const resolved = resolveRef(propSchema, doc); + const propPrefix = `${prefix}.properties.${propName}`; + + if (resolved.description) { + paths.add(`${propPrefix}.description`); + } + + // x-enum-descriptions on property + if (resolved['x-enum-descriptions']) { + paths.add(`${propPrefix}.x-enum-descriptions`); + } + + // Nested object properties + walkProperties(resolved, propPrefix, paths, doc); + + // Array items + if (resolved.items) { + const itemResolved = resolveRef(resolved.items, doc); + walkProperties(itemResolved, `${propPrefix}.items`, paths, doc); + } + } + } +} + +/** + * Walk request body schema (skip $ref to component schemas — those are handled separately). + */ +function walkSchema(schema: any, prefix: string, paths: Set, doc: any): void { + // Request body schemas are usually $ref to components — skip, they're already covered + // Only handle inline schemas + if (schema.$ref) return; + walkProperties(schema, prefix, paths, doc); +} + +/** + * Resolve a $ref to the actual schema object. Only handles local refs. + */ +function resolveRef(schema: any, doc: any): any { + if (!schema || !schema.$ref) return schema || {}; + const ref = schema.$ref; + if (!ref.startsWith('#/')) return schema; + + const parts = ref.replace('#/', '').split('/'); + let current = doc; + for (const part of parts) { + current = current?.[part]; + } + return current || {}; +} + +// --------------------------------------------------------------------------- +// Extract overlay targets +// --------------------------------------------------------------------------- + +function extractOverlayTargets(overlay: any): Set { + const targets = new Set(); + if (!Array.isArray(overlay.actions)) return targets; + + for (const action of overlay.actions) { + if (!action.target) continue; + + if (action.update && typeof action.update === 'object') { + // If update has specific fields, create targets for each + for (const key of Object.keys(action.update)) { + targets.add(`${action.target}.${key}`); + } + } else { + targets.add(action.target); + } + } + return targets; +} + +// --------------------------------------------------------------------------- +// Normalize targets for comparison +// --------------------------------------------------------------------------- + +function normalizeTarget(target: string): string { + // Normalize whitespace and quotes + return target.replace(/\s+/g, ' ').trim(); +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +function main() { + const specDir = path.resolve(__dirname, '..'); + let basePath = path.join(specDir, 'openapi.yaml'); + let overlayPath = path.join(specDir, 'overlay.en.yaml'); + + const args = process.argv.slice(2); + for (let i = 0; i < args.length; i++) { + if (args[i] === '--base' && args[i + 1]) basePath = path.resolve(args[++i]); + if (args[i] === '--overlay' && args[i + 1]) overlayPath = path.resolve(args[++i]); + } + + const baseDoc = yaml.load(fs.readFileSync(basePath, 'utf8')) as any; + const overlay = yaml.load(fs.readFileSync(overlayPath, 'utf8')) as any; + + const translatablePaths = extractTranslatablePaths(baseDoc); + const overlayTargets = extractOverlayTargets(overlay); + + // Normalize both sets + const normalizedTranslatable = new Map(); + for (const p of translatablePaths) { + normalizedTranslatable.set(normalizeTarget(p), p); + } + + const normalizedOverlay = new Map(); + for (const t of overlayTargets) { + normalizedOverlay.set(normalizeTarget(t), t); + } + + // Find missing and stale + const missing: string[] = []; + for (const [norm, original] of normalizedTranslatable) { + if (!normalizedOverlay.has(norm)) { + missing.push(original); + } + } + + const stale: string[] = []; + for (const [norm, original] of normalizedOverlay) { + if (!normalizedTranslatable.has(norm)) { + stale.push(original); + } + } + + // Sort for readability + missing.sort(); + stale.sort(); + + const total = translatablePaths.size; + const translated = total - missing.length; + + // Report + if (missing.length === 0 && stale.length === 0) { + console.log(`✓ Overlay validation passed: ${translated}/${total} strings translated (100%)`); + process.exit(0); + } + + if (missing.length > 0) { + console.error(`\nMissing translations (${missing.length}):`); + for (const m of missing) { + console.error(` ✗ ${m}`); + } + } + + if (stale.length > 0) { + console.warn(`\nStale translations (${stale.length}):`); + for (const s of stale) { + console.warn(` ⚠ ${s}`); + } + } + + console.log(`\nTotal: ${translated}/${total} translated (${((translated / total) * 100).toFixed(1)}%)`); + + if (missing.length > 0) { + console.error(`\nMissing: ${missing.length} — BUILD FAILED`); + process.exit(1); + } + + // Only stale warnings — still pass + process.exit(0); +} + +main(); diff --git a/packages/spec/typespec.tsp b/packages/spec/typespec.tsp index 839b3b62..5a8fdc13 100644 --- a/packages/spec/typespec.tsp +++ b/packages/spec/typespec.tsp @@ -219,11 +219,33 @@ enum TaskStatus { enum SortOrder { @doc("По возрастанию") asc: "asc", - + @doc("По убыванию") desc: "desc", } +@doc("Поле сортировки чатов") +@extension("x-enum-descriptions", #{ + id: "По идентификатору чата", + last_message_at: "По дате и времени создания последнего сообщения", +}) +enum ChatSortField { + @doc("По идентификатору чата") + id: "id", + + @doc("По дате и времени создания последнего сообщения") + last_message_at: "last_message_at", +} + +@doc("Поле сортировки сообщений") +@extension("x-enum-descriptions", #{ + id: "По идентификатору сообщения", +}) +enum MessageSortField { + @doc("По идентификатору сообщения") + id: "id", +} + @doc("Сортировка результатов поиска") @extension("x-enum-descriptions", #{ by_score: "По релевантности", @@ -1110,10 +1132,10 @@ model Task { @doc("Метаданные пагинации") model PaginationMeta { @doc("Вспомогательная информация") - paginate?: { + paginate: { @doc("Курсор пагинации следующей страницы") @example("eyJxZCO2MiwiZGlyIjomSNYjIn3") - next_page?: string; + next_page: string; }; } @@ -1145,10 +1167,17 @@ model DataResponse { @doc("Обертка ответа с данными и пагинацией") model PaginatedDataResponse { data: T; - meta?: PaginationMeta; + meta: PaginationMeta; } +@doc("Данные аватара") +model AvatarData { + @doc("URL аватара") + @example("https://pachca-prod.s3.amazonaws.com/uploads/0001/0001/image.jpg") + image_url: string; +} + // ============================================================================ // OAuth Models // ============================================================================ @@ -1185,8 +1214,10 @@ model PaginatedDataResponse { 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: "Просмотр задач", @@ -1235,8 +1266,10 @@ model PaginatedDataResponse { profile_read: #["owner", "admin", "user", "bot"], profile_status_read: #["owner", "admin", "user", "bot"], profile_status_write: #["owner", "admin", "user", "bot"], + profile_avatar_write: #["owner", "admin", "user", "bot"], user_status_read: #["owner", "admin"], user_status_write: #["owner", "admin"], + user_avatar_write: #["owner", "admin"], custom_properties_read: #["owner", "admin", "user", "bot"], audit_events_read: #["owner"], tasks_read: #["owner", "admin", "user", "bot"], @@ -1342,12 +1375,18 @@ enum OAuthScope { @doc("Изменение и удаление статуса профиля") profile_status_write: "profile_status:write", + @doc("Изменение и удаление аватара профиля") + profile_avatar_write: "profile_avatar:write", + @doc("Просмотр статуса сотрудника") user_status_read: "user_status:read", @doc("Изменение и удаление статуса сотрудника") user_status_write: "user_status:write", + @doc("Изменение и удаление аватара сотрудника") + user_avatar_write: "user_avatar:write", + @doc("Просмотр дополнительных полей") custom_properties_read: "custom_properties:read", @@ -2796,6 +2835,42 @@ model ButtonWebhookPayload { webhook_timestamp: int32; } +@doc("Структура исходящего вебхука о заполнении формы") +model ViewSubmitWebhookPayload { + @doc("Тип объекта") + @example("view") + @extension("x-enum-descriptions", #{ + view: "Для формы всегда view" + }) + type: "view"; + + @doc("Тип события") + @example("submit") + @extension("x-enum-descriptions", #{ + submit: "Отправка формы" + }) + event: "submit"; + + @doc("Идентификатор обратного вызова, указанный при открытии представления") + @example("timeoff_request_form") + callback_id: string | null; + + @doc("Приватные метаданные, указанные при открытии представления") + @example("{'timeoff_id':4378}") + private_metadata: string | null; + + @doc("Идентификатор пользователя, который отправил форму") + @example(1235523) + user_id: int32; + + @doc("Данные заполненных полей представления. Ключ — `action_id` поля, значение — введённые данные") + data: Record; + + @doc("Дата и время отправки вебхука (UTC+0) в формате UNIX") + @example(1755075544) + webhook_timestamp: int32; +} + @doc("Структура исходящего вебхука об участниках чата") model ChatMemberWebhookPayload { @doc("Тип объекта") @@ -2912,6 +2987,7 @@ union WebhookPayloadUnion { MessageWebhookPayload, ReactionWebhookPayload, ButtonWebhookPayload, + ViewSubmitWebhookPayload, ChatMemberWebhookPayload, CompanyMemberWebhookPayload, LinkSharedWebhookPayload, @@ -3247,6 +3323,69 @@ interface ProfileOperations { }; } +// ============================================================================ +// Profile Avatar Operations +// ============================================================================ + +@route("/profile") +@tag("Profile") +interface ProfileAvatarOperations { + @extension("x-requirements", #{ + scope: "profile_avatar:write", + }) + @doc(""" +Загрузка аватара + +Метод для загрузки или обновления аватара своего профиля. Файл передается в формате `multipart/form-data`. +""") + @route("/avatar") + @put + updateProfileAvatar( + @multipartBody body: { + @doc("Файл изображения для аватара") + image: HttpPart; + } + ): { + @statusCode _: 200; + @body body: DataResponse; + } | { + @statusCode _: 401; + @body body: OAuthError; + } | { + @statusCode _: 403; + @body body: ApiError | OAuthError; + } | { + @statusCode _: 402; + @body body: ApiError; + } | { + @statusCode _: 422; + @body body: ApiError; + }; + + @extension("x-requirements", #{ + scope: "profile_avatar:write", + }) + @doc(""" +Удаление аватара + +Метод для удаления аватара своего профиля. +""") + @route("/avatar") + @delete + deleteProfileAvatar(): { + @statusCode _: 204; + } | { + @statusCode _: 401; + @body body: OAuthError; + } | { + @statusCode _: 403; + @body body: ApiError | OAuthError; + } | { + @statusCode _: 402; + @body body: ApiError; + }; +} + // ============================================================================ // User Operations // ============================================================================ @@ -3534,6 +3673,82 @@ interface UserStatusOperations { }; } +// ============================================================================ +// User Avatar Operations +// ============================================================================ + +@route("/users") +@tag("Users") +interface UserAvatarOperations { + @extension("x-requirements", #{ + scope: "user_avatar:write", + }) + @doc(""" +Загрузка аватара сотрудника + +Метод для загрузки или обновления аватара сотрудника. Файл передается в формате `multipart/form-data`. +""") + @route("/{user_id}/avatar") + @put + updateUserAvatar( + @doc("Идентификатор пользователя") + @extension("example", 12) + @path user_id: int32, + @multipartBody body: { + @doc("Файл изображения для аватара") + image: HttpPart; + } + ): { + @statusCode _: 200; + @body body: DataResponse; + } | { + @statusCode _: 401; + @body body: OAuthError; + } | { + @statusCode _: 403; + @body body: ApiError | OAuthError; + } | { + @statusCode _: 404; + @body body: ApiError; + } | { + @statusCode _: 402; + @body body: ApiError; + } | { + @statusCode _: 422; + @body body: ApiError; + }; + + @extension("x-requirements", #{ + scope: "user_avatar:write", + }) + @doc(""" +Удаление аватара сотрудника + +Метод для удаления аватара сотрудника. +""") + @route("/{user_id}/avatar") + @delete + deleteUserAvatar( + @doc("Идентификатор пользователя") + @extension("example", 12) + @path user_id: int32 + ): { + @statusCode _: 204; + } | { + @statusCode _: 401; + @body body: OAuthError; + } | { + @statusCode _: 403; + @body body: ApiError | OAuthError; + } | { + @statusCode _: 404; + @body body: ApiError; + } | { + @statusCode _: 402; + @body body: ApiError; + }; +} + // ============================================================================ // Group Tag Operations // ============================================================================ @@ -3851,13 +4066,12 @@ interface ChatOperations { @extension("x-paginated", true) @get listChats( - @doc("Составной параметр сортировки сущностей выборки") - @extension("x-param-names", #[ - #{ name: "sort[id]", description: "Идентификатор чата" }, - #{ name: "sort[last_message_at]", description: "Дата и время создания последнего сообщения" } - ]) + @doc("Поле сортировки") + @extension("example", "id") + @query sort?: ChatSortField = ChatSortField.id, + @doc("Направление сортировки") @extension("example", "desc") - @query("sort[{field}]") sortField?: SortOrder = SortOrder.desc, + @query order?: SortOrder = SortOrder.desc, @doc("Параметр, который отвечает за доступность и выборку чатов для пользователя") @extension("example", "is_member") @query availability?: ChatAvailability = ChatAvailability.is_member, @@ -4577,12 +4791,12 @@ interface ChatMessageOperations { @doc("Идентификатор чата (беседа, канал, диалог или чат треда)") @extension("example", 198) @query chat_id: int32, - @doc("Составной параметр сортировки сущностей выборки") - @extension("x-param-names", #[ - #{ name: "sort[id]", description: "Идентификатор сообщения" } - ]) + @doc("Поле сортировки") + @extension("example", "id") + @query sort?: MessageSortField = MessageSortField.id, + @doc("Направление сортировки") @extension("example", "desc") - @query("sort[{field}]") sortField?: SortOrder = SortOrder.desc, + @query order?: SortOrder = SortOrder.desc, @doc("Количество возвращаемых сущностей за один запрос") @query @extension("example", 1) diff --git a/packages/spec/workflows.ts b/packages/spec/workflows.ts index 7caff284..02e03b91 100644 --- a/packages/spec/workflows.ts +++ b/packages/spec/workflows.ts @@ -1367,6 +1367,36 @@ export const WORKFLOWS: Record = { }, ], }, + { + title: 'Загрузить аватар сотрудника', + titleEn: 'Upload employee avatar', + steps: [ + { + description: 'Загрузи аватар сотруднику', + descriptionEn: 'Upload avatar for employee', + command: 'pachca users update-avatar --file=<путь_к_файлу>', + apiMethod: 'PUT', + apiPath: '/users/{user_id}/avatar', + notes: 'Требует прав администратора. Файл передается в формате multipart/form-data', + notesEn: 'Requires admin access. File is sent in multipart/form-data format', + }, + ], + }, + { + title: 'Удалить аватар сотрудника', + titleEn: 'Delete employee avatar', + steps: [ + { + description: 'Удали аватар сотрудника', + descriptionEn: 'Delete employee avatar', + command: 'pachca users remove-avatar --force', + apiMethod: 'DELETE', + apiPath: '/users/{user_id}/avatar', + notes: 'Требует прав администратора', + notesEn: 'Requires admin access', + }, + ], + }, ], 'pachca-tasks': [ { @@ -1575,6 +1605,34 @@ export const WORKFLOWS: Record = { notes: 'Кастомные поля настраиваются администратором пространства.', notesEn: 'Custom fields are configured by workspace admin.', }, + { + title: 'Загрузить аватар профиля', + titleEn: 'Upload profile avatar', + steps: [ + { + description: 'Загрузи аватар из файла', + descriptionEn: 'Upload avatar from file', + command: 'pachca profile update-avatar --file=<путь_к_файлу>', + apiMethod: 'PUT', + apiPath: '/profile/avatar', + notes: 'Файл изображения передается в формате multipart/form-data', + notesEn: 'Image file is sent in multipart/form-data format', + }, + ], + }, + { + title: 'Удалить аватар профиля', + titleEn: 'Delete profile avatar', + steps: [ + { + description: 'Удали аватар', + descriptionEn: 'Delete avatar', + command: 'pachca profile delete-avatar --force', + apiMethod: 'DELETE', + apiPath: '/profile/avatar', + }, + ], + }, ], 'pachca-search': [ { diff --git a/sdk/csharp/examples/MainExample.cs b/sdk/csharp/examples/MainExample.cs index 3e38f8d8..542d19f9 100644 --- a/sdk/csharp/examples/MainExample.cs +++ b/sdk/csharp/examples/MainExample.cs @@ -23,6 +23,11 @@ public static async Task RunAsync() using var client = new PachcaClient(token); + // -- Step 0: GET -- Fetch chat (verifies datetime deserialization) + Console.WriteLine("0. Fetching chat..."); + var chat = await client.Chats.GetChatAsync(chatId); + Console.WriteLine($" Chat: {chat.Name}, createdAt={chat.CreatedAt} ({chat.CreatedAt.GetType().Name}), lastMessageAt={chat.LastMessageAt} ({chat.LastMessageAt.GetType().Name})"); + // -- Step 1: POST -- Create a message Console.WriteLine("1. Creating message..."); var created = await client.Messages.CreateMessageAsync(new MessageCreateRequest diff --git a/sdk/csharp/generated/Client.cs b/sdk/csharp/generated/Client.cs index 044b2aae..873bf5f1 100644 --- a/sdk/csharp/generated/Client.cs +++ b/sdk/csharp/generated/Client.cs @@ -51,7 +51,7 @@ public async System.Threading.Tasks.Task GetAuditEventsA if (entityType != null) queryParts.Add($"entity_type={Uri.EscapeDataString(entityType)}"); if (limit != null) - queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString())}"); + queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString()!)}"); if (cursor != null) queryParts.Add($"cursor={Uri.EscapeDataString(cursor)}"); var url = $"{_baseUrl}/audit_events" + (queryParts.Count > 0 ? "?" + string.Join("&", queryParts) : ""); @@ -86,8 +86,9 @@ public async System.Threading.Tasks.Task> GetAuditEventsAllAsyn { var response = await GetAuditEventsAsync(startTime: startTime, endTime: endTime, eventKey: eventKey, actorId: actorId, actorType: actorType, entityId: entityId, entityType: entityType, limit: limit, cursor: cursor, cancellationToken: cancellationToken).ConfigureAwait(false); items.AddRange(response.Data); - cursor = response.Meta?.Paginate?.NextPage; - } while (cursor != null); + if (response.Data.Count == 0) break; + cursor = response.Meta.Paginate.NextPage; + } while (true); return items; } } @@ -110,7 +111,7 @@ public async System.Threading.Tasks.Task GetWebhookEve { var queryParts = new List(); if (limit != null) - queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString())}"); + queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString()!)}"); if (cursor != null) queryParts.Add($"cursor={Uri.EscapeDataString(cursor)}"); var url = $"{_baseUrl}/webhooks/events" + (queryParts.Count > 0 ? "?" + string.Join("&", queryParts) : ""); @@ -138,8 +139,9 @@ public async System.Threading.Tasks.Task> GetWebhookEventsAll { var response = await GetWebhookEventsAsync(limit: limit, cursor: cursor, cancellationToken: cancellationToken).ConfigureAwait(false); items.AddRange(response.Data); - cursor = response.Meta?.Paginate?.NextPage; - } while (cursor != null); + if (response.Data.Count == 0) break; + cursor = response.Meta.Paginate.NextPage; + } while (true); return items; } @@ -194,7 +196,8 @@ internal ChatsService(string baseUrl, HttpClient client) } public async System.Threading.Tasks.Task ListChatsAsync( - SortOrder? sortId = null, + ChatSortField? sort = null, + SortOrder? order = null, ChatAvailability? availability = null, DateTimeOffset? lastMessageAtAfter = null, DateTimeOffset? lastMessageAtBefore = null, @@ -204,8 +207,10 @@ public async System.Threading.Tasks.Task ListChatsAsync( CancellationToken cancellationToken = default) { var queryParts = new List(); - if (sortId != null) - queryParts.Add($"sort[{{field}}]={Uri.EscapeDataString(PachcaUtils.EnumToApiString(sortId.Value))}"); + if (sort != null) + queryParts.Add($"sort={Uri.EscapeDataString(PachcaUtils.EnumToApiString(sort.Value))}"); + if (order != null) + queryParts.Add($"order={Uri.EscapeDataString(PachcaUtils.EnumToApiString(order.Value))}"); if (availability != null) queryParts.Add($"availability={Uri.EscapeDataString(PachcaUtils.EnumToApiString(availability.Value))}"); if (lastMessageAtAfter != null) @@ -215,7 +220,7 @@ public async System.Threading.Tasks.Task ListChatsAsync( if (personal != null) queryParts.Add($"personal={Uri.EscapeDataString((personal.Value ? "true" : "false"))}"); if (limit != null) - queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString())}"); + queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString()!)}"); if (cursor != null) queryParts.Add($"cursor={Uri.EscapeDataString(cursor)}"); var url = $"{_baseUrl}/chats" + (queryParts.Count > 0 ? "?" + string.Join("&", queryParts) : ""); @@ -234,7 +239,8 @@ public async System.Threading.Tasks.Task ListChatsAsync( } public async System.Threading.Tasks.Task> ListChatsAllAsync( - SortOrder? sortId = null, + ChatSortField? sort = null, + SortOrder? order = null, ChatAvailability? availability = null, DateTimeOffset? lastMessageAtAfter = null, DateTimeOffset? lastMessageAtBefore = null, @@ -246,10 +252,11 @@ public async System.Threading.Tasks.Task> ListChatsAllAsync( string? cursor = null; do { - var response = await ListChatsAsync(sortId: sortId, availability: availability, lastMessageAtAfter: lastMessageAtAfter, lastMessageAtBefore: lastMessageAtBefore, personal: personal, limit: limit, cursor: cursor, cancellationToken: cancellationToken).ConfigureAwait(false); + var response = await ListChatsAsync(sort: sort, order: order, availability: availability, lastMessageAtAfter: lastMessageAtAfter, lastMessageAtBefore: lastMessageAtBefore, personal: personal, limit: limit, cursor: cursor, cancellationToken: cancellationToken).ConfigureAwait(false); items.AddRange(response.Data); - cursor = response.Meta?.Paginate?.NextPage; - } while (cursor != null); + if (response.Data.Count == 0) break; + cursor = response.Meta.Paginate.NextPage; + } while (true); return items; } @@ -480,7 +487,7 @@ public async System.Threading.Tasks.Task ListMembersAsync( if (role != null) queryParts.Add($"role={Uri.EscapeDataString(PachcaUtils.EnumToApiString(role.Value))}"); if (limit != null) - queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString())}"); + queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString()!)}"); if (cursor != null) queryParts.Add($"cursor={Uri.EscapeDataString(cursor)}"); var url = $"{_baseUrl}/chats/{id}/members" + (queryParts.Count > 0 ? "?" + string.Join("&", queryParts) : ""); @@ -510,8 +517,9 @@ public async System.Threading.Tasks.Task> ListMembersAllAsync( { var response = await ListMembersAsync(id, role: role, limit: limit, cursor: cursor, cancellationToken: cancellationToken).ConfigureAwait(false); items.AddRange(response.Data); - cursor = response.Meta?.Paginate?.NextPage; - } while (cursor != null); + if (response.Data.Count == 0) break; + cursor = response.Meta.Paginate.NextPage; + } while (true); return items; } @@ -651,16 +659,17 @@ internal GroupTagsService(string baseUrl, HttpClient client) } public async System.Threading.Tasks.Task ListTagsAsync( - TagNamesFilter? names = null, + List? names = null, int? limit = null, string? cursor = null, CancellationToken cancellationToken = default) { var queryParts = new List(); if (names != null) - queryParts.Add($"names={Uri.EscapeDataString(names.ToString())}"); + foreach (var item in names) + queryParts.Add($"names[]={Uri.EscapeDataString(item.ToString()!)}"); if (limit != null) - queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString())}"); + queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString()!)}"); if (cursor != null) queryParts.Add($"cursor={Uri.EscapeDataString(cursor)}"); var url = $"{_baseUrl}/group_tags" + (queryParts.Count > 0 ? "?" + string.Join("&", queryParts) : ""); @@ -679,7 +688,7 @@ public async System.Threading.Tasks.Task ListTagsAsync( } public async System.Threading.Tasks.Task> ListTagsAllAsync( - TagNamesFilter? names = null, + List? names = null, int? limit = null, CancellationToken cancellationToken = default) { @@ -689,8 +698,9 @@ public async System.Threading.Tasks.Task> ListTagsAllAsync( { var response = await ListTagsAsync(names: names, limit: limit, cursor: cursor, cancellationToken: cancellationToken).ConfigureAwait(false); items.AddRange(response.Data); - cursor = response.Meta?.Paginate?.NextPage; - } while (cursor != null); + if (response.Data.Count == 0) break; + cursor = response.Meta.Paginate.NextPage; + } while (true); return items; } @@ -711,7 +721,7 @@ public async System.Threading.Tasks.Task GetTagAsync(int id, Cancellat } } - public async System.Threading.Tasks.Task GetTagUsersAsync( + public async System.Threading.Tasks.Task GetTagUsersAsync( int id, int? limit = null, string? cursor = null, @@ -719,7 +729,7 @@ public async System.Threading.Tasks.Task GetTagUsersAsync( { var queryParts = new List(); if (limit != null) - queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString())}"); + queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString()!)}"); if (cursor != null) queryParts.Add($"cursor={Uri.EscapeDataString(cursor)}"); var url = $"{_baseUrl}/group_tags/{id}/users" + (queryParts.Count > 0 ? "?" + string.Join("&", queryParts) : ""); @@ -729,7 +739,7 @@ public async System.Threading.Tasks.Task GetTagUsersAsync( switch ((int)response.StatusCode) { case 200: - return PachcaUtils.Deserialize(json); + return PachcaUtils.Deserialize(json); case 401: throw PachcaUtils.Deserialize(json); default: @@ -748,8 +758,9 @@ public async System.Threading.Tasks.Task> GetTagUsersAllAsync( { var response = await GetTagUsersAsync(id, limit: limit, cursor: cursor, cancellationToken: cancellationToken).ConfigureAwait(false); items.AddRange(response.Data); - cursor = response.Meta?.Paginate?.NextPage; - } while (cursor != null); + if (response.Data.Count == 0) break; + cursor = response.Meta.Paginate.NextPage; + } while (true); return items; } @@ -823,17 +834,20 @@ internal MessagesService(string baseUrl, HttpClient client) public async System.Threading.Tasks.Task ListChatMessagesAsync( int chatId, - SortOrder? sortId = null, + MessageSortField? sort = null, + SortOrder? order = null, int? limit = null, string? cursor = null, CancellationToken cancellationToken = default) { var queryParts = new List(); - queryParts.Add($"chat_id={Uri.EscapeDataString(chatId.ToString())}"); - if (sortId != null) - queryParts.Add($"sort[{{field}}]={Uri.EscapeDataString(PachcaUtils.EnumToApiString(sortId.Value))}"); + queryParts.Add($"chat_id={Uri.EscapeDataString(chatId.ToString()!)}"); + if (sort != null) + queryParts.Add($"sort={Uri.EscapeDataString(PachcaUtils.EnumToApiString(sort.Value))}"); + if (order != null) + queryParts.Add($"order={Uri.EscapeDataString(PachcaUtils.EnumToApiString(order.Value))}"); if (limit != null) - queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString())}"); + queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString()!)}"); if (cursor != null) queryParts.Add($"cursor={Uri.EscapeDataString(cursor)}"); var url = $"{_baseUrl}/messages" + (queryParts.Count > 0 ? "?" + string.Join("&", queryParts) : ""); @@ -853,7 +867,8 @@ public async System.Threading.Tasks.Task ListChatMessa public async System.Threading.Tasks.Task> ListChatMessagesAllAsync( int chatId, - SortOrder? sortId = null, + MessageSortField? sort = null, + SortOrder? order = null, int? limit = null, CancellationToken cancellationToken = default) { @@ -861,10 +876,11 @@ public async System.Threading.Tasks.Task> ListChatMessagesAllAsync string? cursor = null; do { - var response = await ListChatMessagesAsync(chatId: chatId, sortId: sortId, limit: limit, cursor: cursor, cancellationToken: cancellationToken).ConfigureAwait(false); + var response = await ListChatMessagesAsync(chatId: chatId, sort: sort, order: order, limit: limit, cursor: cursor, cancellationToken: cancellationToken).ConfigureAwait(false); items.AddRange(response.Data); - cursor = response.Meta?.Paginate?.NextPage; - } while (cursor != null); + if (response.Data.Count == 0) break; + cursor = response.Meta.Paginate.NextPage; + } while (true); return items; } @@ -1028,7 +1044,7 @@ public async System.Threading.Tasks.Task ListReactionsAsy { var queryParts = new List(); if (limit != null) - queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString())}"); + queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString()!)}"); if (cursor != null) queryParts.Add($"cursor={Uri.EscapeDataString(cursor)}"); var url = $"{_baseUrl}/messages/{id}/reactions" + (queryParts.Count > 0 ? "?" + string.Join("&", queryParts) : ""); @@ -1057,8 +1073,9 @@ public async System.Threading.Tasks.Task> ListReactionsAllAsync( { var response = await ListReactionsAsync(id, limit: limit, cursor: cursor, cancellationToken: cancellationToken).ConfigureAwait(false); items.AddRange(response.Data); - cursor = response.Meta?.Paginate?.NextPage; - } while (cursor != null); + if (response.Data.Count == 0) break; + cursor = response.Meta.Paginate.NextPage; + } while (true); return items; } @@ -1128,7 +1145,7 @@ public async System.Threading.Tasks.Task ListReadMembersAsync( { var queryParts = new List(); if (limit != null) - queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString())}"); + queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString()!)}"); if (cursor != null) queryParts.Add($"cursor={Uri.EscapeDataString(cursor)}"); var url = $"{_baseUrl}/messages/{id}/read_member_ids" + (queryParts.Count > 0 ? "?" + string.Join("&", queryParts) : ""); @@ -1255,6 +1272,26 @@ public async System.Threading.Tasks.Task GetStatusAsync(CancellationToke } } + public async System.Threading.Tasks.Task UpdateProfileAvatarAsync(byte[] image, CancellationToken cancellationToken = default) + { + var url = $"{_baseUrl}/profile/avatar"; + using var content = new MultipartFormDataContent(); + content.Add(new ByteArrayContent(image), "image", "image"); + using var httpRequest = new HttpRequestMessage(HttpMethod.Put, url); + httpRequest.Content = content; + using var response = await PachcaUtils.SendWithRetryAsync(_client, httpRequest, cancellationToken).ConfigureAwait(false); + var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + switch ((int)response.StatusCode) + { + case 200: + return PachcaUtils.Deserialize(json).Data; + case 401: + throw PachcaUtils.Deserialize(json); + default: + throw PachcaUtils.Deserialize(json); + } + } + public async System.Threading.Tasks.Task UpdateStatusAsync(StatusUpdateRequest request, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/profile/status"; @@ -1273,6 +1310,23 @@ public async System.Threading.Tasks.Task UpdateStatusAsync(StatusUpd } } + public async System.Threading.Tasks.Task DeleteProfileAvatarAsync(CancellationToken cancellationToken = default) + { + var url = $"{_baseUrl}/profile/avatar"; + using var request = new HttpRequestMessage(HttpMethod.Delete, url); + using var response = await PachcaUtils.SendWithRetryAsync(_client, request, cancellationToken).ConfigureAwait(false); + var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + switch ((int)response.StatusCode) + { + case 204: + return; + case 401: + throw PachcaUtils.Deserialize(json); + default: + throw PachcaUtils.Deserialize(json); + } + } + public async System.Threading.Tasks.Task DeleteStatusAsync(CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/profile/status"; @@ -1302,7 +1356,7 @@ internal SearchService(string baseUrl, HttpClient client) _client = client; } - public async System.Threading.Tasks.Task SearchChatsAsync( + public async System.Threading.Tasks.Task SearchChatsAsync( string? query = null, int? limit = null, string? cursor = null, @@ -1318,7 +1372,7 @@ public async System.Threading.Tasks.Task SearchChatsAsync( if (query != null) queryParts.Add($"query={Uri.EscapeDataString(query)}"); if (limit != null) - queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString())}"); + queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString()!)}"); if (cursor != null) queryParts.Add($"cursor={Uri.EscapeDataString(cursor)}"); if (order != null) @@ -1340,7 +1394,7 @@ public async System.Threading.Tasks.Task SearchChatsAsync( switch ((int)response.StatusCode) { case 200: - return PachcaUtils.Deserialize(json); + return PachcaUtils.Deserialize(json); case 401: throw PachcaUtils.Deserialize(json); default: @@ -1365,12 +1419,13 @@ public async System.Threading.Tasks.Task> SearchChatsAllAsync( { var response = await SearchChatsAsync(query: query, limit: limit, cursor: cursor, order: order, createdFrom: createdFrom, createdTo: createdTo, active: active, chatSubtype: chatSubtype, personal: personal, cancellationToken: cancellationToken).ConfigureAwait(false); items.AddRange(response.Data); - cursor = response.Meta?.Paginate?.NextPage; - } while (cursor != null); + if (response.Data.Count == 0) break; + cursor = response.Meta.Paginate.NextPage; + } while (true); return items; } - public async System.Threading.Tasks.Task SearchMessagesAsync( + public async System.Threading.Tasks.Task SearchMessagesAsync( string? query = null, int? limit = null, string? cursor = null, @@ -1386,7 +1441,7 @@ public async System.Threading.Tasks.Task SearchMessage if (query != null) queryParts.Add($"query={Uri.EscapeDataString(query)}"); if (limit != null) - queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString())}"); + queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString()!)}"); if (cursor != null) queryParts.Add($"cursor={Uri.EscapeDataString(cursor)}"); if (order != null) @@ -1396,9 +1451,11 @@ public async System.Threading.Tasks.Task SearchMessage if (createdTo != null) queryParts.Add($"created_to={Uri.EscapeDataString(createdTo.Value.ToString("o"))}"); if (chatIds != null) - queryParts.Add($"chat_ids={Uri.EscapeDataString(chatIds.ToString())}"); + foreach (var item in chatIds) + queryParts.Add($"chat_ids[]={Uri.EscapeDataString(item.ToString()!)}"); if (userIds != null) - queryParts.Add($"user_ids={Uri.EscapeDataString(userIds.ToString())}"); + foreach (var item in userIds) + queryParts.Add($"user_ids[]={Uri.EscapeDataString(item.ToString()!)}"); if (active != null) queryParts.Add($"active={Uri.EscapeDataString((active.Value ? "true" : "false"))}"); var url = $"{_baseUrl}/search/messages" + (queryParts.Count > 0 ? "?" + string.Join("&", queryParts) : ""); @@ -1408,7 +1465,7 @@ public async System.Threading.Tasks.Task SearchMessage switch ((int)response.StatusCode) { case 200: - return PachcaUtils.Deserialize(json); + return PachcaUtils.Deserialize(json); case 401: throw PachcaUtils.Deserialize(json); default: @@ -1433,12 +1490,13 @@ public async System.Threading.Tasks.Task> SearchMessagesAllAsync( { var response = await SearchMessagesAsync(query: query, limit: limit, cursor: cursor, order: order, createdFrom: createdFrom, createdTo: createdTo, chatIds: chatIds, userIds: userIds, active: active, cancellationToken: cancellationToken).ConfigureAwait(false); items.AddRange(response.Data); - cursor = response.Meta?.Paginate?.NextPage; - } while (cursor != null); + if (response.Data.Count == 0) break; + cursor = response.Meta.Paginate.NextPage; + } while (true); return items; } - public async System.Threading.Tasks.Task SearchUsersAsync( + public async System.Threading.Tasks.Task SearchUsersAsync( string? query = null, int? limit = null, string? cursor = null, @@ -1453,7 +1511,7 @@ public async System.Threading.Tasks.Task SearchUsersAsync( if (query != null) queryParts.Add($"query={Uri.EscapeDataString(query)}"); if (limit != null) - queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString())}"); + queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString()!)}"); if (cursor != null) queryParts.Add($"cursor={Uri.EscapeDataString(cursor)}"); if (sort != null) @@ -1465,7 +1523,8 @@ public async System.Threading.Tasks.Task SearchUsersAsync( if (createdTo != null) queryParts.Add($"created_to={Uri.EscapeDataString(createdTo.Value.ToString("o"))}"); if (companyRoles != null) - queryParts.Add($"company_roles={Uri.EscapeDataString(companyRoles.ToString())}"); + foreach (var item in companyRoles) + queryParts.Add($"company_roles[]={Uri.EscapeDataString(PachcaUtils.EnumToApiString(item))}"); var url = $"{_baseUrl}/search/users" + (queryParts.Count > 0 ? "?" + string.Join("&", queryParts) : ""); using var request = new HttpRequestMessage(HttpMethod.Get, url); using var response = await PachcaUtils.SendWithRetryAsync(_client, request, cancellationToken).ConfigureAwait(false); @@ -1473,7 +1532,7 @@ public async System.Threading.Tasks.Task SearchUsersAsync( switch ((int)response.StatusCode) { case 200: - return PachcaUtils.Deserialize(json); + return PachcaUtils.Deserialize(json); case 401: throw PachcaUtils.Deserialize(json); default: @@ -1497,8 +1556,9 @@ public async System.Threading.Tasks.Task> SearchUsersAllAsync( { var response = await SearchUsersAsync(query: query, limit: limit, cursor: cursor, sort: sort, order: order, createdFrom: createdFrom, createdTo: createdTo, companyRoles: companyRoles, cancellationToken: cancellationToken).ConfigureAwait(false); items.AddRange(response.Data); - cursor = response.Meta?.Paginate?.NextPage; - } while (cursor != null); + if (response.Data.Count == 0) break; + cursor = response.Meta.Paginate.NextPage; + } while (true); return items; } } @@ -1521,7 +1581,7 @@ public async System.Threading.Tasks.Task ListTasksAsync( { var queryParts = new List(); if (limit != null) - queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString())}"); + queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString()!)}"); if (cursor != null) queryParts.Add($"cursor={Uri.EscapeDataString(cursor)}"); var url = $"{_baseUrl}/tasks" + (queryParts.Count > 0 ? "?" + string.Join("&", queryParts) : ""); @@ -1549,8 +1609,9 @@ public async System.Threading.Tasks.Task ListTasksAsync( { var response = await ListTasksAsync(limit: limit, cursor: cursor, cancellationToken: cancellationToken).ConfigureAwait(false); items.AddRange(response.Data); - cursor = response.Meta?.Paginate?.NextPage; - } while (cursor != null); + if (response.Data.Count == 0) break; + cursor = response.Meta.Paginate.NextPage; + } while (true); return items; } @@ -1639,7 +1700,7 @@ internal UsersService(string baseUrl, HttpClient client) _client = client; } - public async System.Threading.Tasks.Task ListUsersAsync( + public async System.Threading.Tasks.Task ListUsersAsync( string? query = null, int? limit = null, string? cursor = null, @@ -1649,7 +1710,7 @@ public async System.Threading.Tasks.Task ListUsersAsync( if (query != null) queryParts.Add($"query={Uri.EscapeDataString(query)}"); if (limit != null) - queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString())}"); + queryParts.Add($"limit={Uri.EscapeDataString(limit.Value.ToString()!)}"); if (cursor != null) queryParts.Add($"cursor={Uri.EscapeDataString(cursor)}"); var url = $"{_baseUrl}/users" + (queryParts.Count > 0 ? "?" + string.Join("&", queryParts) : ""); @@ -1659,7 +1720,7 @@ public async System.Threading.Tasks.Task ListUsersAsync( switch ((int)response.StatusCode) { case 200: - return PachcaUtils.Deserialize(json); + return PachcaUtils.Deserialize(json); case 401: throw PachcaUtils.Deserialize(json); default: @@ -1678,8 +1739,9 @@ public async System.Threading.Tasks.Task> ListUsersAllAsync( { var response = await ListUsersAsync(query: query, limit: limit, cursor: cursor, cancellationToken: cancellationToken).ConfigureAwait(false); items.AddRange(response.Data); - cursor = response.Meta?.Paginate?.NextPage; - } while (cursor != null); + if (response.Data.Count == 0) break; + cursor = response.Meta.Paginate.NextPage; + } while (true); return items; } @@ -1756,6 +1818,29 @@ public async System.Threading.Tasks.Task UpdateUserAsync( } } + public async System.Threading.Tasks.Task UpdateUserAvatarAsync( + int userId, + byte[] image, + CancellationToken cancellationToken = default) + { + var url = $"{_baseUrl}/users/{userId}/avatar"; + using var content = new MultipartFormDataContent(); + content.Add(new ByteArrayContent(image), "image", "image"); + using var httpRequest = new HttpRequestMessage(HttpMethod.Put, url); + httpRequest.Content = content; + using var response = await PachcaUtils.SendWithRetryAsync(_client, httpRequest, cancellationToken).ConfigureAwait(false); + var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + switch ((int)response.StatusCode) + { + case 200: + return PachcaUtils.Deserialize(json).Data; + case 401: + throw PachcaUtils.Deserialize(json); + default: + throw PachcaUtils.Deserialize(json); + } + } + public async System.Threading.Tasks.Task UpdateUserStatusAsync( int userId, StatusUpdateRequest request, @@ -1794,6 +1879,23 @@ public async System.Threading.Tasks.Task DeleteUserAsync(int id, CancellationTok } } + public async System.Threading.Tasks.Task DeleteUserAvatarAsync(int userId, CancellationToken cancellationToken = default) + { + var url = $"{_baseUrl}/users/{userId}/avatar"; + using var request = new HttpRequestMessage(HttpMethod.Delete, url); + using var response = await PachcaUtils.SendWithRetryAsync(_client, request, cancellationToken).ConfigureAwait(false); + var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + switch ((int)response.StatusCode) + { + case 204: + return; + case 401: + throw PachcaUtils.Deserialize(json); + default: + throw PachcaUtils.Deserialize(json); + } + } + public async System.Threading.Tasks.Task DeleteUserStatusAsync(int userId, CancellationToken cancellationToken = default) { var url = $"{_baseUrl}/users/{userId}/status"; diff --git a/sdk/csharp/generated/Models.cs b/sdk/csharp/generated/Models.cs index 54274a39..416c0870 100644 --- a/sdk/csharp/generated/Models.cs +++ b/sdk/csharp/generated/Models.cs @@ -295,6 +295,41 @@ public override void Write(Utf8JsonWriter writer, ChatMemberRoleFilter value, Js } } +/// Поле сортировки чатов +[JsonConverter(typeof(ChatSortFieldConverter))] +public enum ChatSortField +{ + /// По идентификатору чата + Id, + /// По дате и времени создания последнего сообщения + LastMessageAt, +} + +internal class ChatSortFieldConverter : JsonConverter +{ + public override ChatSortField Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var value = reader.GetString(); + return value switch + { + "id" => ChatSortField.Id, + "last_message_at" => ChatSortField.LastMessageAt, + _ => throw new JsonException($"Unknown ChatSortField value: {value}"), + }; + } + + public override void Write(Utf8JsonWriter writer, ChatSortField value, JsonSerializerOptions options) + { + var str = value switch + { + ChatSortField.Id => "id", + ChatSortField.LastMessageAt => "last_message_at", + _ => value.ToString(), + }; + writer.WriteStringValue(str); + } +} + /// Тип чата [JsonConverter(typeof(ChatSubtypeConverter))] public enum ChatSubtype @@ -517,6 +552,36 @@ public override void Write(Utf8JsonWriter writer, MessageEntityType value, JsonS } } +[JsonConverter(typeof(MessageSortFieldConverter))] +public enum MessageSortField +{ + /// По идентификатору сообщения + Id, +} + +internal class MessageSortFieldConverter : JsonConverter +{ + public override MessageSortField Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var value = reader.GetString(); + return value switch + { + "id" => MessageSortField.Id, + _ => throw new JsonException($"Unknown MessageSortField value: {value}"), + }; + } + + public override void Write(Utf8JsonWriter writer, MessageSortField value, JsonSerializerOptions options) + { + var str = value switch + { + MessageSortField.Id => "id", + _ => value.ToString(), + }; + writer.WriteStringValue(str); + } +} + /// Скоуп доступа OAuth токена [JsonConverter(typeof(OAuthScopeConverter))] public enum OAuthScope @@ -579,10 +644,14 @@ public enum OAuthScope ProfileStatusRead, /// Изменение и удаление статуса профиля ProfileStatusWrite, + /// Изменение и удаление аватара профиля + ProfileAvatarWrite, /// Просмотр статуса сотрудника UserStatusRead, /// Изменение и удаление статуса сотрудника UserStatusWrite, + /// Изменение и удаление аватара сотрудника + UserAvatarWrite, /// Просмотр дополнительных полей CustomPropertiesRead, /// Просмотр журнала аудита @@ -655,8 +724,10 @@ public override OAuthScope Read(ref Utf8JsonReader reader, Type typeToConvert, J "profile:read" => OAuthScope.ProfileRead, "profile_status:read" => OAuthScope.ProfileStatusRead, "profile_status:write" => OAuthScope.ProfileStatusWrite, + "profile_avatar:write" => OAuthScope.ProfileAvatarWrite, "user_status:read" => OAuthScope.UserStatusRead, "user_status:write" => OAuthScope.UserStatusWrite, + "user_avatar:write" => OAuthScope.UserAvatarWrite, "custom_properties:read" => OAuthScope.CustomPropertiesRead, "audit_events:read" => OAuthScope.AuditEventsRead, "tasks:read" => OAuthScope.TasksRead, @@ -711,8 +782,10 @@ public override void Write(Utf8JsonWriter writer, OAuthScope value, JsonSerializ OAuthScope.ProfileRead => "profile:read", OAuthScope.ProfileStatusRead => "profile_status:read", OAuthScope.ProfileStatusWrite => "profile_status:write", + OAuthScope.ProfileAvatarWrite => "profile_avatar:write", OAuthScope.UserStatusRead => "user_status:read", OAuthScope.UserStatusWrite => "user_status:write", + OAuthScope.UserAvatarWrite => "user_avatar:write", OAuthScope.CustomPropertiesRead => "custom_properties:read", OAuthScope.AuditEventsRead => "audit_events:read", OAuthScope.TasksRead => "tasks:read", @@ -1570,7 +1643,7 @@ public class ViewBlockDate : ViewBlockUnion [JsonPropertyName("label")] public string Label { get; set; } = default!; [JsonPropertyName("initial_date")] - public DateOnly? InitialDate { get; set; } + public string? InitialDate { get; set; } [JsonPropertyName("required")] public bool? Required { get; set; } [JsonPropertyName("hint")] @@ -1613,6 +1686,7 @@ public class ViewBlockFileInput : ViewBlockUnion [JsonDerivedType(typeof(MessageWebhookPayload), "message")] [JsonDerivedType(typeof(ReactionWebhookPayload), "reaction")] [JsonDerivedType(typeof(ButtonWebhookPayload), "button")] +[JsonDerivedType(typeof(ViewSubmitWebhookPayload), "view")] [JsonDerivedType(typeof(ChatMemberWebhookPayload), "chat_member")] [JsonDerivedType(typeof(CompanyMemberWebhookPayload), "company_member")] [JsonDerivedType(typeof(LinkSharedWebhookPayload), "message")] @@ -1687,6 +1761,21 @@ public class ButtonWebhookPayload : WebhookPayloadUnion public int WebhookTimestamp { get; set; } = default!; } +public class ViewSubmitWebhookPayload : WebhookPayloadUnion +{ + public override string Type => "view"; + [JsonPropertyName("callback_id")] + public string? CallbackId { get; set; } + [JsonPropertyName("private_metadata")] + public string? PrivateMetadata { get; set; } + [JsonPropertyName("user_id")] + public int UserId { get; set; } = default!; + [JsonPropertyName("data")] + public Dictionary Data { get; set; } = default!; + [JsonPropertyName("webhook_timestamp")] + public int WebhookTimestamp { get; set; } = default!; +} + public class ChatMemberWebhookPayload : WebhookPayloadUnion { public override string Type => "chat_member"; @@ -1814,6 +1903,12 @@ public class AuditEvent public string UserAgent { get; set; } = default!; } +public class AvatarData +{ + [JsonPropertyName("image_url")] + public string ImageUrl { get; set; } = default!; +} + public class BotResponseWebhook { [JsonPropertyName("outgoing_url")] @@ -1941,9 +2036,9 @@ public class CustomPropertyDefinition public class ExportRequest { [JsonPropertyName("start_at")] - public DateOnly StartAt { get; set; } = default!; + public string StartAt { get; set; } = default!; [JsonPropertyName("end_at")] - public DateOnly EndAt { get; set; } = default!; + public string EndAt { get; set; } = default!; [JsonPropertyName("webhook_url")] public string WebhookUrl { get; set; } = default!; [JsonPropertyName("chat_ids")] @@ -2227,13 +2322,13 @@ public class OpenViewRequest public class PaginationMetaPaginate { [JsonPropertyName("next_page")] - public string? NextPage { get; set; } + public string NextPage { get; set; } = default!; } public class PaginationMeta { [JsonPropertyName("paginate")] - public PaginationMetaPaginate? Paginate { get; set; } + public PaginationMetaPaginate Paginate { get; set; } = default!; } public class Reaction @@ -2290,8 +2385,6 @@ public class StatusUpdateRequest public StatusUpdateRequestStatus Status { get; set; } = default!; } -public class TagNamesFilter { } - public class Task { [JsonPropertyName("id")] @@ -2644,12 +2737,24 @@ public class WebhookMessageThread public int MessageChatId { get; set; } = default!; } +public class UpdateProfileAvatarRequest +{ + [JsonIgnore] + public byte[] Image { get; set; } = Array.Empty(); +} + +public class UpdateUserAvatarRequest +{ + [JsonIgnore] + public byte[] Image { get; set; } = Array.Empty(); +} + public class GetAuditEventsResponse { [JsonPropertyName("data")] public List Data { get; set; } = new(); [JsonPropertyName("meta")] - public PaginationMeta? Meta { get; set; } + public PaginationMeta Meta { get; set; } = default!; } public class ListChatsResponse @@ -2657,7 +2762,7 @@ public class ListChatsResponse [JsonPropertyName("data")] public List Data { get; set; } = new(); [JsonPropertyName("meta")] - public PaginationMeta? Meta { get; set; } + public PaginationMeta Meta { get; set; } = default!; } public class ListMembersResponse @@ -2665,7 +2770,7 @@ public class ListMembersResponse [JsonPropertyName("data")] public List Data { get; set; } = new(); [JsonPropertyName("meta")] - public PaginationMeta? Meta { get; set; } + public PaginationMeta Meta { get; set; } = default!; } public class ListPropertiesResponse @@ -2679,7 +2784,7 @@ public class ListTagsResponse [JsonPropertyName("data")] public List Data { get; set; } = new(); [JsonPropertyName("meta")] - public PaginationMeta? Meta { get; set; } + public PaginationMeta Meta { get; set; } = default!; } public class GetTagUsersResponse @@ -2687,7 +2792,7 @@ public class GetTagUsersResponse [JsonPropertyName("data")] public List Data { get; set; } = new(); [JsonPropertyName("meta")] - public PaginationMeta? Meta { get; set; } + public PaginationMeta Meta { get; set; } = default!; } public class ListChatMessagesResponse @@ -2695,7 +2800,7 @@ public class ListChatMessagesResponse [JsonPropertyName("data")] public List Data { get; set; } = new(); [JsonPropertyName("meta")] - public PaginationMeta? Meta { get; set; } + public PaginationMeta Meta { get; set; } = default!; } public class ListReactionsResponse @@ -2703,7 +2808,7 @@ public class ListReactionsResponse [JsonPropertyName("data")] public List Data { get; set; } = new(); [JsonPropertyName("meta")] - public PaginationMeta? Meta { get; set; } + public PaginationMeta Meta { get; set; } = default!; } public class SearchChatsResponse @@ -2735,7 +2840,7 @@ public class ListTasksResponse [JsonPropertyName("data")] public List Data { get; set; } = new(); [JsonPropertyName("meta")] - public PaginationMeta? Meta { get; set; } + public PaginationMeta Meta { get; set; } = default!; } public class ListUsersResponse @@ -2743,7 +2848,7 @@ public class ListUsersResponse [JsonPropertyName("data")] public List Data { get; set; } = new(); [JsonPropertyName("meta")] - public PaginationMeta? Meta { get; set; } + public PaginationMeta Meta { get; set; } = default!; } public class GetWebhookEventsResponse @@ -2751,7 +2856,7 @@ public class GetWebhookEventsResponse [JsonPropertyName("data")] public List Data { get; set; } = new(); [JsonPropertyName("meta")] - public PaginationMeta? Meta { get; set; } + public PaginationMeta Meta { get; set; } = default!; } public class BotResponseDataWrapper @@ -2796,6 +2901,12 @@ public class UserDataWrapper public User Data { get; set; } = default!; } +public class AvatarDataDataWrapper +{ + [JsonPropertyName("data")] + public AvatarData Data { get; set; } = default!; +} + public class UserStatusDataWrapper { [JsonPropertyName("data")] diff --git a/sdk/csharp/generated/Pachca.csproj b/sdk/csharp/generated/Pachca.csproj index 84028b70..5304942c 100644 --- a/sdk/csharp/generated/Pachca.csproj +++ b/sdk/csharp/generated/Pachca.csproj @@ -20,6 +20,7 @@ true + CS1591 true true true diff --git a/sdk/csharp/generated/README.md b/sdk/csharp/generated/README.md index 90e201e9..bf907f17 100644 --- a/sdk/csharp/generated/README.md +++ b/sdk/csharp/generated/README.md @@ -62,12 +62,13 @@ var message = await client.Messages.CreateMessageAsync(...); // Message, не Me // Вручную 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; +} // Автоматически var allChats = await client.Chats.ListChatsAllAsync(); diff --git a/sdk/csharp/generated/Utils.cs b/sdk/csharp/generated/Utils.cs index 88d4be58..2cf57544 100644 --- a/sdk/csharp/generated/Utils.cs +++ b/sdk/csharp/generated/Utils.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; diff --git a/sdk/csharp/generated/bin/Debug/net8.0/Pachca.deps.json b/sdk/csharp/generated/bin/Debug/net8.0/Pachca.deps.json deleted file mode 100644 index 2961953f..00000000 --- a/sdk/csharp/generated/bin/Debug/net8.0/Pachca.deps.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "runtimeTarget": { - "name": ".NETCoreApp,Version=v8.0", - "signature": "" - }, - "compilationOptions": {}, - "targets": { - ".NETCoreApp,Version=v8.0": { - "Pachca/0.0.0": { - "runtime": { - "Pachca.dll": {} - } - } - } - }, - "libraries": { - "Pachca/0.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - } - } -} \ No newline at end of file diff --git a/sdk/csharp/generated/bin/Debug/net8.0/Pachca.dll b/sdk/csharp/generated/bin/Debug/net8.0/Pachca.dll deleted file mode 100644 index 0752f772..00000000 Binary files a/sdk/csharp/generated/bin/Debug/net8.0/Pachca.dll and /dev/null differ diff --git a/sdk/csharp/generated/bin/Debug/net8.0/Pachca.pdb b/sdk/csharp/generated/bin/Debug/net8.0/Pachca.pdb deleted file mode 100644 index eedaeec0..00000000 Binary files a/sdk/csharp/generated/bin/Debug/net8.0/Pachca.pdb and /dev/null differ diff --git a/sdk/csharp/generated/bin/Debug/net8.0/Pachca.xml b/sdk/csharp/generated/bin/Debug/net8.0/Pachca.xml deleted file mode 100644 index 5b7e389b..00000000 --- a/sdk/csharp/generated/bin/Debug/net8.0/Pachca.xml +++ /dev/null @@ -1,599 +0,0 @@ - - - - Pachca - - - - Тип аудит-события - - - Пользователь успешно вошел в систему - - - Пользователь вышел из системы - - - Неудачная попытка двухфакторной аутентификации - - - Успешная двухфакторная аутентификация - - - Создана новая учетная запись пользователя - - - Учетная запись пользователя удалена - - - Роль пользователя была изменена - - - Данные пользователя обновлены - - - Создан новый тег - - - Тег удален - - - Пользователь добавлен в тег - - - Пользователь удален из тега - - - Создан новый чат - - - Чат переименован - - - Изменены права доступа к чату - - - Пользователь присоединился к чату - - - Пользователь покинул чат - - - Тег добавлен в чат - - - Тег удален из чата - - - Сообщение отредактировано - - - Сообщение удалено - - - Сообщение создано - - - Реакция добавлена - - - Реакция удалена - - - Тред создан - - - Создан новый токен доступа - - - Токен доступа обновлен - - - Токен доступа удален - - - Данные зашифрованы - - - Данные расшифрованы - - - Доступ к журналам аудита получен - - - Срабатывание правила DLP-системы - - - Поиск сотрудников через API - - - Поиск чатов через API - - - Поиск сообщений через API - - - Доступность чатов для пользователя - - - Чаты, где пользователь является участником - - - Все открытые чаты компании, вне зависимости от участия в них пользователя - - - Роль участника чата - - - Админ - - - Редактор (доступно только для каналов) - - - Участник или подписчик - - - Роль участника чата (с фильтром все) - - - Любая роль - - - Создатель - - - Админ - - - Редактор - - - Участник/подписчик - - - Тип чата - - - Канал или беседа - - - Тред - - - Тип данных дополнительного поля - - - Строковое значение - - - Числовое значение - - - Дата - - - Ссылка - - - Тип файла - - - Обычный файл - - - Изображение - - - Статус приглашения пользователя - - - Принято - - - Отправлено - - - Тип события webhook для участников - - - Добавление - - - Удаление - - - Тип сущности для сообщений - - - Беседа или канал - - - Тред - - - Пользователь - - - Скоуп доступа OAuth токена - - - Просмотр чатов и списка чатов - - - Создание новых чатов - - - Изменение настроек чата - - - Архивация и разархивация чатов - - - Выход из чатов - - - Просмотр участников чата - - - Добавление, изменение и удаление участников чата - - - Скачивание экспортов чата - - - Создание экспортов чата - - - Просмотр сообщений в чатах - - - Отправка сообщений - - - Редактирование сообщений - - - Удаление сообщений - - - Просмотр реакций на сообщения - - - Добавление и удаление реакций - - - Закрепление и открепление сообщений - - - Просмотр тредов (комментариев) - - - Создание тредов (комментариев) - - - Unfurl (разворачивание ссылок) - - - Просмотр информации о сотрудниках и списка сотрудников - - - Создание новых сотрудников - - - Редактирование данных сотрудника - - - Удаление сотрудников - - - Просмотр тегов - - - Создание, редактирование и удаление тегов - - - Изменение настроек бота - - - Просмотр информации о своем профиле - - - Просмотр статуса профиля - - - Изменение и удаление статуса профиля - - - Просмотр статуса сотрудника - - - Изменение и удаление статуса сотрудника - - - Просмотр дополнительных полей - - - Просмотр журнала аудита - - - Просмотр задач - - - Создание задач - - - Изменение задачи - - - Удаление задачи - - - Скачивание файлов - - - Загрузка файлов - - - Получение данных для загрузки файлов - - - Открытие форм (представлений) - - - Просмотр вебхуков - - - Создание и управление вебхуками - - - Просмотр лога вебхуков - - - Удаление записи в логе вебхука - - - Поиск сотрудников - - - Поиск чатов - - - Поиск сообщений - - - Тип события webhook для реакций - - - Создание - - - Удаление - - - Тип сущности для поиска - - - Пользователь - - - Задача - - - Сортировка результатов поиска - - - По релевантности - - - По алфавиту - - - Порядок сортировки - - - По возрастанию - - - По убыванию - - - Тип задачи - - - Позвонить контакту - - - Встреча - - - Простое напоминание - - - Событие - - - Написать письмо - - - Статус напоминания - - - Выполнено - - - Активно - - - Тип события webhook для пользователей - - - Приглашение - - - Подтверждение - - - Обновление - - - Приостановка - - - Активация - - - Удаление - - - Роль пользователя в системе - - - Администратор - - - Сотрудник - - - Мульти-гость - - - Гость - - - Роль пользователя, допустимая при создании и редактировании. Роль `guest` недоступна для установки через API. - - - Администратор - - - Сотрудник - - - Мульти-гость - - - Коды ошибок валидации - - - Обязательное поле (не может быть пустым) - - - Слишком длинное значение (пояснения вы получите в поле message) - - - Поле не соответствует правилам (пояснения вы получите в поле message) - - - Поле имеет непредусмотренное значение - - - Поле имеет недопустимое значение - - - Название для этого поля уже существует - - - Emoji статуса не может содержать значения отличные от Emoji символа - - - Объект не найден - - - Объект уже существует (пояснения вы получите в поле message) - - - Ошибка личного чата (пояснения вы получите в поле message) - - - Отображаемая ошибка (пояснения вы получите в поле message) - - - Действие запрещено - - - Выбран слишком большой диапазон дат - - - Некорректный URL вебхука - - - Достигнут лимит запросов - - - Превышен лимит активных сотрудников (пояснения вы получите в поле message) - - - Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций) - - - Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций) - - - Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций) - - - Ошибка выполнения запроса (пояснения вы получите в поле message) - - - Не удалось найти идентификатор события - - - Время жизни идентификатора события истекло - - - Обязательный параметр не передан - - - Недопустимое значение (не входит в список допустимых) - - - Значение неприменимо в данном контексте (пояснения вы получите в поле message) - - - Нельзя изменить свои собственные данные - - - Нельзя изменить данные владельца - - - Значение уже назначено - - - Недостаточно прав для выполнения действия (пояснения вы получите в поле message) - - - Доступ запрещён (недостаточно прав) - - - Доступ запрещён - - - Некорректные параметры запроса (пояснения вы получите в поле message) - - - Требуется оплата - - - Значение слишком короткое (пояснения вы получите в поле message) - - - Значение слишком длинное (пояснения вы получите в поле message) - - - Использовано зарезервированное системное слово (here, all) - - - Тип события webhook - - - Создание - - - Обновление - - - Удаление - - - diff --git a/sdk/csharp/generated/examples.json b/sdk/csharp/generated/examples.json index b87e88de..217373b3 100644 --- a/sdk/csharp/generated/examples.json +++ b/sdk/csharp/generated/examples.json @@ -6,15 +6,15 @@ ] }, "SecurityOperations_getAuditEvents": { - "usage": "var response = await client.Security.GetAuditEventsAsync(\"2025-05-01T09:11:00Z\", \"2025-05-02T09:11:00Z\", AuditEventKey.UserLogin, \"98765\", \"User\", \"98765\", \"User\", 1, \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\");", - "output": "GetAuditEventsResponse(Data: List, Meta: PaginationMeta?)", + "usage": "var response = await client.Security.GetAuditEventsAsync(DateTimeOffset.Parse(\"2025-05-01T09:11:00Z\"), DateTimeOffset.Parse(\"2025-05-02T09:11:00Z\"), AuditEventKey.UserLogin, \"98765\", \"User\", \"98765\", \"User\", 1, \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\");", + "output": "GetAuditEventsResponse(Data: List, Meta: PaginationMeta)", "imports": [ "AuditEventKey" ] }, "BotOperations_getWebhookEvents": { "usage": "var response = await client.Bots.GetWebhookEventsAsync(1, \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\");", - "output": "GetWebhookEventsResponse(Data: List, Meta: PaginationMeta?)" + "output": "GetWebhookEventsResponse(Data: List, Meta: PaginationMeta)" }, "BotOperations_updateBot": { "usage": "var request = new BotUpdateRequest { Bot = new BotUpdateRequestBot { Webhook = new BotUpdateRequestBotWebhook { OutgoingUrl = \"https://www.website.com/tasks/new\" } } };\nvar response = await client.Bots.UpdateBotAsync(1738816, request);", @@ -29,10 +29,11 @@ "usage": "await client.Bots.DeleteWebhookEventAsync(\"01KAJZ2XDSS2S3DSW9EXJZ0TBV\");" }, "ChatOperations_listChats": { - "usage": "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\");", - "output": "ListChatsResponse(Data: List, Meta: PaginationMeta?)", + "usage": "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\");", + "output": "ListChatsResponse(Data: List, Meta: PaginationMeta)", "imports": [ "ChatAvailability", + "ChatSortField", "SortOrder" ] }, @@ -91,7 +92,7 @@ }, "ChatMemberOperations_listMembers": { "usage": "var response = await client.Members.ListMembersAsync(334, ChatMemberRoleFilter.All, 1, \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\");", - "output": "ListMembersResponse(Data: List, Meta: PaginationMeta?)", + "output": "ListMembersResponse(Data: List, Meta: PaginationMeta)", "imports": [ "ChatMemberRoleFilter" ] @@ -121,11 +122,8 @@ "usage": "await client.Members.RemoveMemberAsync(334, 186);" }, "GroupTagOperations_listTags": { - "usage": "var names = new TagNamesFilter();\nvar response = await client.GroupTags.ListTagsAsync(names, 1, \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\");", - "output": "ListTagsResponse(Data: List, Meta: PaginationMeta?)", - "imports": [ - "TagNamesFilter" - ] + "usage": "var names = new List { \"example\" };\nvar response = await client.GroupTags.ListTagsAsync(names, 1, \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\");", + "output": "ListTagsResponse(Data: List, Meta: PaginationMeta)" }, "GroupTagOperations_getTag": { "usage": "var response = await client.GroupTags.GetTagAsync(9111);", @@ -133,7 +131,7 @@ }, "GroupTagOperations_getTagUsers": { "usage": "var response = await client.GroupTags.GetTagUsersAsync(9111, 1, \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\");", - "output": "GetTagUsersResponse(Data: List, Meta: PaginationMeta?)" + "output": "GetTagUsersResponse(Data: List, Meta: PaginationMeta)" }, "GroupTagOperations_createTag": { "usage": "var request = new GroupTagRequest { GroupTag = new GroupTagRequestGroupTag { Name = \"Новое название тега\" } };\nvar response = await client.GroupTags.CreateTagAsync(request);", @@ -155,9 +153,10 @@ "usage": "await client.GroupTags.DeleteTagAsync(9111);" }, "ChatMessageOperations_listChatMessages": { - "usage": "var response = await client.Messages.ListChatMessagesAsync(198, SortOrder.Desc, 1, \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\");", - "output": "ListChatMessagesResponse(Data: List, Meta: PaginationMeta?)", + "usage": "var response = await client.Messages.ListChatMessagesAsync(198, MessageSortField.Id, SortOrder.Desc, 1, \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\");", + "output": "ListChatMessagesResponse(Data: List, Meta: PaginationMeta)", "imports": [ + "MessageSortField", "SortOrder" ] }, @@ -206,7 +205,7 @@ }, "ReactionOperations_listReactions": { "usage": "var response = await client.Reactions.ListReactionsAsync(194275, 1, \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\");", - "output": "ListReactionsResponse(Data: List, Meta: PaginationMeta?)" + "output": "ListReactionsResponse(Data: List, Meta: PaginationMeta)" }, "ReactionOperations_addReaction": { "usage": "var request = new ReactionRequest { Code = \"👍\", Name = \":+1:\" };\nvar response = await client.Reactions.AddReactionAsync(7231942, request);", @@ -242,19 +241,26 @@ "usage": "var response = await client.Profile.GetStatusAsync();", "output": "object" }, + "ProfileAvatarOperations_updateProfileAvatar": { + "usage": "var response = await client.Profile.UpdateProfileAvatarAsync(Array.Empty());", + "output": "AvatarData(ImageUrl: string)" + }, "ProfileOperations_updateStatus": { - "usage": "var request = new StatusUpdateRequest\n{\n Status = new StatusUpdateRequestStatus\n {\n Emoji = \"🎮\",\n Title = \"Очень занят\",\n ExpiresAt = \"2024-04-08T10:00:00.000Z\",\n IsAway = true,\n AwayMessage = \"Вернусь после 15:00\"\n }\n};\nvar response = await client.Profile.UpdateStatusAsync(request);", + "usage": "var request = new StatusUpdateRequest\n{\n Status = new StatusUpdateRequestStatus\n {\n Emoji = \"🎮\",\n Title = \"Очень занят\",\n ExpiresAt = DateTimeOffset.Parse(\"2024-04-08T10:00:00.000Z\"),\n IsAway = true,\n AwayMessage = \"Вернусь после 15:00\"\n }\n};\nvar response = await client.Profile.UpdateStatusAsync(request);", "output": "UserStatus(Emoji: string, Title: string, ExpiresAt: DateTimeOffset?, IsAway: bool, AwayMessage: UserStatusAwayMessage(Text: string)?)", "imports": [ "StatusUpdateRequest", "StatusUpdateRequestStatus" ] }, + "ProfileAvatarOperations_deleteProfileAvatar": { + "usage": "await client.Profile.DeleteProfileAvatarAsync();" + }, "ProfileOperations_deleteStatus": { "usage": "await client.Profile.DeleteStatusAsync();" }, "SearchOperations_searchChats": { - "usage": "var response = await client.Search.SearchChatsAsync(\"Разработка\", 10, \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\", SortOrder.Desc, \"2025-01-01T00:00:00.000Z\", \"2025-02-01T00:00:00.000Z\", true, ChatSubtype.Discussion, false);", + "usage": "var response = await client.Search.SearchChatsAsync(\"Разработка\", 10, \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\", SortOrder.Desc, DateTimeOffset.Parse(\"2025-01-01T00:00:00.000Z\"), DateTimeOffset.Parse(\"2025-02-01T00:00:00.000Z\"), true, ChatSubtype.Discussion, false);", "output": "SearchChatsResponse(Data: List, Meta: SearchPaginationMeta)", "imports": [ "ChatSubtype", @@ -262,14 +268,14 @@ ] }, "SearchOperations_searchMessages": { - "usage": "var chatIds = new List { 123 };\nvar userIds = new List { 123 };\nvar response = await client.Search.SearchMessagesAsync(\"футболки\", 10, \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\", SortOrder.Desc, \"2025-01-01T00:00:00.000Z\", \"2025-02-01T00:00:00.000Z\", chatIds, userIds, true);", + "usage": "var chatIds = new List { 123 };\nvar userIds = new List { 123 };\nvar response = await client.Search.SearchMessagesAsync(\"футболки\", 10, \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\", SortOrder.Desc, DateTimeOffset.Parse(\"2025-01-01T00:00:00.000Z\"), DateTimeOffset.Parse(\"2025-02-01T00:00:00.000Z\"), chatIds, userIds, true);", "output": "SearchMessagesResponse(Data: List, Meta: SearchPaginationMeta)", "imports": [ "SortOrder" ] }, "SearchOperations_searchUsers": { - "usage": "var companyRoles = new List { UserRole.Admin };\nvar response = await client.Search.SearchUsersAsync(\"Олег\", 10, \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\", SearchSortOrder.ByScore, SortOrder.Desc, \"2025-01-01T00:00:00.000Z\", \"2025-02-01T00:00:00.000Z\", companyRoles);", + "usage": "var companyRoles = new List { UserRole.Admin };\nvar response = await client.Search.SearchUsersAsync(\"Олег\", 10, \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\", SearchSortOrder.ByScore, SortOrder.Desc, DateTimeOffset.Parse(\"2025-01-01T00:00:00.000Z\"), DateTimeOffset.Parse(\"2025-02-01T00:00:00.000Z\"), companyRoles);", "output": "SearchUsersResponse(Data: List, Meta: SearchPaginationMeta)", "imports": [ "SearchSortOrder", @@ -279,14 +285,14 @@ }, "TaskOperations_listTasks": { "usage": "var response = await client.Tasks.ListTasksAsync(1, \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\");", - "output": "ListTasksResponse(Data: List, Meta: PaginationMeta?)" + "output": "ListTasksResponse(Data: List, Meta: PaginationMeta)" }, "TaskOperations_getTask": { "usage": "var response = await client.Tasks.GetTaskAsync(22283);", "output": "Task(Id: int, Kind: TaskKind, Content: string, DueAt: DateTimeOffset?, Priority: int, UserId: int, ChatId: int?, Status: TaskStatus, CreatedAt: DateTimeOffset, PerformerIds: List, AllDay: bool, CustomProperties: List)" }, "TaskOperations_createTask": { - "usage": "var request = new TaskCreateRequest\n{\n Task = new TaskCreateRequestTask\n {\n Kind = TaskKind.Reminder,\n Content = \"Забрать со склада 21 заказ\",\n DueAt = \"2020-06-05T12:00:00.000+03:00\",\n Priority = 2,\n PerformerIds = new List { 123 },\n ChatId = 456,\n AllDay = false,\n CustomProperties = new List { new TaskCreateRequestCustomProperty { Id = 78, Value = \"Синий склад\" } }\n }\n};\nvar response = await client.Tasks.CreateTaskAsync(request);", + "usage": "var request = new TaskCreateRequest\n{\n Task = new TaskCreateRequestTask\n {\n Kind = TaskKind.Reminder,\n Content = \"Забрать со склада 21 заказ\",\n DueAt = DateTimeOffset.Parse(\"2020-06-05T12:00:00.000+03:00\"),\n Priority = 2,\n PerformerIds = new List { 123 },\n ChatId = 456,\n AllDay = false,\n CustomProperties = new List { new TaskCreateRequestCustomProperty { Id = 78, Value = \"Синий склад\" } }\n }\n};\nvar response = await client.Tasks.CreateTaskAsync(request);", "output": "Task(Id: int, Kind: TaskKind, Content: string, DueAt: DateTimeOffset?, Priority: int, UserId: int, ChatId: int?, Status: TaskStatus, CreatedAt: DateTimeOffset, PerformerIds: List, AllDay: bool, CustomProperties: List)", "imports": [ "TaskCreateRequest", @@ -296,7 +302,7 @@ ] }, "TaskOperations_updateTask": { - "usage": "var request = new TaskUpdateRequest\n{\n Task = new TaskUpdateRequestTask\n {\n Kind = TaskKind.Reminder,\n Content = \"Забрать со склада 21 заказ\",\n DueAt = \"2020-06-05T12:00:00.000+03:00\",\n Priority = 2,\n PerformerIds = new List { 123 },\n Status = TaskStatus.Done,\n AllDay = false,\n DoneAt = \"2020-06-05T12:00:00.000Z\",\n CustomProperties = new List { new TaskUpdateRequestCustomProperty { Id = 78, Value = \"Синий склад\" } }\n }\n};\nvar response = await client.Tasks.UpdateTaskAsync(22283, request);", + "usage": "var request = new TaskUpdateRequest\n{\n Task = new TaskUpdateRequestTask\n {\n Kind = TaskKind.Reminder,\n Content = \"Забрать со склада 21 заказ\",\n DueAt = DateTimeOffset.Parse(\"2020-06-05T12:00:00.000+03:00\"),\n Priority = 2,\n PerformerIds = new List { 123 },\n Status = TaskStatus.Done,\n AllDay = false,\n DoneAt = DateTimeOffset.Parse(\"2020-06-05T12:00:00.000Z\"),\n CustomProperties = new List { new TaskUpdateRequestCustomProperty { Id = 78, Value = \"Синий склад\" } }\n }\n};\nvar response = await client.Tasks.UpdateTaskAsync(22283, request);", "output": "Task(Id: int, Kind: TaskKind, Content: string, DueAt: DateTimeOffset?, Priority: int, UserId: int, ChatId: int?, Status: TaskStatus, CreatedAt: DateTimeOffset, PerformerIds: List, AllDay: bool, CustomProperties: List)", "imports": [ "TaskKind", @@ -311,7 +317,7 @@ }, "UserOperations_listUsers": { "usage": "var response = await client.Users.ListUsersAsync(\"Олег\", 1, \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\");", - "output": "ListUsersResponse(Data: List, Meta: PaginationMeta?)" + "output": "ListUsersResponse(Data: List, Meta: PaginationMeta)" }, "UserOperations_getUser": { "usage": "var response = await client.Users.GetUserAsync(12);", @@ -341,8 +347,12 @@ "UserUpdateRequestUser" ] }, + "UserAvatarOperations_updateUserAvatar": { + "usage": "var response = await client.Users.UpdateUserAvatarAsync(12, Array.Empty());", + "output": "AvatarData(ImageUrl: string)" + }, "UserStatusOperations_updateUserStatus": { - "usage": "var request = new StatusUpdateRequest\n{\n Status = new StatusUpdateRequestStatus\n {\n Emoji = \"🎮\",\n Title = \"Очень занят\",\n ExpiresAt = \"2024-04-08T10:00:00.000Z\",\n IsAway = true,\n AwayMessage = \"Вернусь после 15:00\"\n }\n};\nvar response = await client.Users.UpdateUserStatusAsync(12, request);", + "usage": "var request = new StatusUpdateRequest\n{\n Status = new StatusUpdateRequestStatus\n {\n Emoji = \"🎮\",\n Title = \"Очень занят\",\n ExpiresAt = DateTimeOffset.Parse(\"2024-04-08T10:00:00.000Z\"),\n IsAway = true,\n AwayMessage = \"Вернусь после 15:00\"\n }\n};\nvar response = await client.Users.UpdateUserStatusAsync(12, request);", "output": "UserStatus(Emoji: string, Title: string, ExpiresAt: DateTimeOffset?, IsAway: bool, AwayMessage: UserStatusAwayMessage(Text: string)?)", "imports": [ "StatusUpdateRequest", @@ -352,11 +362,14 @@ "UserOperations_deleteUser": { "usage": "await client.Users.DeleteUserAsync(12);" }, + "UserAvatarOperations_deleteUserAvatar": { + "usage": "await client.Users.DeleteUserAvatarAsync(12);" + }, "UserStatusOperations_deleteUserStatus": { "usage": "await client.Users.DeleteUserStatusAsync(12);" }, "FormOperations_openView": { - "usage": "var request = new OpenViewRequest\n{\n Type = \"modal\",\n TriggerId = \"791a056b-006c-49dd-834b-c633fde52fe8\",\n PrivateMetadata = \"{\"timeoff_id\":4378}\",\n CallbackId = \"timeoff_reguest_form\",\n View = new OpenViewRequestView\n {\n Title = \"Уведомление об отпуске\",\n CloseText = \"Закрыть\",\n SubmitText = \"Отправить заявку\",\n Blocks = new List { new ViewBlockHeader { Type = \"header\", Text = \"Основная информация\" } }\n }\n};\nawait client.Views.OpenViewAsync(request);", + "usage": "var request = new OpenViewRequest\n{\n TriggerId = \"791a056b-006c-49dd-834b-c633fde52fe8\",\n PrivateMetadata = \"{\\\"timeoff_id\\\":4378}\",\n CallbackId = \"timeoff_reguest_form\",\n View = new OpenViewRequestView\n {\n Title = \"Уведомление об отпуске\",\n CloseText = \"Закрыть\",\n SubmitText = \"Отправить заявку\",\n Blocks = new List { new ViewBlockHeader { Text = \"Основная информация\" } }\n }\n};\nawait client.Views.OpenViewAsync(request);", "imports": [ "OpenViewRequest", "OpenViewRequestView", diff --git a/sdk/csharp/generated/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs b/sdk/csharp/generated/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs deleted file mode 100644 index 2217181c..00000000 --- a/sdk/csharp/generated/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs +++ /dev/null @@ -1,4 +0,0 @@ -// -using System; -using System.Reflection; -[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] diff --git a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.AssemblyInfo.cs b/sdk/csharp/generated/obj/Debug/net8.0/Pachca.AssemblyInfo.cs deleted file mode 100644 index 77c663af..00000000 --- a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.AssemblyInfo.cs +++ /dev/null @@ -1,24 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -using System; -using System.Reflection; - -[assembly: System.Reflection.AssemblyCompanyAttribute("Pachca")] -[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] -[assembly: System.Reflection.AssemblyDescriptionAttribute("Official Pachca API SDK for .NET")] -[assembly: System.Reflection.AssemblyFileVersionAttribute("0.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("0.0.0+9afa2fc4504fb25202c414ae42418737b6da4e0f")] -[assembly: System.Reflection.AssemblyProductAttribute("Pachca")] -[assembly: System.Reflection.AssemblyTitleAttribute("Pachca")] -[assembly: System.Reflection.AssemblyVersionAttribute("0.0.0.0")] -[assembly: System.Reflection.AssemblyMetadataAttribute("RepositoryUrl", "https://github.com/pachca/openapi")] - -// Generated by the MSBuild WriteCodeFragment class. - diff --git a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.AssemblyInfoInputs.cache b/sdk/csharp/generated/obj/Debug/net8.0/Pachca.AssemblyInfoInputs.cache deleted file mode 100644 index 9e84d514..00000000 --- a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.AssemblyInfoInputs.cache +++ /dev/null @@ -1 +0,0 @@ -2c85671f0b10bc23c3ff979e312d97dfd3502ab9620fba499cd27334c0494c28 diff --git a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.GeneratedMSBuildEditorConfig.editorconfig b/sdk/csharp/generated/obj/Debug/net8.0/Pachca.GeneratedMSBuildEditorConfig.editorconfig deleted file mode 100644 index 97d70010..00000000 --- a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.GeneratedMSBuildEditorConfig.editorconfig +++ /dev/null @@ -1,17 +0,0 @@ -is_global = true -build_property.TargetFramework = net8.0 -build_property.TargetFrameworkIdentifier = .NETCoreApp -build_property.TargetFrameworkVersion = v8.0 -build_property.TargetPlatformMinVersion = -build_property.UsingMicrosoftNETSdkWeb = -build_property.ProjectTypeGuids = -build_property.InvariantGlobalization = -build_property.PlatformNeutralAssembly = -build_property.EnforceExtendedAnalyzerRules = -build_property._SupportedPlatformList = Linux,macOS,Windows -build_property.RootNamespace = Pachca.Sdk -build_property.ProjectDir = /home/runner/work/openapi/openapi/sdk/csharp/generated/ -build_property.EnableComHosting = -build_property.EnableGeneratedComInterfaceComImportInterop = -build_property.EffectiveAnalysisLevelStyle = 8.0 -build_property.EnableCodeStyleSeverity = diff --git a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.assets.cache b/sdk/csharp/generated/obj/Debug/net8.0/Pachca.assets.cache deleted file mode 100644 index d6c07009..00000000 Binary files a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.assets.cache and /dev/null differ diff --git a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.csproj.CoreCompileInputs.cache b/sdk/csharp/generated/obj/Debug/net8.0/Pachca.csproj.CoreCompileInputs.cache deleted file mode 100644 index a499bb22..00000000 --- a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.csproj.CoreCompileInputs.cache +++ /dev/null @@ -1 +0,0 @@ -27549f79309ec3d33dd8e8b4fe36e62ef2859016545e339f577318bef5f9ac18 diff --git a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.csproj.FileListAbsolute.txt b/sdk/csharp/generated/obj/Debug/net8.0/Pachca.csproj.FileListAbsolute.txt deleted file mode 100644 index 18b478ec..00000000 --- a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.csproj.FileListAbsolute.txt +++ /dev/null @@ -1,14 +0,0 @@ -/home/runner/work/openapi/openapi/sdk/csharp/generated/bin/Debug/net8.0/Pachca.deps.json -/home/runner/work/openapi/openapi/sdk/csharp/generated/bin/Debug/net8.0/Pachca.dll -/home/runner/work/openapi/openapi/sdk/csharp/generated/bin/Debug/net8.0/Pachca.pdb -/home/runner/work/openapi/openapi/sdk/csharp/generated/bin/Debug/net8.0/Pachca.xml -/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/Debug/net8.0/Pachca.GeneratedMSBuildEditorConfig.editorconfig -/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/Debug/net8.0/Pachca.AssemblyInfoInputs.cache -/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/Debug/net8.0/Pachca.AssemblyInfo.cs -/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/Debug/net8.0/Pachca.csproj.CoreCompileInputs.cache -/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/Debug/net8.0/Pachca.sourcelink.json -/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/Debug/net8.0/Pachca.dll -/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/Debug/net8.0/refint/Pachca.dll -/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/Debug/net8.0/Pachca.xml -/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/Debug/net8.0/Pachca.pdb -/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/Debug/net8.0/ref/Pachca.dll diff --git a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.dll b/sdk/csharp/generated/obj/Debug/net8.0/Pachca.dll deleted file mode 100644 index 0752f772..00000000 Binary files a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.dll and /dev/null differ diff --git a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.pdb b/sdk/csharp/generated/obj/Debug/net8.0/Pachca.pdb deleted file mode 100644 index eedaeec0..00000000 Binary files a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.pdb and /dev/null differ diff --git a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.sourcelink.json b/sdk/csharp/generated/obj/Debug/net8.0/Pachca.sourcelink.json deleted file mode 100644 index 929a9980..00000000 --- a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.sourcelink.json +++ /dev/null @@ -1 +0,0 @@ -{"documents":{"/home/runner/work/openapi/openapi/*":"https://raw.githubusercontent.com/pachca/openapi/9afa2fc4504fb25202c414ae42418737b6da4e0f/*"}} \ No newline at end of file diff --git a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.xml b/sdk/csharp/generated/obj/Debug/net8.0/Pachca.xml deleted file mode 100644 index 5b7e389b..00000000 --- a/sdk/csharp/generated/obj/Debug/net8.0/Pachca.xml +++ /dev/null @@ -1,599 +0,0 @@ - - - - Pachca - - - - Тип аудит-события - - - Пользователь успешно вошел в систему - - - Пользователь вышел из системы - - - Неудачная попытка двухфакторной аутентификации - - - Успешная двухфакторная аутентификация - - - Создана новая учетная запись пользователя - - - Учетная запись пользователя удалена - - - Роль пользователя была изменена - - - Данные пользователя обновлены - - - Создан новый тег - - - Тег удален - - - Пользователь добавлен в тег - - - Пользователь удален из тега - - - Создан новый чат - - - Чат переименован - - - Изменены права доступа к чату - - - Пользователь присоединился к чату - - - Пользователь покинул чат - - - Тег добавлен в чат - - - Тег удален из чата - - - Сообщение отредактировано - - - Сообщение удалено - - - Сообщение создано - - - Реакция добавлена - - - Реакция удалена - - - Тред создан - - - Создан новый токен доступа - - - Токен доступа обновлен - - - Токен доступа удален - - - Данные зашифрованы - - - Данные расшифрованы - - - Доступ к журналам аудита получен - - - Срабатывание правила DLP-системы - - - Поиск сотрудников через API - - - Поиск чатов через API - - - Поиск сообщений через API - - - Доступность чатов для пользователя - - - Чаты, где пользователь является участником - - - Все открытые чаты компании, вне зависимости от участия в них пользователя - - - Роль участника чата - - - Админ - - - Редактор (доступно только для каналов) - - - Участник или подписчик - - - Роль участника чата (с фильтром все) - - - Любая роль - - - Создатель - - - Админ - - - Редактор - - - Участник/подписчик - - - Тип чата - - - Канал или беседа - - - Тред - - - Тип данных дополнительного поля - - - Строковое значение - - - Числовое значение - - - Дата - - - Ссылка - - - Тип файла - - - Обычный файл - - - Изображение - - - Статус приглашения пользователя - - - Принято - - - Отправлено - - - Тип события webhook для участников - - - Добавление - - - Удаление - - - Тип сущности для сообщений - - - Беседа или канал - - - Тред - - - Пользователь - - - Скоуп доступа OAuth токена - - - Просмотр чатов и списка чатов - - - Создание новых чатов - - - Изменение настроек чата - - - Архивация и разархивация чатов - - - Выход из чатов - - - Просмотр участников чата - - - Добавление, изменение и удаление участников чата - - - Скачивание экспортов чата - - - Создание экспортов чата - - - Просмотр сообщений в чатах - - - Отправка сообщений - - - Редактирование сообщений - - - Удаление сообщений - - - Просмотр реакций на сообщения - - - Добавление и удаление реакций - - - Закрепление и открепление сообщений - - - Просмотр тредов (комментариев) - - - Создание тредов (комментариев) - - - Unfurl (разворачивание ссылок) - - - Просмотр информации о сотрудниках и списка сотрудников - - - Создание новых сотрудников - - - Редактирование данных сотрудника - - - Удаление сотрудников - - - Просмотр тегов - - - Создание, редактирование и удаление тегов - - - Изменение настроек бота - - - Просмотр информации о своем профиле - - - Просмотр статуса профиля - - - Изменение и удаление статуса профиля - - - Просмотр статуса сотрудника - - - Изменение и удаление статуса сотрудника - - - Просмотр дополнительных полей - - - Просмотр журнала аудита - - - Просмотр задач - - - Создание задач - - - Изменение задачи - - - Удаление задачи - - - Скачивание файлов - - - Загрузка файлов - - - Получение данных для загрузки файлов - - - Открытие форм (представлений) - - - Просмотр вебхуков - - - Создание и управление вебхуками - - - Просмотр лога вебхуков - - - Удаление записи в логе вебхука - - - Поиск сотрудников - - - Поиск чатов - - - Поиск сообщений - - - Тип события webhook для реакций - - - Создание - - - Удаление - - - Тип сущности для поиска - - - Пользователь - - - Задача - - - Сортировка результатов поиска - - - По релевантности - - - По алфавиту - - - Порядок сортировки - - - По возрастанию - - - По убыванию - - - Тип задачи - - - Позвонить контакту - - - Встреча - - - Простое напоминание - - - Событие - - - Написать письмо - - - Статус напоминания - - - Выполнено - - - Активно - - - Тип события webhook для пользователей - - - Приглашение - - - Подтверждение - - - Обновление - - - Приостановка - - - Активация - - - Удаление - - - Роль пользователя в системе - - - Администратор - - - Сотрудник - - - Мульти-гость - - - Гость - - - Роль пользователя, допустимая при создании и редактировании. Роль `guest` недоступна для установки через API. - - - Администратор - - - Сотрудник - - - Мульти-гость - - - Коды ошибок валидации - - - Обязательное поле (не может быть пустым) - - - Слишком длинное значение (пояснения вы получите в поле message) - - - Поле не соответствует правилам (пояснения вы получите в поле message) - - - Поле имеет непредусмотренное значение - - - Поле имеет недопустимое значение - - - Название для этого поля уже существует - - - Emoji статуса не может содержать значения отличные от Emoji символа - - - Объект не найден - - - Объект уже существует (пояснения вы получите в поле message) - - - Ошибка личного чата (пояснения вы получите в поле message) - - - Отображаемая ошибка (пояснения вы получите в поле message) - - - Действие запрещено - - - Выбран слишком большой диапазон дат - - - Некорректный URL вебхука - - - Достигнут лимит запросов - - - Превышен лимит активных сотрудников (пояснения вы получите в поле message) - - - Превышен лимит количества реакций, которые может добавить пользователь (20 уникальных реакций) - - - Превышен лимит количества уникальных реакций, которые можно добавить на сообщение (30 уникальных реакций) - - - Превышен лимит количества реакций, которые можно добавить на сообщение (1000 реакций) - - - Ошибка выполнения запроса (пояснения вы получите в поле message) - - - Не удалось найти идентификатор события - - - Время жизни идентификатора события истекло - - - Обязательный параметр не передан - - - Недопустимое значение (не входит в список допустимых) - - - Значение неприменимо в данном контексте (пояснения вы получите в поле message) - - - Нельзя изменить свои собственные данные - - - Нельзя изменить данные владельца - - - Значение уже назначено - - - Недостаточно прав для выполнения действия (пояснения вы получите в поле message) - - - Доступ запрещён (недостаточно прав) - - - Доступ запрещён - - - Некорректные параметры запроса (пояснения вы получите в поле message) - - - Требуется оплата - - - Значение слишком короткое (пояснения вы получите в поле message) - - - Значение слишком длинное (пояснения вы получите в поле message) - - - Использовано зарезервированное системное слово (here, all) - - - Тип события webhook - - - Создание - - - Обновление - - - Удаление - - - diff --git a/sdk/csharp/generated/obj/Debug/net8.0/ref/Pachca.dll b/sdk/csharp/generated/obj/Debug/net8.0/ref/Pachca.dll deleted file mode 100644 index ce7bcef2..00000000 Binary files a/sdk/csharp/generated/obj/Debug/net8.0/ref/Pachca.dll and /dev/null differ diff --git a/sdk/csharp/generated/obj/Debug/net8.0/refint/Pachca.dll b/sdk/csharp/generated/obj/Debug/net8.0/refint/Pachca.dll deleted file mode 100644 index ce7bcef2..00000000 Binary files a/sdk/csharp/generated/obj/Debug/net8.0/refint/Pachca.dll and /dev/null differ diff --git a/sdk/csharp/generated/obj/Pachca.csproj.nuget.dgspec.json b/sdk/csharp/generated/obj/Pachca.csproj.nuget.dgspec.json deleted file mode 100644 index 49db440d..00000000 --- a/sdk/csharp/generated/obj/Pachca.csproj.nuget.dgspec.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "format": 1, - "restore": { - "/home/runner/work/openapi/openapi/sdk/csharp/generated/Pachca.csproj": {} - }, - "projects": { - "/home/runner/work/openapi/openapi/sdk/csharp/generated/Pachca.csproj": { - "version": "0.0.0", - "restore": { - "projectUniqueName": "/home/runner/work/openapi/openapi/sdk/csharp/generated/Pachca.csproj", - "projectName": "Pachca.Sdk", - "projectPath": "/home/runner/work/openapi/openapi/sdk/csharp/generated/Pachca.csproj", - "packagesPath": "/home/runner/.nuget/packages/", - "outputPath": "/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/", - "projectStyle": "PackageReference", - "configFilePaths": [ - "/home/runner/.nuget/NuGet/NuGet.Config" - ], - "originalTargetFrameworks": [ - "net8.0" - ], - "sources": { - "https://api.nuget.org/v3/index.json": {} - }, - "frameworks": { - "net8.0": { - "targetAlias": "net8.0", - "projectReferences": {} - } - }, - "warningProperties": { - "warnAsError": [ - "NU1605" - ] - }, - "restoreAuditProperties": { - "enableAudit": "true", - "auditLevel": "low", - "auditMode": "direct" - }, - "SdkAnalysisLevel": "10.0.200" - }, - "frameworks": { - "net8.0": { - "targetAlias": "net8.0", - "imports": [ - "net461", - "net462", - "net47", - "net471", - "net472", - "net48", - "net481" - ], - "assetTargetFallback": true, - "warn": true, - "frameworkReferences": { - "Microsoft.NETCore.App": { - "privateAssets": "all" - } - }, - "runtimeIdentifierGraphPath": "/usr/share/dotnet/sdk/10.0.201/PortableRuntimeIdentifierGraph.json" - } - } - } - } -} \ No newline at end of file diff --git a/sdk/csharp/generated/obj/Pachca.csproj.nuget.g.props b/sdk/csharp/generated/obj/Pachca.csproj.nuget.g.props deleted file mode 100644 index 2098f6f9..00000000 --- a/sdk/csharp/generated/obj/Pachca.csproj.nuget.g.props +++ /dev/null @@ -1,15 +0,0 @@ - - - - True - NuGet - $(MSBuildThisFileDirectory)project.assets.json - /home/runner/.nuget/packages/ - /home/runner/.nuget/packages/ - PackageReference - 7.0.0 - - - - - \ No newline at end of file diff --git a/sdk/csharp/generated/obj/Pachca.csproj.nuget.g.targets b/sdk/csharp/generated/obj/Pachca.csproj.nuget.g.targets deleted file mode 100644 index 3dc06ef3..00000000 --- a/sdk/csharp/generated/obj/Pachca.csproj.nuget.g.targets +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/sdk/csharp/generated/obj/project.assets.json b/sdk/csharp/generated/obj/project.assets.json deleted file mode 100644 index 6e7a2895..00000000 --- a/sdk/csharp/generated/obj/project.assets.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "version": 3, - "targets": { - "net8.0": {} - }, - "libraries": {}, - "projectFileDependencyGroups": { - "net8.0": [] - }, - "packageFolders": { - "/home/runner/.nuget/packages/": {} - }, - "project": { - "version": "0.0.0", - "restore": { - "projectUniqueName": "/home/runner/work/openapi/openapi/sdk/csharp/generated/Pachca.csproj", - "projectName": "Pachca.Sdk", - "projectPath": "/home/runner/work/openapi/openapi/sdk/csharp/generated/Pachca.csproj", - "packagesPath": "/home/runner/.nuget/packages/", - "outputPath": "/home/runner/work/openapi/openapi/sdk/csharp/generated/obj/", - "projectStyle": "PackageReference", - "configFilePaths": [ - "/home/runner/.nuget/NuGet/NuGet.Config" - ], - "originalTargetFrameworks": [ - "net8.0" - ], - "sources": { - "https://api.nuget.org/v3/index.json": {} - }, - "frameworks": { - "net8.0": { - "targetAlias": "net8.0", - "projectReferences": {} - } - }, - "warningProperties": { - "warnAsError": [ - "NU1605" - ] - }, - "restoreAuditProperties": { - "enableAudit": "true", - "auditLevel": "low", - "auditMode": "direct" - }, - "SdkAnalysisLevel": "10.0.200" - }, - "frameworks": { - "net8.0": { - "targetAlias": "net8.0", - "imports": [ - "net461", - "net462", - "net47", - "net471", - "net472", - "net48", - "net481" - ], - "assetTargetFallback": true, - "warn": true, - "frameworkReferences": { - "Microsoft.NETCore.App": { - "privateAssets": "all" - } - }, - "runtimeIdentifierGraphPath": "/usr/share/dotnet/sdk/10.0.201/PortableRuntimeIdentifierGraph.json" - } - } - } -} \ No newline at end of file diff --git a/sdk/csharp/generated/obj/project.nuget.cache b/sdk/csharp/generated/obj/project.nuget.cache deleted file mode 100644 index c3a217b3..00000000 --- a/sdk/csharp/generated/obj/project.nuget.cache +++ /dev/null @@ -1,8 +0,0 @@ -{ - "version": 2, - "dgSpecHash": "L7vmAtClJb4=", - "success": true, - "projectFilePath": "/home/runner/work/openapi/openapi/sdk/csharp/generated/Pachca.csproj", - "expectedPackageFiles": [], - "logs": [] -} \ No newline at end of file diff --git a/sdk/go/README.md b/sdk/go/README.md index bb0b5c45..9e4db7c4 100644 --- a/sdk/go/README.md +++ b/sdk/go/README.md @@ -5,7 +5,7 @@ Go клиент для [Pachca API](https://dev.pachca.com). ## Установка ```bash -go get github.com/pachca/openapi/sdk/go/generated@v1.0.1 +go get github.com/pachca/openapi/sdk/go/generated@latest ``` ## Использование @@ -68,11 +68,10 @@ var cursor *string for { result, err := client.Chats.ListChats(ctx, &pachca.ListChatsParams{Cursor: cursor}) if err != nil { break } + if len(result.Data) == 0 { break } chats = append(chats, result.Data...) - if result.Meta == nil || result.Meta.Paginate == nil || result.Meta.Paginate.NextPage == nil { - break - } - cursor = result.Meta.Paginate.NextPage + nextPage := result.Meta.Paginate.NextPage + cursor = &nextPage } // Автоматически diff --git a/sdk/go/examples/main.go b/sdk/go/examples/main.go index 174a0a87..572b507d 100644 --- a/sdk/go/examples/main.go +++ b/sdk/go/examples/main.go @@ -35,6 +35,15 @@ func main() { client := pachca.NewPachcaClient(token) ctx := context.Background() + // ── Step 0: GET — Fetch chat (verifies datetime deserialization) ─ + fmt.Println("0. Fetching chat...") + chat, err := client.Chats.GetChat(ctx, int32(chatID)) + if err != nil { + log.Fatalf("GetChat failed: %v", err) + } + fmt.Printf(" Chat: %s, createdAt=%v (%T), lastMessageAt=%v (%T)\n", + chat.Name, chat.CreatedAt, chat.CreatedAt, chat.LastMessageAt, chat.LastMessageAt) + // ── Step 1: POST — Create a message ────────────────────────────── fmt.Println("1. Creating message...") created, err := client.Messages.CreateMessage(ctx, pachca.MessageCreateRequest{ diff --git a/sdk/go/generated/client.go b/sdk/go/generated/client.go index f5190cee..2bf97491 100644 --- a/sdk/go/generated/client.go +++ b/sdk/go/generated/client.go @@ -120,11 +120,15 @@ func (s *SecurityService) GetAuditEvents(ctx context.Context, params *GetAuditEv return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -142,10 +146,11 @@ func (s *SecurityService) GetAuditEventsAll(ctx context.Context, params *GetAudi return nil, err } items = append(items, result.Data...) - if result.Meta == nil || result.Meta.Paginate == nil || result.Meta.Paginate.NextPage == nil { + if len(result.Data) == 0 { return items, nil } - cursor = result.Meta.Paginate.NextPage + nextPage := result.Meta.Paginate.NextPage + cursor = &nextPage } } @@ -185,11 +190,15 @@ func (s *BotsService) GetWebhookEvents(ctx context.Context, params *GetWebhookEv return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -207,10 +216,11 @@ func (s *BotsService) GetWebhookEventsAll(ctx context.Context, params *GetWebhoo return nil, err } items = append(items, result.Data...) - if result.Meta == nil || result.Meta.Paginate == nil || result.Meta.Paginate.NextPage == nil { + if len(result.Data) == 0 { return items, nil } - cursor = result.Meta.Paginate.NextPage + nextPage := result.Meta.Paginate.NextPage + cursor = &nextPage } } @@ -240,11 +250,15 @@ func (s *BotsService) UpdateBot(ctx context.Context, id int32, request BotUpdate return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -264,11 +278,15 @@ func (s *BotsService) DeleteWebhookEvent(ctx context.Context, id string) error { return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -284,8 +302,11 @@ func (s *ChatsService) ListChats(ctx context.Context, params *ListChatsParams) ( return nil, err } q := u.Query() - if params != nil && params.SortID != nil { - q.Set("sort[{field}]", string(*params.SortID)) + if params != nil && params.Sort != nil { + q.Set("sort", string(*params.Sort)) + } + if params != nil && params.Order != nil { + q.Set("order", string(*params.Order)) } if params != nil && params.Availability != nil { q.Set("availability", string(*params.Availability)) @@ -324,11 +345,15 @@ func (s *ChatsService) ListChats(ctx context.Context, params *ListChatsParams) ( return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -346,10 +371,11 @@ func (s *ChatsService) ListChatsAll(ctx context.Context, params *ListChatsParams return nil, err } items = append(items, result.Data...) - if result.Meta == nil || result.Meta.Paginate == nil || result.Meta.Paginate.NextPage == nil { + if len(result.Data) == 0 { return items, nil } - cursor = result.Meta.Paginate.NextPage + nextPage := result.Meta.Paginate.NextPage + cursor = &nextPage } } @@ -374,11 +400,15 @@ func (s *ChatsService) GetChat(ctx context.Context, id int32) (*Chat, error) { return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -409,11 +439,15 @@ func (s *ChatsService) CreateChat(ctx context.Context, request ChatCreateRequest return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -444,11 +478,15 @@ func (s *ChatsService) UpdateChat(ctx context.Context, id int32, request ChatUpd return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -468,11 +506,15 @@ func (s *ChatsService) ArchiveChat(ctx context.Context, id int32) error { return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -492,11 +534,15 @@ func (s *ChatsService) UnarchiveChat(ctx context.Context, id int32) error { return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -525,11 +571,15 @@ func (s *CommonService) DownloadExport(ctx context.Context, id int32) (string, e return location, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return "", &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return "", fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return "", &e } } @@ -560,11 +610,15 @@ func (s *CommonService) ListProperties(ctx context.Context, params ListPropertie return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -589,11 +643,15 @@ func (s *CommonService) RequestExport(ctx context.Context, request ExportRequest return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -637,7 +695,9 @@ func (s *CommonService) UploadFile(ctx context.Context, directUrl string, reques return nil default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -661,11 +721,15 @@ func (s *CommonService) GetUploadParams(ctx context.Context) (*UploadParams, err return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -709,11 +773,15 @@ func (s *MembersService) ListMembers(ctx context.Context, id int32, params *List return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -731,10 +799,11 @@ func (s *MembersService) ListMembersAll(ctx context.Context, id int32, params *L return nil, err } items = append(items, result.Data...) - if result.Meta == nil || result.Meta.Paginate == nil || result.Meta.Paginate.NextPage == nil { + if len(result.Data) == 0 { return items, nil } - cursor = result.Meta.Paginate.NextPage + nextPage := result.Meta.Paginate.NextPage + cursor = &nextPage } } @@ -758,11 +827,15 @@ func (s *MembersService) AddTags(ctx context.Context, id int32, groupTagIds []in return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -787,11 +860,15 @@ func (s *MembersService) AddMembers(ctx context.Context, id int32, request AddMe return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -816,11 +893,15 @@ func (s *MembersService) UpdateMemberRole(ctx context.Context, id int32, userId return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -840,11 +921,15 @@ func (s *MembersService) RemoveTag(ctx context.Context, id int32, tagId int32) e return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -864,11 +949,15 @@ func (s *MembersService) LeaveChat(ctx context.Context, id int32) error { return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -888,11 +977,15 @@ func (s *MembersService) RemoveMember(ctx context.Context, id int32, userId int3 return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -908,8 +1001,10 @@ func (s *GroupTagsService) ListTags(ctx context.Context, params *ListTagsParams) return nil, err } q := u.Query() - if params != nil && params.Names != nil { - q.Set("names", fmt.Sprintf("%v", *params.Names)) + if params != nil { + for _, v := range params.Names { + q.Add("names[]", fmt.Sprintf("%v", v)) + } } if params != nil && params.Limit != nil { q.Set("limit", fmt.Sprintf("%v", *params.Limit)) @@ -936,11 +1031,15 @@ func (s *GroupTagsService) ListTags(ctx context.Context, params *ListTagsParams) return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -958,10 +1057,11 @@ func (s *GroupTagsService) ListTagsAll(ctx context.Context, params *ListTagsPara return nil, err } items = append(items, result.Data...) - if result.Meta == nil || result.Meta.Paginate == nil || result.Meta.Paginate.NextPage == nil { + if len(result.Data) == 0 { return items, nil } - cursor = result.Meta.Paginate.NextPage + nextPage := result.Meta.Paginate.NextPage + cursor = &nextPage } } @@ -986,16 +1086,20 @@ func (s *GroupTagsService) GetTag(ctx context.Context, id int32) (*GroupTag, err return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } -func (s *GroupTagsService) GetTagUsers(ctx context.Context, id int32, params *GetTagUsersParams) (*ListMembersResponse, error) { +func (s *GroupTagsService) GetTagUsers(ctx context.Context, id int32, params *GetTagUsersParams) (*GetTagUsersResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/group_tags/%v/users", s.baseURL, id)) if err != nil { return nil, err @@ -1019,18 +1123,22 @@ func (s *GroupTagsService) GetTagUsers(ctx context.Context, id int32, params *Ge defer resp.Body.Close() switch resp.StatusCode { case http.StatusOK: - var result ListMembersResponse + var result GetTagUsersResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, err } return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -1048,10 +1156,11 @@ func (s *GroupTagsService) GetTagUsersAll(ctx context.Context, id int32, params return nil, err } items = append(items, result.Data...) - if result.Meta == nil || result.Meta.Paginate == nil || result.Meta.Paginate.NextPage == nil { + if len(result.Data) == 0 { return items, nil } - cursor = result.Meta.Paginate.NextPage + nextPage := result.Meta.Paginate.NextPage + cursor = &nextPage } } @@ -1081,11 +1190,15 @@ func (s *GroupTagsService) CreateTag(ctx context.Context, request GroupTagReques return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -1116,11 +1229,15 @@ func (s *GroupTagsService) UpdateTag(ctx context.Context, id int32, request Grou return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -1140,11 +1257,15 @@ func (s *GroupTagsService) DeleteTag(ctx context.Context, id int32) error { return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -1161,8 +1282,11 @@ func (s *MessagesService) ListChatMessages(ctx context.Context, params ListChatM } q := u.Query() q.Set("chat_id", fmt.Sprintf("%v", params.ChatID)) - if params.SortID != nil { - q.Set("sort[{field}]", string(*params.SortID)) + if params.Sort != nil { + q.Set("sort", string(*params.Sort)) + } + if params.Order != nil { + q.Set("order", string(*params.Order)) } if params.Limit != nil { q.Set("limit", fmt.Sprintf("%v", *params.Limit)) @@ -1189,11 +1313,15 @@ func (s *MessagesService) ListChatMessages(ctx context.Context, params ListChatM return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -1211,10 +1339,11 @@ func (s *MessagesService) ListChatMessagesAll(ctx context.Context, params *ListC return nil, err } items = append(items, result.Data...) - if result.Meta == nil || result.Meta.Paginate == nil || result.Meta.Paginate.NextPage == nil { + if len(result.Data) == 0 { return items, nil } - cursor = result.Meta.Paginate.NextPage + nextPage := result.Meta.Paginate.NextPage + cursor = &nextPage } } @@ -1239,11 +1368,15 @@ func (s *MessagesService) GetMessage(ctx context.Context, id int32) (*Message, e return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -1274,11 +1407,15 @@ func (s *MessagesService) CreateMessage(ctx context.Context, request MessageCrea return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -1298,11 +1435,15 @@ func (s *MessagesService) PinMessage(ctx context.Context, id int32) error { return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -1333,11 +1474,15 @@ func (s *MessagesService) UpdateMessage(ctx context.Context, id int32, request M return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -1357,11 +1502,15 @@ func (s *MessagesService) DeleteMessage(ctx context.Context, id int32) error { return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -1381,11 +1530,15 @@ func (s *MessagesService) UnpinMessage(ctx context.Context, id int32) error { return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -1415,11 +1568,15 @@ func (s *LinkPreviewsService) CreateLinkPreviews(ctx context.Context, id int32, return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -1460,11 +1617,15 @@ func (s *ReactionsService) ListReactions(ctx context.Context, id int32, params * return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -1482,10 +1643,11 @@ func (s *ReactionsService) ListReactionsAll(ctx context.Context, id int32, param return nil, err } items = append(items, result.Data...) - if result.Meta == nil || result.Meta.Paginate == nil || result.Meta.Paginate.NextPage == nil { + if len(result.Data) == 0 { return items, nil } - cursor = result.Meta.Paginate.NextPage + nextPage := result.Meta.Paginate.NextPage + cursor = &nextPage } } @@ -1513,11 +1675,15 @@ func (s *ReactionsService) AddReaction(ctx context.Context, id int32, request Re return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -1547,11 +1713,15 @@ func (s *ReactionsService) RemoveReaction(ctx context.Context, id int32, params return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -1592,11 +1762,15 @@ func (s *ReadMembersService) ListReadMembers(ctx context.Context, id int32, para return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -1627,11 +1801,15 @@ func (s *ThreadsService) GetThread(ctx context.Context, id int32) (*Thread, erro return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -1657,11 +1835,15 @@ func (s *ThreadsService) CreateThread(ctx context.Context, id int32) (*Thread, e return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -1692,11 +1874,15 @@ func (s *ProfileService) GetTokenInfo(ctx context.Context) (*AccessTokenInfo, er return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -1722,11 +1908,15 @@ func (s *ProfileService) GetProfile(ctx context.Context) (*User, error) { return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -1750,11 +1940,65 @@ func (s *ProfileService) GetStatus(ctx context.Context) (*any, error) { return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } + return nil, &e + default: + var e ApiError + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } + return nil, &e + } +} + +func (s *ProfileService) UpdateProfileAvatar(ctx context.Context, image io.Reader) (*AvatarData, error) { + pr, pw := io.Pipe() + writer := multipart.NewWriter(pw) + go func() { + defer pw.Close() + defer writer.Close() + part, err := writer.CreateFormFile("image", "upload") + if err != nil { + pw.CloseWithError(err) + return + } + if _, err := io.Copy(part, image); err != nil { + pw.CloseWithError(err) + return + } + }() + req, err := http.NewRequestWithContext(ctx, "PUT", fmt.Sprintf("%s/profile/avatar", s.baseURL), pr) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", writer.FormDataContentType()) + resp, err := doWithRetry(s.client, req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + switch resp.StatusCode { + case http.StatusOK: + var result struct { + Data AvatarData `json:"data"` + } + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return nil, err + } + return &result.Data, nil + case http.StatusUnauthorized: + var e OAuthError + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -1785,15 +2029,47 @@ func (s *ProfileService) UpdateStatus(ctx context.Context, request StatusUpdateR return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } +func (s *ProfileService) DeleteProfileAvatar(ctx context.Context) error { + req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/profile/avatar", s.baseURL), nil) + if err != nil { + return err + } + resp, err := doWithRetry(s.client, req) + if err != nil { + return err + } + defer resp.Body.Close() + switch resp.StatusCode { + case http.StatusNoContent: + return nil + case http.StatusUnauthorized: + var e OAuthError + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } + return &e + default: + var e ApiError + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } + return &e + } +} + func (s *ProfileService) DeleteStatus(ctx context.Context) error { req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/profile/status", s.baseURL), nil) if err != nil { @@ -1809,11 +2085,15 @@ func (s *ProfileService) DeleteStatus(ctx context.Context) error { return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -1823,7 +2103,7 @@ type SearchService struct { client *http.Client } -func (s *SearchService) SearchChats(ctx context.Context, params *SearchChatsParams) (*ListChatsResponse, error) { +func (s *SearchService) SearchChats(ctx context.Context, params *SearchChatsParams) (*SearchChatsResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/search/chats", s.baseURL)) if err != nil { return nil, err @@ -1868,18 +2148,22 @@ func (s *SearchService) SearchChats(ctx context.Context, params *SearchChatsPara defer resp.Body.Close() switch resp.StatusCode { case http.StatusOK: - var result ListChatsResponse + var result SearchChatsResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, err } return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -1897,14 +2181,15 @@ func (s *SearchService) SearchChatsAll(ctx context.Context, params *SearchChatsP return nil, err } items = append(items, result.Data...) - if result.Meta == nil || result.Meta.Paginate == nil || result.Meta.Paginate.NextPage == nil { + if len(result.Data) == 0 { return items, nil } - cursor = result.Meta.Paginate.NextPage + nextPage := result.Meta.Paginate.NextPage + cursor = &nextPage } } -func (s *SearchService) SearchMessages(ctx context.Context, params *SearchMessagesParams) (*ListChatMessagesResponse, error) { +func (s *SearchService) SearchMessages(ctx context.Context, params *SearchMessagesParams) (*SearchMessagesResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/search/messages", s.baseURL)) if err != nil { return nil, err @@ -1928,11 +2213,15 @@ func (s *SearchService) SearchMessages(ctx context.Context, params *SearchMessag if params != nil && params.CreatedTo != nil { q.Set("created_to", params.CreatedTo.Format(time.RFC3339)) } - if params != nil && params.ChatIDs != nil { - q.Set("chat_ids", fmt.Sprintf("%v", params.ChatIDs)) + if params != nil { + for _, v := range params.ChatIDs { + q.Add("chat_ids[]", fmt.Sprintf("%v", v)) + } } - if params != nil && params.UserIDs != nil { - q.Set("user_ids", fmt.Sprintf("%v", params.UserIDs)) + if params != nil { + for _, v := range params.UserIDs { + q.Add("user_ids[]", fmt.Sprintf("%v", v)) + } } if params != nil && params.Active != nil { q.Set("active", fmt.Sprintf("%v", *params.Active)) @@ -1949,18 +2238,22 @@ func (s *SearchService) SearchMessages(ctx context.Context, params *SearchMessag defer resp.Body.Close() switch resp.StatusCode { case http.StatusOK: - var result ListChatMessagesResponse + var result SearchMessagesResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, err } return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -1978,14 +2271,15 @@ func (s *SearchService) SearchMessagesAll(ctx context.Context, params *SearchMes return nil, err } items = append(items, result.Data...) - if result.Meta == nil || result.Meta.Paginate == nil || result.Meta.Paginate.NextPage == nil { + if len(result.Data) == 0 { return items, nil } - cursor = result.Meta.Paginate.NextPage + nextPage := result.Meta.Paginate.NextPage + cursor = &nextPage } } -func (s *SearchService) SearchUsers(ctx context.Context, params *SearchUsersParams) (*ListMembersResponse, error) { +func (s *SearchService) SearchUsers(ctx context.Context, params *SearchUsersParams) (*SearchUsersResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/search/users", s.baseURL)) if err != nil { return nil, err @@ -2012,8 +2306,10 @@ func (s *SearchService) SearchUsers(ctx context.Context, params *SearchUsersPara if params != nil && params.CreatedTo != nil { q.Set("created_to", params.CreatedTo.Format(time.RFC3339)) } - if params != nil && params.CompanyRoles != nil { - q.Set("company_roles", fmt.Sprintf("%v", params.CompanyRoles)) + if params != nil { + for _, v := range params.CompanyRoles { + q.Add("company_roles[]", fmt.Sprintf("%v", v)) + } } u.RawQuery = q.Encode() req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) @@ -2027,18 +2323,22 @@ func (s *SearchService) SearchUsers(ctx context.Context, params *SearchUsersPara defer resp.Body.Close() switch resp.StatusCode { case http.StatusOK: - var result ListMembersResponse + var result SearchUsersResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, err } return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -2056,10 +2356,11 @@ func (s *SearchService) SearchUsersAll(ctx context.Context, params *SearchUsersP return nil, err } items = append(items, result.Data...) - if result.Meta == nil || result.Meta.Paginate == nil || result.Meta.Paginate.NextPage == nil { + if len(result.Data) == 0 { return items, nil } - cursor = result.Meta.Paginate.NextPage + nextPage := result.Meta.Paginate.NextPage + cursor = &nextPage } } @@ -2099,11 +2400,15 @@ func (s *TasksService) ListTasks(ctx context.Context, params *ListTasksParams) ( return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -2121,10 +2426,11 @@ func (s *TasksService) ListTasksAll(ctx context.Context, params *ListTasksParams return nil, err } items = append(items, result.Data...) - if result.Meta == nil || result.Meta.Paginate == nil || result.Meta.Paginate.NextPage == nil { + if len(result.Data) == 0 { return items, nil } - cursor = result.Meta.Paginate.NextPage + nextPage := result.Meta.Paginate.NextPage + cursor = &nextPage } } @@ -2149,11 +2455,15 @@ func (s *TasksService) GetTask(ctx context.Context, id int32) (*Task, error) { return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -2184,11 +2494,15 @@ func (s *TasksService) CreateTask(ctx context.Context, request TaskCreateRequest return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -2219,11 +2533,15 @@ func (s *TasksService) UpdateTask(ctx context.Context, id int32, request TaskUpd return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -2243,11 +2561,15 @@ func (s *TasksService) DeleteTask(ctx context.Context, id int32) error { return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -2257,7 +2579,7 @@ type UsersService struct { client *http.Client } -func (s *UsersService) ListUsers(ctx context.Context, params *ListUsersParams) (*ListMembersResponse, error) { +func (s *UsersService) ListUsers(ctx context.Context, params *ListUsersParams) (*ListUsersResponse, error) { u, err := url.Parse(fmt.Sprintf("%s/users", s.baseURL)) if err != nil { return nil, err @@ -2284,18 +2606,22 @@ func (s *UsersService) ListUsers(ctx context.Context, params *ListUsersParams) ( defer resp.Body.Close() switch resp.StatusCode { case http.StatusOK: - var result ListMembersResponse + var result ListUsersResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, err } return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -2313,10 +2639,11 @@ func (s *UsersService) ListUsersAll(ctx context.Context, params *ListUsersParams return nil, err } items = append(items, result.Data...) - if result.Meta == nil || result.Meta.Paginate == nil || result.Meta.Paginate.NextPage == nil { + if len(result.Data) == 0 { return items, nil } - cursor = result.Meta.Paginate.NextPage + nextPage := result.Meta.Paginate.NextPage + cursor = &nextPage } } @@ -2341,11 +2668,15 @@ func (s *UsersService) GetUser(ctx context.Context, id int32) (*User, error) { return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -2369,11 +2700,15 @@ func (s *UsersService) GetUserStatus(ctx context.Context, userId int32) (*any, e return &result, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -2404,11 +2739,15 @@ func (s *UsersService) CreateUser(ctx context.Context, request UserCreateRequest return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -2439,11 +2778,65 @@ func (s *UsersService) UpdateUser(ctx context.Context, id int32, request UserUpd return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } + return nil, &e + } +} + +func (s *UsersService) UpdateUserAvatar(ctx context.Context, userId int32, image io.Reader) (*AvatarData, error) { + pr, pw := io.Pipe() + writer := multipart.NewWriter(pw) + go func() { + defer pw.Close() + defer writer.Close() + part, err := writer.CreateFormFile("image", "upload") + if err != nil { + pw.CloseWithError(err) + return + } + if _, err := io.Copy(part, image); err != nil { + pw.CloseWithError(err) + return + } + }() + req, err := http.NewRequestWithContext(ctx, "PUT", fmt.Sprintf("%s/users/%v/avatar", s.baseURL, userId), pr) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", writer.FormDataContentType()) + resp, err := doWithRetry(s.client, req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + switch resp.StatusCode { + case http.StatusOK: + var result struct { + Data AvatarData `json:"data"` + } + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return nil, err + } + return &result.Data, nil + case http.StatusUnauthorized: + var e OAuthError + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } + return nil, &e + default: + var e ApiError + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -2474,11 +2867,15 @@ func (s *UsersService) UpdateUserStatus(ctx context.Context, userId int32, reque return &result.Data, nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return nil, &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return nil, &e } } @@ -2498,11 +2895,43 @@ func (s *UsersService) DeleteUser(ctx context.Context, id int32) error { return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } + return &e + } +} + +func (s *UsersService) DeleteUserAvatar(ctx context.Context, userId int32) error { + req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/users/%v/avatar", s.baseURL, userId), nil) + if err != nil { + return err + } + resp, err := doWithRetry(s.client, req) + if err != nil { + return err + } + defer resp.Body.Close() + switch resp.StatusCode { + case http.StatusNoContent: + return nil + case http.StatusUnauthorized: + var e OAuthError + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } + return &e + default: + var e ApiError + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -2522,11 +2951,15 @@ func (s *UsersService) DeleteUserStatus(ctx context.Context, userId int32) error return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } @@ -2556,11 +2989,15 @@ func (s *ViewsService) OpenView(ctx context.Context, request OpenViewRequest) er return nil case http.StatusUnauthorized: var e OAuthError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + e.Err = fmt.Sprintf("HTTP 401: %v", err) + } return &e default: var e ApiError - json.NewDecoder(resp.Body).Decode(&e) + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return fmt.Errorf("HTTP %d: %w", resp.StatusCode, err) + } return &e } } diff --git a/sdk/go/generated/examples.json b/sdk/go/generated/examples.json index f83d2c02..1e2ae0f8 100644 --- a/sdk/go/generated/examples.json +++ b/sdk/go/generated/examples.json @@ -7,7 +7,7 @@ }, "SecurityOperations_getAuditEvents": { "usage": "params := &GetAuditEventsParams{\n\tStartTime: Ptr(\"2025-05-01T09:11:00Z\"),\n\tEndTime: Ptr(\"2025-05-02T09:11:00Z\"),\n\tEventKey: Ptr(AuditEventKeyUserLogin),\n\tActorID: Ptr(\"98765\"),\n\tActorType: Ptr(\"User\"),\n\tEntityID: Ptr(\"98765\"),\n\tEntityType: Ptr(\"User\"),\n\tLimit: Ptr(int32(1)),\n\tCursor: Ptr(\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"),\n}\nresponse, err := client.Security.GetAuditEvents(ctx, params)", - "output": "GetAuditEventsResponse{Data: []AuditEvent, Meta: *PaginationMeta}", + "output": "GetAuditEventsResponse{Data: []AuditEvent, Meta: PaginationMeta}", "imports": [ "AuditEventKey", "GetAuditEventsParams" @@ -15,7 +15,7 @@ }, "BotOperations_getWebhookEvents": { "usage": "params := &GetWebhookEventsParams{\n\tLimit: Ptr(int32(1)),\n\tCursor: Ptr(\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"),\n}\nresponse, err := client.Bots.GetWebhookEvents(ctx, params)", - "output": "GetWebhookEventsResponse{Data: []WebhookEvent, Meta: *PaginationMeta}", + "output": "GetWebhookEventsResponse{Data: []WebhookEvent, Meta: PaginationMeta}", "imports": [ "GetWebhookEventsParams" ] @@ -33,10 +33,11 @@ "usage": "client.Bots.DeleteWebhookEvent(ctx, \"01KAJZ2XDSS2S3DSW9EXJZ0TBV\")" }, "ChatOperations_listChats": { - "usage": "params := &ListChatsParams{\n\tSortID: Ptr(SortOrderDesc),\n\tAvailability: Ptr(ChatAvailabilityIsMember),\n\tLastMessageAtAfter: Ptr(\"2025-01-01T00:00:00.000Z\"),\n\tLastMessageAtBefore: Ptr(\"2025-02-01T00:00:00.000Z\"),\n\tPersonal: Ptr(false),\n\tLimit: Ptr(int32(1)),\n\tCursor: Ptr(\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"),\n}\nresponse, err := client.Chats.ListChats(ctx, params)", - "output": "ListChatsResponse{Data: []Chat, Meta: *PaginationMeta}", + "usage": "params := &ListChatsParams{\n\tSort: Ptr(ChatSortFieldID),\n\tOrder: Ptr(SortOrderDesc),\n\tAvailability: Ptr(ChatAvailabilityIsMember),\n\tLastMessageAtAfter: Ptr(\"2025-01-01T00:00:00.000Z\"),\n\tLastMessageAtBefore: Ptr(\"2025-02-01T00:00:00.000Z\"),\n\tPersonal: Ptr(false),\n\tLimit: Ptr(int32(1)),\n\tCursor: Ptr(\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"),\n}\nresponse, err := client.Chats.ListChats(ctx, params)", + "output": "ListChatsResponse{Data: []Chat, Meta: PaginationMeta}", "imports": [ "ChatAvailability", + "ChatSortField", "ListChatsParams", "SortOrder" ] @@ -97,7 +98,7 @@ }, "ChatMemberOperations_listMembers": { "usage": "params := &ListMembersParams{\n\tRole: Ptr(ChatMemberRoleFilterAll),\n\tLimit: Ptr(int32(1)),\n\tCursor: Ptr(\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"),\n}\nresponse, err := client.Members.ListMembers(ctx, int32(334), params)", - "output": "ListMembersResponse{Data: []User, Meta: *PaginationMeta}", + "output": "ListMembersResponse{Data: []User, Meta: PaginationMeta}", "imports": [ "ChatMemberRoleFilter", "ListMembersParams" @@ -128,11 +129,10 @@ "usage": "client.Members.RemoveMember(ctx, int32(334), int32(186))" }, "GroupTagOperations_listTags": { - "usage": "params := &ListTagsParams{\n\tNames: Ptr(TagNamesFilter{}),\n\tLimit: Ptr(int32(1)),\n\tCursor: Ptr(\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"),\n}\nresponse, err := client.GroupTags.ListTags(ctx, params)", - "output": "ListTagsResponse{Data: []GroupTag, Meta: *PaginationMeta}", + "usage": "params := &ListTagsParams{\n\tNames: []string{\"example\"},\n\tLimit: Ptr(int32(1)),\n\tCursor: Ptr(\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"),\n}\nresponse, err := client.GroupTags.ListTags(ctx, params)", + "output": "ListTagsResponse{Data: []GroupTag, Meta: PaginationMeta}", "imports": [ - "ListTagsParams", - "TagNamesFilter" + "ListTagsParams" ] }, "GroupTagOperations_getTag": { @@ -141,7 +141,7 @@ }, "GroupTagOperations_getTagUsers": { "usage": "params := &GetTagUsersParams{\n\tLimit: Ptr(int32(1)),\n\tCursor: Ptr(\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"),\n}\nresponse, err := client.GroupTags.GetTagUsers(ctx, int32(9111), params)", - "output": "GetTagUsersResponse{Data: []User, Meta: *PaginationMeta}", + "output": "GetTagUsersResponse{Data: []User, Meta: PaginationMeta}", "imports": [ "GetTagUsersParams" ] @@ -166,10 +166,11 @@ "usage": "client.GroupTags.DeleteTag(ctx, int32(9111))" }, "ChatMessageOperations_listChatMessages": { - "usage": "params := ListChatMessagesParams{\n\tChatID: int32(198),\n\tSortID: Ptr(SortOrderDesc),\n\tLimit: Ptr(int32(1)),\n\tCursor: Ptr(\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"),\n}\nresponse, err := client.Messages.ListChatMessages(ctx, params)", - "output": "ListChatMessagesResponse{Data: []Message, Meta: *PaginationMeta}", + "usage": "params := ListChatMessagesParams{\n\tChatID: int32(198),\n\tSort: Ptr(MessageSortFieldID),\n\tOrder: Ptr(SortOrderDesc),\n\tLimit: Ptr(int32(1)),\n\tCursor: Ptr(\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"),\n}\nresponse, err := client.Messages.ListChatMessages(ctx, params)", + "output": "ListChatMessagesResponse{Data: []Message, Meta: PaginationMeta}", "imports": [ "ListChatMessagesParams", + "MessageSortField", "SortOrder" ] }, @@ -218,7 +219,7 @@ }, "ReactionOperations_listReactions": { "usage": "params := &ListReactionsParams{\n\tLimit: Ptr(int32(1)),\n\tCursor: Ptr(\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"),\n}\nresponse, err := client.Reactions.ListReactions(ctx, int32(194275), params)", - "output": "ListReactionsResponse{Data: []Reaction, Meta: *PaginationMeta}", + "output": "ListReactionsResponse{Data: []Reaction, Meta: PaginationMeta}", "imports": [ "ListReactionsParams" ] @@ -263,6 +264,10 @@ "usage": "response, err := client.Profile.GetStatus(ctx)", "output": "any" }, + "ProfileAvatarOperations_updateProfileAvatar": { + "usage": "response, err := client.Profile.UpdateProfileAvatar(ctx, bytes.NewReader(nil))", + "output": "AvatarData{ImageURL: string}" + }, "ProfileOperations_updateStatus": { "usage": "request := StatusUpdateRequest{\n\tStatus: StatusUpdateRequestStatus{\n\t\tEmoji: \"🎮\",\n\t\tTitle: \"Очень занят\",\n\t\tExpiresAt: Ptr(\"2024-04-08T10:00:00.000Z\"),\n\t\tIsAway: Ptr(true),\n\t\tAwayMessage: Ptr(\"Вернусь после 15:00\"),\n\t},\n}\nresponse, err := client.Profile.UpdateStatus(ctx, request)", "output": "UserStatus{Emoji: string, Title: string, ExpiresAt: *string, IsAway: bool, AwayMessage: *UserStatusAwayMessage{Text: string}}", @@ -271,6 +276,9 @@ "StatusUpdateRequestStatus" ] }, + "ProfileAvatarOperations_deleteProfileAvatar": { + "usage": "client.Profile.DeleteProfileAvatar(ctx)" + }, "ProfileOperations_deleteStatus": { "usage": "client.Profile.DeleteStatus(ctx)" }, @@ -303,7 +311,7 @@ }, "TaskOperations_listTasks": { "usage": "params := &ListTasksParams{\n\tLimit: Ptr(int32(1)),\n\tCursor: Ptr(\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"),\n}\nresponse, err := client.Tasks.ListTasks(ctx, params)", - "output": "ListTasksResponse{Data: []Task, Meta: *PaginationMeta}", + "output": "ListTasksResponse{Data: []Task, Meta: PaginationMeta}", "imports": [ "ListTasksParams" ] @@ -338,7 +346,7 @@ }, "UserOperations_listUsers": { "usage": "params := &ListUsersParams{\n\tQuery: Ptr(\"Олег\"),\n\tLimit: Ptr(int32(1)),\n\tCursor: Ptr(\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"),\n}\nresponse, err := client.Users.ListUsers(ctx, params)", - "output": "ListUsersResponse{Data: []User, Meta: *PaginationMeta}", + "output": "ListUsersResponse{Data: []User, Meta: PaginationMeta}", "imports": [ "ListUsersParams" ] @@ -371,6 +379,10 @@ "UserUpdateRequestUser" ] }, + "UserAvatarOperations_updateUserAvatar": { + "usage": "response, err := client.Users.UpdateUserAvatar(ctx, int32(12), bytes.NewReader(nil))", + "output": "AvatarData{ImageURL: string}" + }, "UserStatusOperations_updateUserStatus": { "usage": "request := StatusUpdateRequest{\n\tStatus: StatusUpdateRequestStatus{\n\t\tEmoji: \"🎮\",\n\t\tTitle: \"Очень занят\",\n\t\tExpiresAt: Ptr(\"2024-04-08T10:00:00.000Z\"),\n\t\tIsAway: Ptr(true),\n\t\tAwayMessage: Ptr(\"Вернусь после 15:00\"),\n\t},\n}\nresponse, err := client.Users.UpdateUserStatus(ctx, int32(12), request)", "output": "UserStatus{Emoji: string, Title: string, ExpiresAt: *string, IsAway: bool, AwayMessage: *UserStatusAwayMessage{Text: string}}", @@ -382,6 +394,9 @@ "UserOperations_deleteUser": { "usage": "client.Users.DeleteUser(ctx, int32(12))" }, + "UserAvatarOperations_deleteUserAvatar": { + "usage": "client.Users.DeleteUserAvatar(ctx, int32(12))" + }, "UserStatusOperations_deleteUserStatus": { "usage": "client.Users.DeleteUserStatus(ctx, int32(12))" }, diff --git a/sdk/go/generated/types.go b/sdk/go/generated/types.go index ae446773..3e945649 100644 --- a/sdk/go/generated/types.go +++ b/sdk/go/generated/types.go @@ -73,6 +73,13 @@ const ( ChatMemberRoleFilterMember ChatMemberRoleFilter = "member" // Участник/подписчик ) +type ChatSortField string + +const ( + ChatSortFieldID ChatSortField = "id" // По идентификатору чата + ChatSortFieldLastMessageAt ChatSortField = "last_message_at" // По дате и времени создания последнего сообщения +) + type ChatSubtype string const ( @@ -118,6 +125,12 @@ const ( MessageEntityTypeUser MessageEntityType = "user" // Пользователь ) +type MessageSortField string + +const ( + MessageSortFieldID MessageSortField = "id" // По идентификатору сообщения +) + type OAuthScope string const ( @@ -150,8 +163,10 @@ const ( OAuthScopeProfileRead OAuthScope = "profile:read" // Просмотр информации о своем профиле OAuthScopeProfileStatusRead OAuthScope = "profile_status:read" // Просмотр статуса профиля OAuthScopeProfileStatusWrite OAuthScope = "profile_status:write" // Изменение и удаление статуса профиля + OAuthScopeProfileAvatarWrite OAuthScope = "profile_avatar:write" // Изменение и удаление аватара профиля OAuthScopeUserStatusRead OAuthScope = "user_status:read" // Просмотр статуса сотрудника OAuthScopeUserStatusWrite OAuthScope = "user_status:write" // Изменение и удаление статуса сотрудника + OAuthScopeUserAvatarWrite OAuthScope = "user_avatar:write" // Изменение и удаление аватара сотрудника OAuthScopeCustomPropertiesRead OAuthScope = "custom_properties:read" // Просмотр дополнительных полей OAuthScopeAuditEventsRead OAuthScope = "audit_events:read" // Просмотр журнала аудита OAuthScopeTasksRead OAuthScope = "tasks:read" // Просмотр задач @@ -424,6 +439,10 @@ type AuditEvent struct { UserAgent string `json:"user_agent"` } +type AvatarData struct { + ImageURL string `json:"image_url"` +} + type BotResponseWebhook struct { OutgoingURL string `json:"outgoing_url"` } @@ -529,11 +548,11 @@ type CustomPropertyDefinition struct { } type ExportRequest struct { - StartAt time.Time `json:"start_at"` - EndAt time.Time `json:"end_at"` - WebhookURL string `json:"webhook_url"` - ChatIDs []int32 `json:"chat_ids,omitempty"` - SkipChatsFile *bool `json:"skip_chats_file,omitempty"` + StartAt string `json:"start_at"` + EndAt string `json:"end_at"` + WebhookURL string `json:"webhook_url"` + ChatIDs []int32 `json:"chat_ids,omitempty"` + SkipChatsFile *bool `json:"skip_chats_file,omitempty"` } type File struct { @@ -727,11 +746,11 @@ type OpenViewRequest struct { } type PaginationMetaPaginate struct { - NextPage *string `json:"next_page,omitempty"` + NextPage string `json:"next_page"` } type PaginationMeta struct { - Paginate *PaginationMetaPaginate `json:"paginate,omitempty"` + Paginate PaginationMetaPaginate `json:"paginate"` } type Reaction struct { @@ -778,9 +797,6 @@ type StatusUpdateRequest struct { Status StatusUpdateRequestStatus `json:"status"` } -type TagNamesFilter struct { -} - type Task struct { ID int32 `json:"id"` Kind TaskKind `json:"kind"` @@ -968,12 +984,12 @@ type ViewBlockCheckboxOption struct { } type ViewBlockDate struct { - Type string `json:"type"` // always "date" - Name string `json:"name"` - Label string `json:"label"` - InitialDate *time.Time `json:"initial_date,omitempty"` - Required *bool `json:"required,omitempty"` - Hint *string `json:"hint,omitempty"` + Type string `json:"type"` // always "date" + Name string `json:"name"` + Label string `json:"label"` + InitialDate *string `json:"initial_date,omitempty"` + Required *bool `json:"required,omitempty"` + Hint *string `json:"hint,omitempty"` } type ViewBlockDivider struct { @@ -1052,6 +1068,16 @@ type ViewBlockTime struct { Hint *string `json:"hint,omitempty"` } +type ViewSubmitWebhookPayload struct { + Type string `json:"type"` // always "view" + Event string `json:"event"` // always "submit" + UserID int32 `json:"user_id"` + Data map[string]string `json:"data"` + WebhookTimestamp int32 `json:"webhook_timestamp"` + CallbackID *string `json:"callback_id"` + PrivateMetadata *string `json:"private_metadata"` +} + type WebhookEvent struct { ID string `json:"id"` EventType string `json:"event_type"` @@ -1069,6 +1095,14 @@ type WebhookMessageThread struct { MessageChatID int32 `json:"message_chat_id"` } +type UpdateProfileAvatarRequest struct { + Image io.Reader `json:"image"` +} + +type UpdateUserAvatarRequest struct { + Image io.Reader `json:"image"` +} + type AuditEventDetailsUnion struct { AuditDetailsEmpty *AuditDetailsEmpty AuditDetailsUserUpdated *AuditDetailsUserUpdated @@ -1288,6 +1322,7 @@ type WebhookPayloadUnion struct { MessageWebhookPayload *MessageWebhookPayload ReactionWebhookPayload *ReactionWebhookPayload ButtonWebhookPayload *ButtonWebhookPayload + ViewSubmitWebhookPayload *ViewSubmitWebhookPayload ChatMemberWebhookPayload *ChatMemberWebhookPayload CompanyMemberWebhookPayload *CompanyMemberWebhookPayload LinkSharedWebhookPayload *LinkSharedWebhookPayload @@ -1310,6 +1345,9 @@ func (u *WebhookPayloadUnion) UnmarshalJSON(data []byte) error { case "button": u.ButtonWebhookPayload = &ButtonWebhookPayload{} return json.Unmarshal(data, u.ButtonWebhookPayload) + case "view": + u.ViewSubmitWebhookPayload = &ViewSubmitWebhookPayload{} + return json.Unmarshal(data, u.ViewSubmitWebhookPayload) case "chat_member": u.ChatMemberWebhookPayload = &ChatMemberWebhookPayload{} return json.Unmarshal(data, u.ChatMemberWebhookPayload) @@ -1331,6 +1369,9 @@ func (u WebhookPayloadUnion) MarshalJSON() ([]byte, error) { if u.ButtonWebhookPayload != nil { return json.Marshal(u.ButtonWebhookPayload) } + if u.ViewSubmitWebhookPayload != nil { + return json.Marshal(u.ViewSubmitWebhookPayload) + } if u.ChatMemberWebhookPayload != nil { return json.Marshal(u.ChatMemberWebhookPayload) } @@ -1356,7 +1397,8 @@ type GetAuditEventsParams struct { } type ListChatsParams struct { - SortID *SortOrder + Sort *ChatSortField + Order *SortOrder Availability *ChatAvailability LastMessageAtAfter *time.Time LastMessageAtBefore *time.Time @@ -1376,7 +1418,7 @@ type ListPropertiesParams struct { } type ListTagsParams struct { - Names *TagNamesFilter + Names []string Limit *int32 Cursor *string } @@ -1388,7 +1430,8 @@ type GetTagUsersParams struct { type ListChatMessagesParams struct { ChatID int32 - SortID *SortOrder + Sort *MessageSortField + Order *SortOrder Limit *int32 Cursor *string } @@ -1460,18 +1503,18 @@ type GetWebhookEventsParams struct { } type GetAuditEventsResponse struct { - Data []AuditEvent `json:"data"` - Meta *PaginationMeta `json:"meta,omitempty"` + Data []AuditEvent `json:"data"` + Meta PaginationMeta `json:"meta"` } type ListChatsResponse struct { - Data []Chat `json:"data"` - Meta *PaginationMeta `json:"meta,omitempty"` + Data []Chat `json:"data"` + Meta PaginationMeta `json:"meta"` } type ListMembersResponse struct { - Data []User `json:"data"` - Meta *PaginationMeta `json:"meta,omitempty"` + Data []User `json:"data"` + Meta PaginationMeta `json:"meta"` } type ListPropertiesResponse struct { @@ -1479,23 +1522,23 @@ type ListPropertiesResponse struct { } type ListTagsResponse struct { - Data []GroupTag `json:"data"` - Meta *PaginationMeta `json:"meta,omitempty"` + Data []GroupTag `json:"data"` + Meta PaginationMeta `json:"meta"` } type GetTagUsersResponse struct { - Data []User `json:"data"` - Meta *PaginationMeta `json:"meta,omitempty"` + Data []User `json:"data"` + Meta PaginationMeta `json:"meta"` } type ListChatMessagesResponse struct { - Data []Message `json:"data"` - Meta *PaginationMeta `json:"meta,omitempty"` + Data []Message `json:"data"` + Meta PaginationMeta `json:"meta"` } type ListReactionsResponse struct { - Data []Reaction `json:"data"` - Meta *PaginationMeta `json:"meta,omitempty"` + Data []Reaction `json:"data"` + Meta PaginationMeta `json:"meta"` } type SearchChatsResponse struct { @@ -1514,16 +1557,16 @@ type SearchUsersResponse struct { } type ListTasksResponse struct { - Data []Task `json:"data"` - Meta *PaginationMeta `json:"meta,omitempty"` + Data []Task `json:"data"` + Meta PaginationMeta `json:"meta"` } type ListUsersResponse struct { - Data []User `json:"data"` - Meta *PaginationMeta `json:"meta,omitempty"` + Data []User `json:"data"` + Meta PaginationMeta `json:"meta"` } type GetWebhookEventsResponse struct { - Data []WebhookEvent `json:"data"` - Meta *PaginationMeta `json:"meta,omitempty"` + Data []WebhookEvent `json:"data"` + Meta PaginationMeta `json:"meta"` } diff --git a/sdk/kotlin/README.md b/sdk/kotlin/README.md index 52ae08c5..163f6706 100644 --- a/sdk/kotlin/README.md +++ b/sdk/kotlin/README.md @@ -12,7 +12,7 @@ Kotlin клиент для [Pachca API](https://dev.pachca.com). ```kotlin // build.gradle.kts dependencies { - implementation("com.pachca:pachca-sdk:1.0.1") + implementation("com.pachca:pachca-sdk:latest.release") } ``` @@ -69,11 +69,12 @@ val message = pachca.messages.createMessage(...) // Message, не MessageRespon // Вручную val chats = mutableListOf() var cursor: String? = null -do { +while (true) { val response = pachca.chats.listChats(cursor = cursor) + if (response.data.isEmpty()) break chats.addAll(response.data) - cursor = response.meta?.paginate?.nextPage -} while (cursor != null) + cursor = response.meta.paginate.nextPage +} // Автоматически val allChats = pachca.chats.listChatsAll() diff --git a/sdk/kotlin/examples/main.kt b/sdk/kotlin/examples/main.kt index 325199f8..fc4723e4 100644 --- a/sdk/kotlin/examples/main.kt +++ b/sdk/kotlin/examples/main.kt @@ -28,6 +28,11 @@ fun main() = runBlocking { val client = PachcaClient(token) + // ── Step 0: GET — Fetch chat (verifies datetime deserialization) ─ + println("0. Fetching chat...") + val chat = client.chats.getChat(chatId) + println(" Chat: ${chat.name}, createdAt=${chat.createdAt} (${chat.createdAt::class.simpleName}), lastMessageAt=${chat.lastMessageAt} (${chat.lastMessageAt::class.simpleName})") + // ── Step 1: POST — Create a message ────────────────────────────── println("1. Creating message...") val created = client.messages.createMessage( diff --git a/sdk/kotlin/generated/src/main/kotlin/com/pachca/Client.kt b/sdk/kotlin/generated/src/main/kotlin/com/pachca/Client.kt index 67eae581..364fb0dd 100644 --- a/sdk/kotlin/generated/src/main/kotlin/com/pachca/Client.kt +++ b/sdk/kotlin/generated/src/main/kotlin/com/pachca/Client.kt @@ -13,14 +13,15 @@ import io.ktor.http.* import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json import java.io.Closeable +import java.time.OffsetDateTime class SecurityService internal constructor( private val baseUrl: String, private val client: HttpClient, ) { suspend fun getAuditEvents( - startTime: String? = null, - endTime: String? = null, + startTime: OffsetDateTime? = null, + endTime: OffsetDateTime? = null, eventKey: AuditEventKey? = null, actorId: String? = null, actorType: String? = null, @@ -30,8 +31,8 @@ class SecurityService internal constructor( cursor: String? = null, ): GetAuditEventsResponse { val response = client.get("$baseUrl/audit_events") { - startTime?.let { parameter("start_time", it) } - endTime?.let { parameter("end_time", it) } + startTime?.let { parameter("start_time", it.toString()) } + endTime?.let { parameter("end_time", it.toString()) } eventKey?.let { parameter("event_key", it.value) } actorId?.let { parameter("actor_id", it) } actorType?.let { parameter("actor_type", it) } @@ -48,8 +49,8 @@ class SecurityService internal constructor( } suspend fun getAuditEventsAll( - startTime: String? = null, - endTime: String? = null, + startTime: OffsetDateTime? = null, + endTime: OffsetDateTime? = null, eventKey: AuditEventKey? = null, actorId: String? = null, actorType: String? = null, @@ -72,8 +73,9 @@ class SecurityService internal constructor( cursor = cursor, ) items.addAll(response.data) - cursor = response.meta?.paginate?.nextPage - } while (cursor != null) + if (response.data.isEmpty()) break + cursor = response.meta.paginate.nextPage + } while (true) return items } } @@ -100,8 +102,9 @@ class BotsService internal constructor( do { val response = getWebhookEvents(limit = limit, cursor = cursor) items.addAll(response.data) - cursor = response.meta?.paginate?.nextPage - } while (cursor != null) + if (response.data.isEmpty()) break + cursor = response.meta.paginate.nextPage + } while (true) return items } @@ -132,19 +135,21 @@ class ChatsService internal constructor( private val client: HttpClient, ) { suspend fun listChats( - sortId: SortOrder? = null, + sort: ChatSortField? = null, + order: SortOrder? = null, availability: ChatAvailability? = null, - lastMessageAtAfter: String? = null, - lastMessageAtBefore: String? = null, + lastMessageAtAfter: OffsetDateTime? = null, + lastMessageAtBefore: OffsetDateTime? = null, personal: Boolean? = null, limit: Int? = null, cursor: String? = null, ): ListChatsResponse { val response = client.get("$baseUrl/chats") { - sortId?.let { parameter("sort[{field}]", it.value) } + sort?.let { parameter("sort", it.value) } + order?.let { parameter("order", it.value) } availability?.let { parameter("availability", it.value) } - lastMessageAtAfter?.let { parameter("last_message_at_after", it) } - lastMessageAtBefore?.let { parameter("last_message_at_before", it) } + lastMessageAtAfter?.let { parameter("last_message_at_after", it.toString()) } + lastMessageAtBefore?.let { parameter("last_message_at_before", it.toString()) } personal?.let { parameter("personal", it) } limit?.let { parameter("limit", it) } cursor?.let { parameter("cursor", it) } @@ -157,10 +162,11 @@ class ChatsService internal constructor( } suspend fun listChatsAll( - sortId: SortOrder? = null, + sort: ChatSortField? = null, + order: SortOrder? = null, availability: ChatAvailability? = null, - lastMessageAtAfter: String? = null, - lastMessageAtBefore: String? = null, + lastMessageAtAfter: OffsetDateTime? = null, + lastMessageAtBefore: OffsetDateTime? = null, personal: Boolean? = null, limit: Int? = null, ): List { @@ -168,7 +174,8 @@ class ChatsService internal constructor( var cursor: String? = null do { val response = listChats( - sortId = sortId, + sort = sort, + order = order, availability = availability, lastMessageAtAfter = lastMessageAtAfter, lastMessageAtBefore = lastMessageAtBefore, @@ -177,8 +184,9 @@ class ChatsService internal constructor( cursor = cursor, ) items.addAll(response.data) - cursor = response.meta?.paginate?.nextPage - } while (cursor != null) + if (response.data.isEmpty()) break + cursor = response.meta.paginate.nextPage + } while (true) return items } @@ -343,8 +351,9 @@ class MembersService internal constructor( cursor = cursor, ) items.addAll(response.data) - cursor = response.meta?.paginate?.nextPage - } while (cursor != null) + if (response.data.isEmpty()) break + cursor = response.meta.paginate.nextPage + } while (true) return items } @@ -421,12 +430,12 @@ class GroupTagsService internal constructor( private val client: HttpClient, ) { suspend fun listTags( - names: TagNamesFilter? = null, + names: List? = null, limit: Int? = null, cursor: String? = null, ): ListTagsResponse { val response = client.get("$baseUrl/group_tags") { - names?.let { parameter("names", it) } + names?.forEach { parameter("names[]", it) } limit?.let { parameter("limit", it) } cursor?.let { parameter("cursor", it) } } @@ -437,14 +446,15 @@ class GroupTagsService internal constructor( } } - suspend fun listTagsAll(names: TagNamesFilter? = null, limit: Int? = null): List { + suspend fun listTagsAll(names: List? = null, limit: Int? = null): List { val items = mutableListOf() var cursor: String? = null do { val response = listTags(names = names, limit = limit, cursor = cursor) items.addAll(response.data) - cursor = response.meta?.paginate?.nextPage - } while (cursor != null) + if (response.data.isEmpty()) break + cursor = response.meta.paginate.nextPage + } while (true) return items } @@ -461,7 +471,7 @@ class GroupTagsService internal constructor( id: Int, limit: Int? = null, cursor: String? = null, - ): ListMembersResponse { + ): GetTagUsersResponse { val response = client.get("$baseUrl/group_tags/$id/users") { limit?.let { parameter("limit", it) } cursor?.let { parameter("cursor", it) } @@ -479,8 +489,9 @@ class GroupTagsService internal constructor( do { val response = getTagUsers(id = id, limit = limit, cursor = cursor) items.addAll(response.data) - cursor = response.meta?.paginate?.nextPage - } while (cursor != null) + if (response.data.isEmpty()) break + cursor = response.meta.paginate.nextPage + } while (true) return items } @@ -524,13 +535,15 @@ class MessagesService internal constructor( ) { suspend fun listChatMessages( chatId: Int, - sortId: SortOrder? = null, + sort: MessageSortField? = null, + order: SortOrder? = null, limit: Int? = null, cursor: String? = null, ): ListChatMessagesResponse { val response = client.get("$baseUrl/messages") { parameter("chat_id", chatId) - sortId?.let { parameter("sort[{field}]", it.value) } + sort?.let { parameter("sort", it.value) } + order?.let { parameter("order", it.value) } limit?.let { parameter("limit", it) } cursor?.let { parameter("cursor", it) } } @@ -543,7 +556,8 @@ class MessagesService internal constructor( suspend fun listChatMessagesAll( chatId: Int, - sortId: SortOrder? = null, + sort: MessageSortField? = null, + order: SortOrder? = null, limit: Int? = null, ): List { val items = mutableListOf() @@ -551,13 +565,15 @@ class MessagesService internal constructor( do { val response = listChatMessages( chatId = chatId, - sortId = sortId, + sort = sort, + order = order, limit = limit, cursor = cursor, ) items.addAll(response.data) - cursor = response.meta?.paginate?.nextPage - } while (cursor != null) + if (response.data.isEmpty()) break + cursor = response.meta.paginate.nextPage + } while (true) return items } @@ -665,8 +681,9 @@ class ReactionsService internal constructor( do { val response = listReactions(id = id, limit = limit, cursor = cursor) items.addAll(response.data) - cursor = response.meta?.paginate?.nextPage - } while (cursor != null) + if (response.data.isEmpty()) break + cursor = response.meta.paginate.nextPage + } while (true) return items } @@ -774,6 +791,22 @@ class ProfileService internal constructor( } } + suspend fun updateProfileAvatar(image: ByteArray): AvatarData { + val response = client.submitFormWithBinaryData( + "$baseUrl/profile/avatar", + formData { + append("image", image, Headers.build { + append(HttpHeaders.ContentDisposition, "filename=\"image\"") + }) + }, + ) + return when (response.status.value) { + 200 -> response.body().data + 401 -> throw response.body() + else -> throw response.body() + } + } + suspend fun updateStatus(request: StatusUpdateRequest): UserStatus { val response = client.put("$baseUrl/profile/status") { contentType(ContentType.Application.Json) @@ -786,6 +819,15 @@ class ProfileService internal constructor( } } + suspend fun deleteProfileAvatar() { + val response = client.delete("$baseUrl/profile/avatar") + when (response.status.value) { + 204 -> return + 401 -> throw response.body() + else -> throw response.body() + } + } + suspend fun deleteStatus() { val response = client.delete("$baseUrl/profile/status") when (response.status.value) { @@ -805,19 +847,19 @@ class SearchService internal constructor( limit: Int? = null, cursor: String? = null, order: SortOrder? = null, - createdFrom: String? = null, - createdTo: String? = null, + createdFrom: OffsetDateTime? = null, + createdTo: OffsetDateTime? = null, active: Boolean? = null, chatSubtype: ChatSubtype? = null, personal: Boolean? = null, - ): ListChatsResponse { + ): SearchChatsResponse { val response = client.get("$baseUrl/search/chats") { query?.let { parameter("query", it) } limit?.let { parameter("limit", it) } cursor?.let { parameter("cursor", it) } order?.let { parameter("order", it.value) } - createdFrom?.let { parameter("created_from", it) } - createdTo?.let { parameter("created_to", it) } + createdFrom?.let { parameter("created_from", it.toString()) } + createdTo?.let { parameter("created_to", it.toString()) } active?.let { parameter("active", it) } chatSubtype?.let { parameter("chat_subtype", it.value) } personal?.let { parameter("personal", it) } @@ -833,8 +875,8 @@ class SearchService internal constructor( query: String? = null, limit: Int? = null, order: SortOrder? = null, - createdFrom: String? = null, - createdTo: String? = null, + createdFrom: OffsetDateTime? = null, + createdTo: OffsetDateTime? = null, active: Boolean? = null, chatSubtype: ChatSubtype? = null, personal: Boolean? = null, @@ -854,8 +896,9 @@ class SearchService internal constructor( personal = personal, ) items.addAll(response.data) - cursor = response.meta?.paginate?.nextPage - } while (cursor != null) + if (response.data.isEmpty()) break + cursor = response.meta.paginate.nextPage + } while (true) return items } @@ -864,21 +907,21 @@ class SearchService internal constructor( limit: Int? = null, cursor: String? = null, order: SortOrder? = null, - createdFrom: String? = null, - createdTo: String? = null, + createdFrom: OffsetDateTime? = null, + createdTo: OffsetDateTime? = null, chatIds: List? = null, userIds: List? = null, active: Boolean? = null, - ): ListChatMessagesResponse { + ): SearchMessagesResponse { val response = client.get("$baseUrl/search/messages") { query?.let { parameter("query", it) } limit?.let { parameter("limit", it) } cursor?.let { parameter("cursor", it) } order?.let { parameter("order", it.value) } - createdFrom?.let { parameter("created_from", it) } - createdTo?.let { parameter("created_to", it) } - chatIds?.let { parameter("chat_ids", it) } - userIds?.let { parameter("user_ids", it) } + createdFrom?.let { parameter("created_from", it.toString()) } + createdTo?.let { parameter("created_to", it.toString()) } + chatIds?.forEach { parameter("chat_ids[]", it) } + userIds?.forEach { parameter("user_ids[]", it) } active?.let { parameter("active", it) } } return when (response.status.value) { @@ -892,8 +935,8 @@ class SearchService internal constructor( query: String? = null, limit: Int? = null, order: SortOrder? = null, - createdFrom: String? = null, - createdTo: String? = null, + createdFrom: OffsetDateTime? = null, + createdTo: OffsetDateTime? = null, chatIds: List? = null, userIds: List? = null, active: Boolean? = null, @@ -913,8 +956,9 @@ class SearchService internal constructor( active = active, ) items.addAll(response.data) - cursor = response.meta?.paginate?.nextPage - } while (cursor != null) + if (response.data.isEmpty()) break + cursor = response.meta.paginate.nextPage + } while (true) return items } @@ -924,19 +968,19 @@ class SearchService internal constructor( cursor: String? = null, sort: SearchSortOrder? = null, order: SortOrder? = null, - createdFrom: String? = null, - createdTo: String? = null, + createdFrom: OffsetDateTime? = null, + createdTo: OffsetDateTime? = null, companyRoles: List? = null, - ): ListMembersResponse { + ): SearchUsersResponse { val response = client.get("$baseUrl/search/users") { query?.let { parameter("query", it) } limit?.let { parameter("limit", it) } cursor?.let { parameter("cursor", it) } sort?.let { parameter("sort", it.value) } order?.let { parameter("order", it.value) } - createdFrom?.let { parameter("created_from", it) } - createdTo?.let { parameter("created_to", it) } - companyRoles?.let { parameter("company_roles", it) } + createdFrom?.let { parameter("created_from", it.toString()) } + createdTo?.let { parameter("created_to", it.toString()) } + companyRoles?.forEach { parameter("company_roles[]", it.value) } } return when (response.status.value) { 200 -> response.body() @@ -950,8 +994,8 @@ class SearchService internal constructor( limit: Int? = null, sort: SearchSortOrder? = null, order: SortOrder? = null, - createdFrom: String? = null, - createdTo: String? = null, + createdFrom: OffsetDateTime? = null, + createdTo: OffsetDateTime? = null, companyRoles: List? = null, ): List { val items = mutableListOf() @@ -968,8 +1012,9 @@ class SearchService internal constructor( companyRoles = companyRoles, ) items.addAll(response.data) - cursor = response.meta?.paginate?.nextPage - } while (cursor != null) + if (response.data.isEmpty()) break + cursor = response.meta.paginate.nextPage + } while (true) return items } } @@ -996,8 +1041,9 @@ class TasksService internal constructor( do { val response = listTasks(limit = limit, cursor = cursor) items.addAll(response.data) - cursor = response.meta?.paginate?.nextPage - } while (cursor != null) + if (response.data.isEmpty()) break + cursor = response.meta.paginate.nextPage + } while (true) return items } @@ -1052,7 +1098,7 @@ class UsersService internal constructor( query: String? = null, limit: Int? = null, cursor: String? = null, - ): ListMembersResponse { + ): ListUsersResponse { val response = client.get("$baseUrl/users") { query?.let { parameter("query", it) } limit?.let { parameter("limit", it) } @@ -1071,8 +1117,9 @@ class UsersService internal constructor( do { val response = listUsers(query = query, limit = limit, cursor = cursor) items.addAll(response.data) - cursor = response.meta?.paginate?.nextPage - } while (cursor != null) + if (response.data.isEmpty()) break + cursor = response.meta.paginate.nextPage + } while (true) return items } @@ -1118,6 +1165,22 @@ class UsersService internal constructor( } } + suspend fun updateUserAvatar(userId: Int, image: ByteArray): AvatarData { + val response = client.submitFormWithBinaryData( + "$baseUrl/users/$userId/avatar", + formData { + append("image", image, Headers.build { + append(HttpHeaders.ContentDisposition, "filename=\"image\"") + }) + }, + ) + return when (response.status.value) { + 200 -> response.body().data + 401 -> throw response.body() + else -> throw response.body() + } + } + suspend fun updateUserStatus(userId: Int, request: StatusUpdateRequest): UserStatus { val response = client.put("$baseUrl/users/$userId/status") { contentType(ContentType.Application.Json) @@ -1139,6 +1202,15 @@ class UsersService internal constructor( } } + suspend fun deleteUserAvatar(userId: Int) { + val response = client.delete("$baseUrl/users/$userId/avatar") + when (response.status.value) { + 204 -> return + 401 -> throw response.body() + else -> throw response.body() + } + } + suspend fun deleteUserStatus(userId: Int) { val response = client.delete("$baseUrl/users/$userId/status") when (response.status.value) { diff --git a/sdk/kotlin/generated/src/main/kotlin/com/pachca/Models.kt b/sdk/kotlin/generated/src/main/kotlin/com/pachca/Models.kt index 2080b559..99fd3cac 100644 --- a/sdk/kotlin/generated/src/main/kotlin/com/pachca/Models.kt +++ b/sdk/kotlin/generated/src/main/kotlin/com/pachca/Models.kt @@ -1,8 +1,21 @@ package com.pachca.sdk +import java.time.OffsetDateTime +import java.time.format.DateTimeFormatter +import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +object OffsetDateTimeSerializer : KSerializer { + override val descriptor = PrimitiveSerialDescriptor("OffsetDateTime", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: OffsetDateTime) = encoder.encodeString(value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)) + override fun deserialize(decoder: Decoder): OffsetDateTime = OffsetDateTime.parse(decoder.decodeString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME) +} /** Тип аудит-события */ @Serializable @@ -114,6 +127,15 @@ enum class ChatMemberRoleFilter(val value: String) { @SerialName("member") MEMBER("member"), } +/** Поле сортировки чатов */ +@Serializable +enum class ChatSortField(val value: String) { + /** По идентификатору чата */ + @SerialName("id") ID("id"), + /** По дате и времени создания последнего сообщения */ + @SerialName("last_message_at") LAST_MESSAGE_AT("last_message_at"), +} + /** Тип чата */ @Serializable enum class ChatSubtype(val value: String) { @@ -174,6 +196,12 @@ enum class MessageEntityType(val value: String) { @SerialName("user") USER("user"), } +@Serializable +enum class MessageSortField(val value: String) { + /** По идентификатору сообщения */ + @SerialName("id") ID("id"), +} + /** Скоуп доступа OAuth токена */ @Serializable enum class OAuthScope(val value: String) { @@ -235,10 +263,14 @@ enum class OAuthScope(val value: String) { @SerialName("profile_status:read") PROFILE_STATUS_READ("profile_status:read"), /** Изменение и удаление статуса профиля */ @SerialName("profile_status:write") PROFILE_STATUS_WRITE("profile_status:write"), + /** Изменение и удаление аватара профиля */ + @SerialName("profile_avatar:write") PROFILE_AVATAR_WRITE("profile_avatar:write"), /** Просмотр статуса сотрудника */ @SerialName("user_status:read") USER_STATUS_READ("user_status:read"), /** Изменение и удаление статуса сотрудника */ @SerialName("user_status:write") USER_STATUS_WRITE("user_status:write"), + /** Изменение и удаление аватара сотрудника */ + @SerialName("user_avatar:write") USER_AVATAR_WRITE("user_avatar:write"), /** Просмотр дополнительных полей */ @SerialName("custom_properties:read") CUSTOM_PROPERTIES_READ("custom_properties:read"), /** Просмотр журнала аудита */ @@ -711,7 +743,7 @@ data class MessageWebhookPayload( @SerialName("entity_id") val entityId: Int, val content: String, @SerialName("user_id") val userId: Int, - @SerialName("created_at") val createdAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("created_at") val createdAt: OffsetDateTime, val url: String, @SerialName("chat_id") val chatId: Int, @SerialName("parent_message_id") val parentMessageId: Int? = null, @@ -728,7 +760,7 @@ data class ReactionWebhookPayload( val code: String, val name: String, @SerialName("user_id") val userId: Int, - @SerialName("created_at") val createdAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("created_at") val createdAt: OffsetDateTime, @SerialName("webhook_timestamp") val webhookTimestamp: Int, ) : WebhookPayloadUnion @@ -744,6 +776,17 @@ data class ButtonWebhookPayload( @SerialName("webhook_timestamp") val webhookTimestamp: Int, ) : WebhookPayloadUnion +@Serializable +@SerialName("view") +data class ViewSubmitWebhookPayload( + override val type: String = "view", + @SerialName("callback_id") val callbackId: String, + @SerialName("private_metadata") val privateMetadata: String, + @SerialName("user_id") val userId: Int, + val data: Map, + @SerialName("webhook_timestamp") val webhookTimestamp: Int, +) : WebhookPayloadUnion + @Serializable @SerialName("chat_member") data class ChatMemberWebhookPayload( @@ -752,7 +795,7 @@ data class ChatMemberWebhookPayload( @SerialName("chat_id") val chatId: Int, @SerialName("thread_id") val threadId: Int? = null, @SerialName("user_ids") val userIds: List, - @SerialName("created_at") val createdAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("created_at") val createdAt: OffsetDateTime, @SerialName("webhook_timestamp") val webhookTimestamp: Int, ) : WebhookPayloadUnion @@ -762,7 +805,7 @@ data class CompanyMemberWebhookPayload( override val type: String = "company_member", val event: UserEventType, @SerialName("user_ids") val userIds: List, - @SerialName("created_at") val createdAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("created_at") val createdAt: OffsetDateTime, @SerialName("webhook_timestamp") val webhookTimestamp: Int, ) : WebhookPayloadUnion @@ -774,7 +817,7 @@ data class LinkSharedWebhookPayload( @SerialName("message_id") val messageId: Int, val links: List, @SerialName("user_id") val userId: Int, - @SerialName("created_at") val createdAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("created_at") val createdAt: OffsetDateTime, @SerialName("webhook_timestamp") val webhookTimestamp: Int, ) : WebhookPayloadUnion @@ -785,10 +828,10 @@ data class AccessTokenInfo( val name: String? = null, @SerialName("user_id") val userId: Long, val scopes: List, - @SerialName("created_at") val createdAt: String, - @SerialName("revoked_at") val revokedAt: String? = null, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("created_at") val createdAt: OffsetDateTime, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("revoked_at") val revokedAt: OffsetDateTime? = null, @SerialName("expires_in") val expiresIn: Int? = null, - @SerialName("last_used_at") val lastUsedAt: String? = null, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("last_used_at") val lastUsedAt: OffsetDateTime? = null, ) @Serializable @@ -819,7 +862,7 @@ data class ApiErrorItem( @Serializable data class AuditEvent( val id: String, - @SerialName("created_at") val createdAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("created_at") val createdAt: OffsetDateTime, @SerialName("event_key") val eventKey: AuditEventKey, @SerialName("entity_id") val entityId: String, @SerialName("entity_type") val entityType: String, @@ -830,6 +873,11 @@ data class AuditEvent( @SerialName("user_agent") val userAgent: String, ) +@Serializable +data class AvatarData( + @SerialName("image_url") val imageUrl: String, +) + @Serializable data class BotResponseWebhook( @SerialName("outgoing_url") val outgoingUrl: String, @@ -867,14 +915,14 @@ data class Button( data class Chat( val id: Int, val name: String, - @SerialName("created_at") val createdAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("created_at") val createdAt: OffsetDateTime, @SerialName("owner_id") val ownerId: Int, @SerialName("member_ids") val memberIds: List, @SerialName("group_tag_ids") val groupTagIds: List, val channel: Boolean, val personal: Boolean, val public: Boolean, - @SerialName("last_message_at") val lastMessageAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("last_message_at") val lastMessageAt: OffsetDateTime, @SerialName("meet_room_url") val meetRoomUrl: String, ) @@ -956,7 +1004,7 @@ data class Forwarding( @SerialName("original_message_id") val originalMessageId: Int, @SerialName("original_chat_id") val originalChatId: Int, @SerialName("author_id") val authorId: Int, - @SerialName("original_created_at") val originalCreatedAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("original_created_at") val originalCreatedAt: OffsetDateTime, @SerialName("original_thread_id") val originalThreadId: Int? = null, @SerialName("original_thread_message_id") val originalThreadMessageId: Int? = null, @SerialName("original_thread_parent_chat_id") val originalThreadParentChatId: Int? = null, @@ -1014,7 +1062,7 @@ data class Message( @SerialName("root_chat_id") val rootChatId: Int, val content: String, @SerialName("user_id") val userId: Int, - @SerialName("created_at") val createdAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("created_at") val createdAt: OffsetDateTime, val url: String, val files: List, val buttons: List>? = null, @@ -1023,8 +1071,8 @@ data class Message( @SerialName("parent_message_id") val parentMessageId: Int? = null, @SerialName("display_avatar_url") val displayAvatarUrl: String? = null, @SerialName("display_name") val displayName: String? = null, - @SerialName("changed_at") val changedAt: String? = null, - @SerialName("deleted_at") val deletedAt: String? = null, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("changed_at") val changedAt: OffsetDateTime? = null, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("deleted_at") val deletedAt: OffsetDateTime? = null, ) @Serializable @@ -1105,18 +1153,18 @@ data class OpenViewRequest( @Serializable data class PaginationMetaPaginate( - @SerialName("next_page") val nextPage: String? = null, + @SerialName("next_page") val nextPage: String, ) @Serializable data class PaginationMeta( - val paginate: PaginationMetaPaginate? = null, + val paginate: PaginationMetaPaginate, ) @Serializable data class Reaction( @SerialName("user_id") val userId: Int, - @SerialName("created_at") val createdAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("created_at") val createdAt: OffsetDateTime, val code: String, val name: String? = null, ) @@ -1142,7 +1190,7 @@ data class SearchPaginationMeta( data class StatusUpdateRequestStatus( val emoji: String, val title: String, - @SerialName("expires_at") val expiresAt: String? = null, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("expires_at") val expiresAt: OffsetDateTime? = null, @SerialName("is_away") val isAway: Boolean? = null, @SerialName("away_message") val awayMessage: String? = null, ) @@ -1152,20 +1200,17 @@ data class StatusUpdateRequest( val status: StatusUpdateRequestStatus, ) -@Serializable -class TagNamesFilter - @Serializable data class Task( val id: Int, val kind: TaskKind, val content: String, - @SerialName("due_at") val dueAt: String? = null, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("due_at") val dueAt: OffsetDateTime? = null, val priority: Int, @SerialName("user_id") val userId: Int, @SerialName("chat_id") val chatId: Int? = null, val status: TaskStatus, - @SerialName("created_at") val createdAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("created_at") val createdAt: OffsetDateTime, @SerialName("performer_ids") val performerIds: List, @SerialName("all_day") val allDay: Boolean, @SerialName("custom_properties") val customProperties: List, @@ -1181,7 +1226,7 @@ data class TaskCreateRequestCustomProperty( data class TaskCreateRequestTask( val kind: TaskKind, val content: String? = null, - @SerialName("due_at") val dueAt: String? = null, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("due_at") val dueAt: OffsetDateTime? = null, val priority: Int? = 1, @SerialName("performer_ids") val performerIds: List? = null, @SerialName("chat_id") val chatId: Int? = null, @@ -1204,12 +1249,12 @@ data class TaskUpdateRequestCustomProperty( data class TaskUpdateRequestTask( val kind: TaskKind? = null, val content: String? = null, - @SerialName("due_at") val dueAt: String? = null, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("due_at") val dueAt: OffsetDateTime? = null, val priority: Int? = null, @SerialName("performer_ids") val performerIds: List? = null, val status: TaskStatus? = null, @SerialName("all_day") val allDay: Boolean? = null, - @SerialName("done_at") val doneAt: String? = null, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("done_at") val doneAt: OffsetDateTime? = null, @SerialName("custom_properties") val customProperties: List? = null, ) @@ -1224,7 +1269,7 @@ data class Thread( @SerialName("chat_id") val chatId: Long, @SerialName("message_id") val messageId: Long, @SerialName("message_chat_id") val messageChatId: Long, - @SerialName("updated_at") val updatedAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("updated_at") val updatedAt: OffsetDateTime, ) @Serializable @@ -1263,8 +1308,8 @@ data class User( @SerialName("user_status") val userStatus: UserStatus? = null, val bot: Boolean, val sso: Boolean, - @SerialName("created_at") val createdAt: String, - @SerialName("last_activity_at") val lastActivityAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("created_at") val createdAt: OffsetDateTime, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("last_activity_at") val lastActivityAt: OffsetDateTime, @SerialName("time_zone") val timeZone: String, @SerialName("image_url") val imageUrl: String? = null, ) @@ -1305,7 +1350,7 @@ data class UserStatusAwayMessage( data class UserStatus( val emoji: String, val title: String, - @SerialName("expires_at") val expiresAt: String? = null, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("expires_at") val expiresAt: OffsetDateTime? = null, @SerialName("is_away") val isAway: Boolean, @SerialName("away_message") val awayMessage: UserStatusAwayMessage? = null, ) @@ -1342,7 +1387,7 @@ data class ViewBlock( val text: String? = null, val name: String? = null, val label: String? = null, - @SerialName("initial_date") val initialDate: String? = null, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("initial_date") val initialDate: OffsetDateTime? = null, ) @Serializable @@ -1366,7 +1411,7 @@ data class WebhookEvent( val id: String, @SerialName("event_type") val eventType: String, val payload: WebhookPayloadUnion, - @SerialName("created_at") val createdAt: String, + @Serializable(with = OffsetDateTimeSerializer::class) @SerialName("created_at") val createdAt: OffsetDateTime, ) @Serializable @@ -1381,22 +1426,32 @@ data class WebhookMessageThread( @SerialName("message_chat_id") val messageChatId: Int, ) +@Serializable +data class UpdateProfileAvatarRequest( + @Transient val image: ByteArray = ByteArray(0), +) + +@Serializable +data class UpdateUserAvatarRequest( + @Transient val image: ByteArray = ByteArray(0), +) + @Serializable data class GetAuditEventsResponse( val data: List, - val meta: PaginationMeta? = null, + val meta: PaginationMeta, ) @Serializable data class ListChatsResponse( val data: List, - val meta: PaginationMeta? = null, + val meta: PaginationMeta, ) @Serializable data class ListMembersResponse( val data: List, - val meta: PaginationMeta? = null, + val meta: PaginationMeta, ) @Serializable @@ -1407,25 +1462,25 @@ data class ListPropertiesResponse( @Serializable data class ListTagsResponse( val data: List, - val meta: PaginationMeta? = null, + val meta: PaginationMeta, ) @Serializable data class GetTagUsersResponse( val data: List, - val meta: PaginationMeta? = null, + val meta: PaginationMeta, ) @Serializable data class ListChatMessagesResponse( val data: List, - val meta: PaginationMeta? = null, + val meta: PaginationMeta, ) @Serializable data class ListReactionsResponse( val data: List, - val meta: PaginationMeta? = null, + val meta: PaginationMeta, ) @Serializable @@ -1449,19 +1504,19 @@ data class SearchUsersResponse( @Serializable data class ListTasksResponse( val data: List, - val meta: PaginationMeta? = null, + val meta: PaginationMeta, ) @Serializable data class ListUsersResponse( val data: List, - val meta: PaginationMeta? = null, + val meta: PaginationMeta, ) @Serializable data class GetWebhookEventsResponse( val data: List, - val meta: PaginationMeta? = null, + val meta: PaginationMeta, ) @Serializable @@ -1485,6 +1540,9 @@ data class AccessTokenInfoDataWrapper(val data: AccessTokenInfo) @Serializable data class UserDataWrapper(val data: User) +@Serializable +data class AvatarDataDataWrapper(val data: AvatarData) + @Serializable data class UserStatusDataWrapper(val data: UserStatus) diff --git a/sdk/kotlin/generated/src/main/kotlin/com/pachca/examples.json b/sdk/kotlin/generated/src/main/kotlin/com/pachca/examples.json index 31e73649..6db478c1 100644 --- a/sdk/kotlin/generated/src/main/kotlin/com/pachca/examples.json +++ b/sdk/kotlin/generated/src/main/kotlin/com/pachca/examples.json @@ -6,15 +6,15 @@ ] }, "SecurityOperations_getAuditEvents": { - "usage": "val response = client.security.getAuditEvents(startTime = \"2025-05-01T09:11:00Z\", endTime = \"2025-05-02T09:11:00Z\", eventKey = AuditEventKey.USER_LOGIN, actorId = \"98765\", actorType = \"User\", entityId = \"98765\", entityType = \"User\", limit = 1, cursor = \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", - "output": "GetAuditEventsResponse(data: List, meta: PaginationMeta?)", + "usage": "val startTime = OffsetDateTime.parse(\"2025-05-01T09:11:00Z\")\nval endTime = OffsetDateTime.parse(\"2025-05-02T09:11:00Z\")\nval response = client.security.getAuditEvents(startTime = startTime, endTime = endTime, eventKey = AuditEventKey.USER_LOGIN, actorId = \"98765\", actorType = \"User\", entityId = \"98765\", entityType = \"User\", limit = 1, cursor = \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", + "output": "GetAuditEventsResponse(data: List, meta: PaginationMeta)", "imports": [ "AuditEventKey" ] }, "BotOperations_getWebhookEvents": { "usage": "val response = client.bots.getWebhookEvents(limit = 1, cursor = \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", - "output": "GetWebhookEventsResponse(data: List, meta: PaginationMeta?)" + "output": "GetWebhookEventsResponse(data: List, meta: PaginationMeta)" }, "BotOperations_updateBot": { "usage": "val request = BotUpdateRequest(bot = BotUpdateRequestBot(webhook = BotUpdateRequestBotWebhook(outgoingUrl = \"https://www.website.com/tasks/new\")))\nval response = client.bots.updateBot(id = 1738816, request = request)", @@ -29,20 +29,21 @@ "usage": "client.bots.deleteWebhookEvent(id = \"01KAJZ2XDSS2S3DSW9EXJZ0TBV\")" }, "ChatOperations_listChats": { - "usage": "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\")", - "output": "ListChatsResponse(data: List, meta: PaginationMeta?)", + "usage": "val lastMessageAtAfter = OffsetDateTime.parse(\"2025-01-01T00:00:00.000Z\")\nval lastMessageAtBefore = OffsetDateTime.parse(\"2025-02-01T00:00:00.000Z\")\nval response = client.chats.listChats(sort = ChatSortField.ID, order = SortOrder.DESC, availability = ChatAvailability.IS_MEMBER, lastMessageAtAfter = lastMessageAtAfter, lastMessageAtBefore = lastMessageAtBefore, personal = false, limit = 1, cursor = \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", + "output": "ListChatsResponse(data: List, meta: PaginationMeta)", "imports": [ "ChatAvailability", + "ChatSortField", "SortOrder" ] }, "ChatOperations_getChat": { "usage": "val response = client.chats.getChat(id = 334)", - "output": "Chat(id: Int, name: String, createdAt: String, ownerId: Int, memberIds: List, groupTagIds: List, channel: Boolean, personal: Boolean, public: Boolean, lastMessageAt: String, meetRoomUrl: String)" + "output": "Chat(id: Int, name: String, createdAt: OffsetDateTime, ownerId: Int, memberIds: List, groupTagIds: List, channel: Boolean, personal: Boolean, public: Boolean, lastMessageAt: OffsetDateTime, meetRoomUrl: String)" }, "ChatOperations_createChat": { "usage": "val request = ChatCreateRequest(\n chat = ChatCreateRequestChat(\n name = \"🤿 aqua\",\n memberIds = listOf(123),\n groupTagIds = listOf(123),\n channel = true,\n public = false\n )\n)\nval response = client.chats.createChat(request = request)", - "output": "Chat(id: Int, name: String, createdAt: String, ownerId: Int, memberIds: List, groupTagIds: List, channel: Boolean, personal: Boolean, public: Boolean, lastMessageAt: String, meetRoomUrl: String)", + "output": "Chat(id: Int, name: String, createdAt: OffsetDateTime, ownerId: Int, memberIds: List, groupTagIds: List, channel: Boolean, personal: Boolean, public: Boolean, lastMessageAt: OffsetDateTime, meetRoomUrl: String)", "imports": [ "ChatCreateRequest", "ChatCreateRequestChat" @@ -50,7 +51,7 @@ }, "ChatOperations_updateChat": { "usage": "val request = ChatUpdateRequest(chat = ChatUpdateRequestChat(name = \"Бассейн\", public = true))\nval response = client.chats.updateChat(id = 334, request = request)", - "output": "Chat(id: Int, name: String, createdAt: String, ownerId: Int, memberIds: List, groupTagIds: List, channel: Boolean, personal: Boolean, public: Boolean, lastMessageAt: String, meetRoomUrl: String)", + "output": "Chat(id: Int, name: String, createdAt: OffsetDateTime, ownerId: Int, memberIds: List, groupTagIds: List, channel: Boolean, personal: Boolean, public: Boolean, lastMessageAt: OffsetDateTime, meetRoomUrl: String)", "imports": [ "ChatUpdateRequest", "ChatUpdateRequestChat" @@ -91,7 +92,7 @@ }, "ChatMemberOperations_listMembers": { "usage": "val response = client.members.listMembers(id = 334, role = ChatMemberRoleFilter.ALL, limit = 1, cursor = \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", - "output": "ListMembersResponse(data: List, meta: PaginationMeta?)", + "output": "ListMembersResponse(data: List, meta: PaginationMeta)", "imports": [ "ChatMemberRoleFilter" ] @@ -121,11 +122,8 @@ "usage": "client.members.removeMember(id = 334, userId = 186)" }, "GroupTagOperations_listTags": { - "usage": "val names = TagNamesFilter()\nval response = client.groupTags.listTags(names = names, limit = 1, cursor = \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", - "output": "ListTagsResponse(data: List, meta: PaginationMeta?)", - "imports": [ - "TagNamesFilter" - ] + "usage": "val names = listOf(\"example\")\nval response = client.groupTags.listTags(names = names, limit = 1, cursor = \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", + "output": "ListTagsResponse(data: List, meta: PaginationMeta)" }, "GroupTagOperations_getTag": { "usage": "val response = client.groupTags.getTag(id = 9111)", @@ -133,7 +131,7 @@ }, "GroupTagOperations_getTagUsers": { "usage": "val response = client.groupTags.getTagUsers(id = 9111, limit = 1, cursor = \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", - "output": "GetTagUsersResponse(data: List, meta: PaginationMeta?)" + "output": "GetTagUsersResponse(data: List, meta: PaginationMeta)" }, "GroupTagOperations_createTag": { "usage": "val request = GroupTagRequest(groupTag = GroupTagRequestGroupTag(name = \"Новое название тега\"))\nval response = client.groupTags.createTag(request = request)", @@ -155,19 +153,20 @@ "usage": "client.groupTags.deleteTag(id = 9111)" }, "ChatMessageOperations_listChatMessages": { - "usage": "val response = client.messages.listChatMessages(chatId = 198, sortId = SortOrder.DESC, limit = 1, cursor = \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", - "output": "ListChatMessagesResponse(data: List, meta: PaginationMeta?)", + "usage": "val response = client.messages.listChatMessages(chatId = 198, sort = MessageSortField.ID, order = SortOrder.DESC, limit = 1, cursor = \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", + "output": "ListChatMessagesResponse(data: List, meta: PaginationMeta)", "imports": [ + "MessageSortField", "SortOrder" ] }, "MessageOperations_getMessage": { "usage": "val response = client.messages.getMessage(id = 194275)", - "output": "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?)" + "output": "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?)" }, "MessageOperations_createMessage": { "usage": "val request = MessageCreateRequest(\n message = MessageCreateRequestMessage(\n entityType = MessageEntityType.DISCUSSION,\n entityId = 334,\n content = \"Вчера мы продали 756 футболок (что на 10% больше, чем в прошлое воскресенье)\",\n files = listOf(MessageCreateRequestFile(\n key = \"attaches/files/93746/e354fd79-4f3e-4b5a-9c8d-1a2b3c4d5e6f/logo.png\",\n name = \"logo.png\",\n fileType = FileType.IMAGE,\n size = 12345,\n width = 800,\n height = 600\n )),\n buttons = listOf(listOf(Button(\n text = \"Подробнее\",\n url = \"https://example.com/details\",\n data = \"awesome\"\n ))),\n parentMessageId = 194270,\n displayAvatarUrl = \"https://example.com/avatar.png\",\n displayName = \"Бот Поддержки\",\n skipInviteMentions = false\n ),\n linkPreview = false\n)\nval response = client.messages.createMessage(request = request)", - "output": "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?)", + "output": "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?)", "imports": [ "Button", "FileType", @@ -182,7 +181,7 @@ }, "MessageOperations_updateMessage": { "usage": "val request = MessageUpdateRequest(\n message = MessageUpdateRequestMessage(\n content = \"Вот попробуйте написать правильно это с первого раза: Будущий, Полощи, Прийти, Грейпфрут, Мозаика, Бюллетень, Дуршлаг, Винегрет.\",\n files = listOf(MessageUpdateRequestFile(\n key = \"attaches/files/93746/e354fd79-4f3e-4b5a-9c8d-1a2b3c4d5e6f/logo.png\",\n name = \"logo.png\",\n fileType = \"image\",\n size = 12345,\n width = 800,\n height = 600\n )),\n buttons = listOf(listOf(Button(\n text = \"Подробнее\",\n url = \"https://example.com/details\",\n data = \"awesome\"\n ))),\n displayAvatarUrl = \"https://example.com/avatar.png\",\n displayName = \"Бот Поддержки\"\n )\n)\nval response = client.messages.updateMessage(id = 194275, request = request)", - "output": "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?)", + "output": "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?)", "imports": [ "Button", "MessageUpdateRequest", @@ -206,11 +205,11 @@ }, "ReactionOperations_listReactions": { "usage": "val response = client.reactions.listReactions(id = 194275, limit = 1, cursor = \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", - "output": "ListReactionsResponse(data: List, meta: PaginationMeta?)" + "output": "ListReactionsResponse(data: List, meta: PaginationMeta)" }, "ReactionOperations_addReaction": { "usage": "val request = ReactionRequest(code = \"👍\", name = \":+1:\")\nval response = client.reactions.addReaction(id = 7231942, request = request)", - "output": "Reaction(userId: Int, createdAt: String, code: String, name: String?)", + "output": "Reaction(userId: Int, createdAt: OffsetDateTime, code: String, name: String?)", "imports": [ "ReactionRequest" ] @@ -224,37 +223,44 @@ }, "ThreadOperations_getThread": { "usage": "val response = client.threads.getThread(id = 265142)", - "output": "Thread(id: Long, chatId: Long, messageId: Long, messageChatId: Long, updatedAt: String)" + "output": "Thread(id: Long, chatId: Long, messageId: Long, messageChatId: Long, updatedAt: OffsetDateTime)" }, "ThreadOperations_createThread": { "usage": "val response = client.threads.createThread(id = 154332686)", - "output": "Thread(id: Long, chatId: Long, messageId: Long, messageChatId: Long, updatedAt: String)" + "output": "Thread(id: Long, chatId: Long, messageId: Long, messageChatId: Long, updatedAt: OffsetDateTime)" }, "OAuthOperations_getTokenInfo": { "usage": "val response = client.profile.getTokenInfo()", - "output": "AccessTokenInfo(id: Long, token: String, name: String?, userId: Long, scopes: List, createdAt: String, revokedAt: String?, expiresIn: Int?, lastUsedAt: String?)" + "output": "AccessTokenInfo(id: Long, token: String, name: String?, userId: Long, scopes: List, createdAt: OffsetDateTime, revokedAt: OffsetDateTime?, expiresIn: Int?, lastUsedAt: OffsetDateTime?)" }, "ProfileOperations_getProfile": { "usage": "val response = client.profile.getProfile()", - "output": "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?)" + "output": "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?)" }, "ProfileOperations_getStatus": { "usage": "val response = client.profile.getStatus()", "output": "Any" }, + "ProfileAvatarOperations_updateProfileAvatar": { + "usage": "val image = ByteArray(0)\nval response = client.profile.updateProfileAvatar(image = image)", + "output": "AvatarData(imageUrl: String)" + }, "ProfileOperations_updateStatus": { - "usage": "val request = StatusUpdateRequest(\n status = StatusUpdateRequestStatus(\n emoji = \"🎮\",\n title = \"Очень занят\",\n expiresAt = \"2024-04-08T10:00:00.000Z\",\n isAway = true,\n awayMessage = \"Вернусь после 15:00\"\n )\n)\nval response = client.profile.updateStatus(request = request)", - "output": "UserStatus(emoji: String, title: String, expiresAt: String?, isAway: Boolean, awayMessage: UserStatusAwayMessage(text: String)?)", + "usage": "val request = StatusUpdateRequest(\n status = StatusUpdateRequestStatus(\n emoji = \"🎮\",\n title = \"Очень занят\",\n expiresAt = OffsetDateTime.parse(\"2024-04-08T10:00:00.000Z\"),\n isAway = true,\n awayMessage = \"Вернусь после 15:00\"\n )\n)\nval response = client.profile.updateStatus(request = request)", + "output": "UserStatus(emoji: String, title: String, expiresAt: OffsetDateTime?, isAway: Boolean, awayMessage: UserStatusAwayMessage(text: String)?)", "imports": [ "StatusUpdateRequest", "StatusUpdateRequestStatus" ] }, + "ProfileAvatarOperations_deleteProfileAvatar": { + "usage": "client.profile.deleteProfileAvatar()" + }, "ProfileOperations_deleteStatus": { "usage": "client.profile.deleteStatus()" }, "SearchOperations_searchChats": { - "usage": "val response = client.search.searchChats(query = \"Разработка\", limit = 10, cursor = \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\", order = SortOrder.DESC, createdFrom = \"2025-01-01T00:00:00.000Z\", createdTo = \"2025-02-01T00:00:00.000Z\", active = true, chatSubtype = ChatSubtype.DISCUSSION, personal = false)", + "usage": "val createdFrom = OffsetDateTime.parse(\"2025-01-01T00:00:00.000Z\")\nval createdTo = OffsetDateTime.parse(\"2025-02-01T00:00:00.000Z\")\nval response = client.search.searchChats(query = \"Разработка\", limit = 10, cursor = \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\", order = SortOrder.DESC, createdFrom = createdFrom, createdTo = createdTo, active = true, chatSubtype = ChatSubtype.DISCUSSION, personal = false)", "output": "SearchChatsResponse(data: List, meta: SearchPaginationMeta)", "imports": [ "ChatSubtype", @@ -262,14 +268,14 @@ ] }, "SearchOperations_searchMessages": { - "usage": "val chatIds = listOf(123)\nval userIds = listOf(123)\nval response = client.search.searchMessages(query = \"футболки\", limit = 10, cursor = \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\", order = SortOrder.DESC, createdFrom = \"2025-01-01T00:00:00.000Z\", createdTo = \"2025-02-01T00:00:00.000Z\", chatIds = chatIds, userIds = userIds, active = true)", + "usage": "val createdFrom = OffsetDateTime.parse(\"2025-01-01T00:00:00.000Z\")\nval createdTo = OffsetDateTime.parse(\"2025-02-01T00:00:00.000Z\")\nval chatIds = listOf(123)\nval userIds = listOf(123)\nval response = client.search.searchMessages(query = \"футболки\", limit = 10, cursor = \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\", order = SortOrder.DESC, createdFrom = createdFrom, createdTo = createdTo, chatIds = chatIds, userIds = userIds, active = true)", "output": "SearchMessagesResponse(data: List, meta: SearchPaginationMeta)", "imports": [ "SortOrder" ] }, "SearchOperations_searchUsers": { - "usage": "val companyRoles = listOf(UserRole.ADMIN)\nval response = client.search.searchUsers(query = \"Олег\", limit = 10, cursor = \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\", sort = SearchSortOrder.BY_SCORE, order = SortOrder.DESC, createdFrom = \"2025-01-01T00:00:00.000Z\", createdTo = \"2025-02-01T00:00:00.000Z\", companyRoles = companyRoles)", + "usage": "val createdFrom = OffsetDateTime.parse(\"2025-01-01T00:00:00.000Z\")\nval createdTo = OffsetDateTime.parse(\"2025-02-01T00:00:00.000Z\")\nval companyRoles = listOf(UserRole.ADMIN)\nval response = client.search.searchUsers(query = \"Олег\", limit = 10, cursor = \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\", sort = SearchSortOrder.BY_SCORE, order = SortOrder.DESC, createdFrom = createdFrom, createdTo = createdTo, companyRoles = companyRoles)", "output": "SearchUsersResponse(data: List, meta: SearchPaginationMeta)", "imports": [ "SearchSortOrder", @@ -279,15 +285,15 @@ }, "TaskOperations_listTasks": { "usage": "val response = client.tasks.listTasks(limit = 1, cursor = \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", - "output": "ListTasksResponse(data: List, meta: PaginationMeta?)" + "output": "ListTasksResponse(data: List, meta: PaginationMeta)" }, "TaskOperations_getTask": { "usage": "val response = client.tasks.getTask(id = 22283)", - "output": "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)" + "output": "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)" }, "TaskOperations_createTask": { - "usage": "val request = TaskCreateRequest(\n task = TaskCreateRequestTask(\n kind = TaskKind.REMINDER,\n content = \"Забрать со склада 21 заказ\",\n dueAt = \"2020-06-05T12:00:00.000+03:00\",\n priority = 2,\n performerIds = listOf(123),\n chatId = 456,\n allDay = false,\n customProperties = listOf(TaskCreateRequestCustomProperty(id = 78, value = \"Синий склад\"))\n )\n)\nval response = client.tasks.createTask(request = request)", - "output": "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)", + "usage": "val request = TaskCreateRequest(\n task = TaskCreateRequestTask(\n kind = TaskKind.REMINDER,\n content = \"Забрать со склада 21 заказ\",\n dueAt = OffsetDateTime.parse(\"2020-06-05T12:00:00.000+03:00\"),\n priority = 2,\n performerIds = listOf(123),\n chatId = 456,\n allDay = false,\n customProperties = listOf(TaskCreateRequestCustomProperty(id = 78, value = \"Синий склад\"))\n )\n)\nval response = client.tasks.createTask(request = request)", + "output": "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)", "imports": [ "TaskCreateRequest", "TaskCreateRequestCustomProperty", @@ -296,8 +302,8 @@ ] }, "TaskOperations_updateTask": { - "usage": "val request = TaskUpdateRequest(\n task = TaskUpdateRequestTask(\n kind = TaskKind.REMINDER,\n content = \"Забрать со склада 21 заказ\",\n dueAt = \"2020-06-05T12:00:00.000+03:00\",\n priority = 2,\n performerIds = listOf(123),\n status = TaskStatus.DONE,\n allDay = false,\n doneAt = \"2020-06-05T12:00:00.000Z\",\n customProperties = listOf(TaskUpdateRequestCustomProperty(id = 78, value = \"Синий склад\"))\n )\n)\nval response = client.tasks.updateTask(id = 22283, request = request)", - "output": "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)", + "usage": "val request = TaskUpdateRequest(\n task = TaskUpdateRequestTask(\n kind = TaskKind.REMINDER,\n content = \"Забрать со склада 21 заказ\",\n dueAt = OffsetDateTime.parse(\"2020-06-05T12:00:00.000+03:00\"),\n priority = 2,\n performerIds = listOf(123),\n status = TaskStatus.DONE,\n allDay = false,\n doneAt = OffsetDateTime.parse(\"2020-06-05T12:00:00.000Z\"),\n customProperties = listOf(TaskUpdateRequestCustomProperty(id = 78, value = \"Синий склад\"))\n )\n)\nval response = client.tasks.updateTask(id = 22283, request = request)", + "output": "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)", "imports": [ "TaskKind", "TaskStatus", @@ -311,11 +317,11 @@ }, "UserOperations_listUsers": { "usage": "val response = client.users.listUsers(query = \"Олег\", limit = 1, cursor = \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", - "output": "ListUsersResponse(data: List, meta: PaginationMeta?)" + "output": "ListUsersResponse(data: List, meta: PaginationMeta)" }, "UserOperations_getUser": { "usage": "val response = client.users.getUser(id = 12)", - "output": "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?)" + "output": "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?)" }, "UserStatusOperations_getUserStatus": { "usage": "val response = client.users.getUserStatus(userId = 12)", @@ -323,7 +329,7 @@ }, "UserOperations_createUser": { "usage": "val request = UserCreateRequest(\n user = UserCreateRequestUser(\n firstName = \"Олег\",\n lastName = \"Петров\",\n email = \"olegp@example.com\",\n phoneNumber = \"+79001234567\",\n nickname = \"olegpetrov\",\n department = \"Продукт\",\n title = \"CIO\",\n role = UserRoleInput.USER,\n suspended = false,\n listTags = listOf(\"example\"),\n customProperties = listOf(UserCreateRequestCustomProperty(id = 1678, value = \"Санкт-Петербург\"))\n ),\n skipEmailNotify = true\n)\nval response = client.users.createUser(request = request)", - "output": "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?)", + "output": "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?)", "imports": [ "UserCreateRequest", "UserCreateRequestCustomProperty", @@ -333,7 +339,7 @@ }, "UserOperations_updateUser": { "usage": "val request = UserUpdateRequest(\n user = UserUpdateRequestUser(\n firstName = \"Олег\",\n lastName = \"Петров\",\n email = \"olegpetrov@example.com\",\n phoneNumber = \"+79001234567\",\n nickname = \"olegpetrov\",\n department = \"Отдел разработки\",\n title = \"Старший разработчик\",\n role = UserRoleInput.USER,\n suspended = false,\n listTags = listOf(\"example\"),\n customProperties = listOf(UserUpdateRequestCustomProperty(id = 1678, value = \"Санкт-Петербург\"))\n )\n)\nval response = client.users.updateUser(id = 12, request = request)", - "output": "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?)", + "output": "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?)", "imports": [ "UserRoleInput", "UserUpdateRequest", @@ -341,9 +347,13 @@ "UserUpdateRequestUser" ] }, + "UserAvatarOperations_updateUserAvatar": { + "usage": "val image = ByteArray(0)\nval response = client.users.updateUserAvatar(userId = 12, image = image)", + "output": "AvatarData(imageUrl: String)" + }, "UserStatusOperations_updateUserStatus": { - "usage": "val request = StatusUpdateRequest(\n status = StatusUpdateRequestStatus(\n emoji = \"🎮\",\n title = \"Очень занят\",\n expiresAt = \"2024-04-08T10:00:00.000Z\",\n isAway = true,\n awayMessage = \"Вернусь после 15:00\"\n )\n)\nval response = client.users.updateUserStatus(userId = 12, request = request)", - "output": "UserStatus(emoji: String, title: String, expiresAt: String?, isAway: Boolean, awayMessage: UserStatusAwayMessage(text: String)?)", + "usage": "val request = StatusUpdateRequest(\n status = StatusUpdateRequestStatus(\n emoji = \"🎮\",\n title = \"Очень занят\",\n expiresAt = OffsetDateTime.parse(\"2024-04-08T10:00:00.000Z\"),\n isAway = true,\n awayMessage = \"Вернусь после 15:00\"\n )\n)\nval response = client.users.updateUserStatus(userId = 12, request = request)", + "output": "UserStatus(emoji: String, title: String, expiresAt: OffsetDateTime?, isAway: Boolean, awayMessage: UserStatusAwayMessage(text: String)?)", "imports": [ "StatusUpdateRequest", "StatusUpdateRequestStatus" @@ -352,6 +362,9 @@ "UserOperations_deleteUser": { "usage": "client.users.deleteUser(id = 12)" }, + "UserAvatarOperations_deleteUserAvatar": { + "usage": "client.users.deleteUserAvatar(userId = 12)" + }, "UserStatusOperations_deleteUserStatus": { "usage": "client.users.deleteUserStatus(userId = 12)" }, diff --git a/sdk/python/examples/main.py b/sdk/python/examples/main.py index a1e3d804..bc1949fe 100644 --- a/sdk/python/examples/main.py +++ b/sdk/python/examples/main.py @@ -27,6 +27,10 @@ async def main(): client = PachcaClient(token) + # 0. Get chat (verifies datetime deserialization) + chat = await client.chats.get_chat(chat_id) + print(f"0. Chat: {chat.name}, created_at={chat.created_at} ({type(chat.created_at).__name__}), last_message_at={chat.last_message_at} ({type(chat.last_message_at).__name__})") + # 1. Create message msg = await client.messages.create_message( MessageCreateRequest( diff --git a/sdk/python/generated/README.md b/sdk/python/generated/README.md index 8fbc3ffc..de807b50 100644 --- a/sdk/python/generated/README.md +++ b/sdk/python/generated/README.md @@ -5,7 +5,7 @@ Python клиент для [Pachca API](https://dev.pachca.com). ## Установка ```bash -pip install pachca-sdk==1.0.1 +pip install pachca-sdk ``` ## Использование @@ -61,10 +61,10 @@ chats = [] cursor = None while True: response = await client.chats.list_chats(cursor=cursor) - chats.extend(response.data) - cursor = response.meta.paginate.next_page if response.meta and response.meta.paginate else None - if not cursor: + if not response.data: break + chats.extend(response.data) + cursor = response.meta.paginate.next_page # Автоматически all_chats = await client.chats.list_chats_all() diff --git a/sdk/python/generated/pachca/client.py b/sdk/python/generated/pachca/client.py index c767669c..14b8e9b6 100644 --- a/sdk/python/generated/pachca/client.py +++ b/sdk/python/generated/pachca/client.py @@ -17,6 +17,7 @@ ListChatsParams, ListChatsResponse, Chat, + ChatSortField, SortOrder, ChatAvailability, ChatCreateRequest, @@ -36,12 +37,13 @@ ListTagsParams, ListTagsResponse, GroupTag, - TagNamesFilter, GetTagUsersParams, + GetTagUsersResponse, GroupTagRequest, ListChatMessagesParams, ListChatMessagesResponse, Message, + MessageSortField, MessageCreateRequest, MessageUpdateRequest, LinkPreviewsRequest, @@ -53,12 +55,16 @@ ListReadMembersParams, Thread, AccessTokenInfo, + AvatarData, StatusUpdateRequest, UserStatus, SearchChatsParams, + SearchChatsResponse, ChatSubtype, SearchMessagesParams, + SearchMessagesResponse, SearchUsersParams, + SearchUsersResponse, SearchSortOrder, ListTasksParams, ListTasksResponse, @@ -66,6 +72,7 @@ TaskCreateRequest, TaskUpdateRequest, ListUsersParams, + ListUsersResponse, UserCreateRequest, UserUpdateRequest, OpenViewRequest, @@ -82,9 +89,9 @@ async def get_audit_events( ) -> GetAuditEventsResponse: query: dict[str, str] = {} if params is not None and params.start_time is not None: - query["start_time"] = params.start_time + query["start_time"] = params.start_time.isoformat() if params is not None and params.end_time is not None: - query["end_time"] = params.end_time + query["end_time"] = params.end_time.isoformat() if params is not None and params.event_key is not None: query["event_key"] = params.event_key if params is not None and params.actor_id is not None: @@ -124,9 +131,9 @@ async def get_audit_events_all( params.cursor = cursor response = await self.get_audit_events(params=params) items.extend(response.data) - cursor = response.meta.paginate.next_page if response.meta and response.meta.paginate else None - if not cursor: + if not response.data: break + cursor = response.meta.paginate.next_page return items @@ -168,9 +175,9 @@ async def get_webhook_events_all( params.cursor = cursor response = await self.get_webhook_events(params=params) items.extend(response.data) - cursor = response.meta.paginate.next_page if response.meta and response.meta.paginate else None - if not cursor: + if not response.data: break + cursor = response.meta.paginate.next_page return items async def update_bot( @@ -216,14 +223,16 @@ async def list_chats( params: ListChatsParams | None = None, ) -> ListChatsResponse: query: dict[str, str] = {} - if params is not None and params.sort_id is not None: - query["sort[{field}]"] = params.sort_id + if params is not None and params.sort is not None: + query["sort"] = params.sort + if params is not None and params.order is not None: + query["order"] = params.order if params is not None and params.availability is not None: query["availability"] = params.availability if params is not None and params.last_message_at_after is not None: - query["last_message_at_after"] = params.last_message_at_after + query["last_message_at_after"] = params.last_message_at_after.isoformat() if params is not None and params.last_message_at_before is not None: - query["last_message_at_before"] = params.last_message_at_before + query["last_message_at_before"] = params.last_message_at_before.isoformat() if params is not None and params.personal is not None: query["personal"] = str(params.personal).lower() if params is not None and params.limit is not None: @@ -255,9 +264,9 @@ async def list_chats_all( params.cursor = cursor response = await self.list_chats(params=params) items.extend(response.data) - cursor = response.meta.paginate.next_page if response.meta and response.meta.paginate else None - if not cursor: + if not response.data: break + cursor = response.meta.paginate.next_page return items async def get_chat( @@ -485,9 +494,9 @@ async def list_members_all( params.cursor = cursor response = await self.list_members(id, params=params) items.extend(response.data) - cursor = response.meta.paginate.next_page if response.meta and response.meta.paginate else None - if not cursor: + if not response.data: break + cursor = response.meta.paginate.next_page return items async def add_tags( @@ -598,13 +607,14 @@ async def list_tags( self, params: ListTagsParams | None = None, ) -> ListTagsResponse: - query: dict[str, str] = {} + query: list[tuple[str, str]] = [] if params is not None and params.names is not None: - query["names"] = params.names + for v in params.names: + query.append(("names[]", str(v))) if params is not None and params.limit is not None: - query["limit"] = str(params.limit) + query.append(("limit", str(params.limit))) if params is not None and params.cursor is not None: - query["cursor"] = params.cursor + query.append(("cursor", params.cursor)) response = await self._client.get( "/group_tags", params=query, @@ -630,9 +640,9 @@ async def list_tags_all( params.cursor = cursor response = await self.list_tags(params=params) items.extend(response.data) - cursor = response.meta.paginate.next_page if response.meta and response.meta.paginate else None - if not cursor: + if not response.data: break + cursor = response.meta.paginate.next_page return items async def get_tag( @@ -655,7 +665,7 @@ async def get_tag_users( self, id: int, params: GetTagUsersParams | None = None, - ) -> ListMembersResponse: + ) -> GetTagUsersResponse: query: dict[str, str] = {} if params is not None and params.limit is not None: query["limit"] = str(params.limit) @@ -668,7 +678,7 @@ async def get_tag_users( body = response.json() match response.status_code: case 200: - return deserialize(ListMembersResponse, body) + return deserialize(GetTagUsersResponse, body) case 401: raise deserialize(OAuthError, body) case _: @@ -687,9 +697,9 @@ async def get_tag_users_all( params.cursor = cursor response = await self.get_tag_users(id, params=params) items.extend(response.data) - cursor = response.meta.paginate.next_page if response.meta and response.meta.paginate else None - if not cursor: + if not response.data: break + cursor = response.meta.paginate.next_page return items async def create_tag( @@ -753,8 +763,10 @@ async def list_chat_messages( ) -> ListChatMessagesResponse: query: list[tuple[str, str]] = [] query.append(("chat_id", str(params.chat_id))) - if params is not None and params.sort_id is not None: - query.append(("sort[{field}]", params.sort_id)) + if params is not None and params.sort is not None: + query.append(("sort", params.sort)) + if params is not None and params.order is not None: + query.append(("order", params.order)) if params is not None and params.limit is not None: query.append(("limit", str(params.limit))) if params is not None and params.cursor is not None: @@ -784,9 +796,9 @@ async def list_chat_messages_all( params.cursor = cursor response = await self.list_chat_messages(params=params) items.extend(response.data) - cursor = response.meta.paginate.next_page if response.meta and response.meta.paginate else None - if not cursor: + if not response.data: break + cursor = response.meta.paginate.next_page return items async def get_message( @@ -948,9 +960,9 @@ async def list_reactions_all( params.cursor = cursor response = await self.list_reactions(id, params=params) items.extend(response.data) - cursor = response.meta.paginate.next_page if response.meta and response.meta.paginate else None - if not cursor: + if not response.data: break + cursor = response.meta.paginate.next_page return items async def add_reaction( @@ -1104,6 +1116,25 @@ async def get_status( case _: raise deserialize(ApiError, body) + async def update_profile_avatar( + self, + image: bytes, + ) -> AvatarData: + data: dict[str, str] = {} + response = await self._client.post( + "/profile/avatar", + data=data, + files={"image": image}, + ) + body = response.json() + match response.status_code: + case 200: + return deserialize(AvatarData, body["data"]) + case 401: + raise deserialize(OAuthError, body) + case _: + raise deserialize(ApiError, body) + async def update_status( self, request: StatusUpdateRequest, @@ -1121,6 +1152,19 @@ async def update_status( case _: raise deserialize(ApiError, body) + async def delete_profile_avatar( + self) -> None: + response = await self._client.delete( + "/profile/avatar", + ) + match response.status_code: + case 204: + return + case 401: + raise deserialize(OAuthError, response.json()) + case _: + raise deserialize(ApiError, response.json()) + async def delete_status( self) -> None: response = await self._client.delete( @@ -1142,7 +1186,7 @@ def __init__(self, client: httpx.AsyncClient) -> None: async def search_chats( self, params: SearchChatsParams | None = None, - ) -> ListChatsResponse: + ) -> SearchChatsResponse: query: dict[str, str] = {} if params is not None and params.query is not None: query["query"] = params.query @@ -1153,9 +1197,9 @@ async def search_chats( if params is not None and params.order is not None: query["order"] = params.order if params is not None and params.created_from is not None: - query["created_from"] = params.created_from + query["created_from"] = params.created_from.isoformat() if params is not None and params.created_to is not None: - query["created_to"] = params.created_to + query["created_to"] = params.created_to.isoformat() if params is not None and params.active is not None: query["active"] = str(params.active).lower() if params is not None and params.chat_subtype is not None: @@ -1169,7 +1213,7 @@ async def search_chats( body = response.json() match response.status_code: case 200: - return deserialize(ListChatsResponse, body) + return deserialize(SearchChatsResponse, body) case 401: raise deserialize(OAuthError, body) case _: @@ -1187,34 +1231,36 @@ async def search_chats_all( params.cursor = cursor response = await self.search_chats(params=params) items.extend(response.data) - cursor = response.meta.paginate.next_page if response.meta and response.meta.paginate else None - if not cursor: + if not response.data: break + cursor = response.meta.paginate.next_page return items async def search_messages( self, params: SearchMessagesParams | None = None, - ) -> ListChatMessagesResponse: - query: dict[str, str] = {} + ) -> SearchMessagesResponse: + query: list[tuple[str, str]] = [] if params is not None and params.query is not None: - query["query"] = params.query + query.append(("query", params.query)) if params is not None and params.limit is not None: - query["limit"] = str(params.limit) + query.append(("limit", str(params.limit))) if params is not None and params.cursor is not None: - query["cursor"] = params.cursor + query.append(("cursor", params.cursor)) if params is not None and params.order is not None: - query["order"] = params.order + query.append(("order", params.order)) if params is not None and params.created_from is not None: - query["created_from"] = params.created_from + query.append(("created_from", params.created_from.isoformat())) if params is not None and params.created_to is not None: - query["created_to"] = params.created_to + query.append(("created_to", params.created_to.isoformat())) if params is not None and params.chat_ids is not None: - query["chat_ids"] = params.chat_ids + for v in params.chat_ids: + query.append(("chat_ids[]", str(v))) if params is not None and params.user_ids is not None: - query["user_ids"] = params.user_ids + for v in params.user_ids: + query.append(("user_ids[]", str(v))) if params is not None and params.active is not None: - query["active"] = str(params.active).lower() + query.append(("active", str(params.active).lower())) response = await self._client.get( "/search/messages", params=query, @@ -1222,7 +1268,7 @@ async def search_messages( body = response.json() match response.status_code: case 200: - return deserialize(ListChatMessagesResponse, body) + return deserialize(SearchMessagesResponse, body) case 401: raise deserialize(OAuthError, body) case _: @@ -1240,32 +1286,33 @@ async def search_messages_all( params.cursor = cursor response = await self.search_messages(params=params) items.extend(response.data) - cursor = response.meta.paginate.next_page if response.meta and response.meta.paginate else None - if not cursor: + if not response.data: break + cursor = response.meta.paginate.next_page return items async def search_users( self, params: SearchUsersParams | None = None, - ) -> ListMembersResponse: - query: dict[str, str] = {} + ) -> SearchUsersResponse: + query: list[tuple[str, str]] = [] if params is not None and params.query is not None: - query["query"] = params.query + query.append(("query", params.query)) if params is not None and params.limit is not None: - query["limit"] = str(params.limit) + query.append(("limit", str(params.limit))) if params is not None and params.cursor is not None: - query["cursor"] = params.cursor + query.append(("cursor", params.cursor)) if params is not None and params.sort is not None: - query["sort"] = params.sort + query.append(("sort", params.sort)) if params is not None and params.order is not None: - query["order"] = params.order + query.append(("order", params.order)) if params is not None and params.created_from is not None: - query["created_from"] = params.created_from + query.append(("created_from", params.created_from.isoformat())) if params is not None and params.created_to is not None: - query["created_to"] = params.created_to + query.append(("created_to", params.created_to.isoformat())) if params is not None and params.company_roles is not None: - query["company_roles"] = params.company_roles + for v in params.company_roles: + query.append(("company_roles[]", str(v))) response = await self._client.get( "/search/users", params=query, @@ -1273,7 +1320,7 @@ async def search_users( body = response.json() match response.status_code: case 200: - return deserialize(ListMembersResponse, body) + return deserialize(SearchUsersResponse, body) case 401: raise deserialize(OAuthError, body) case _: @@ -1291,9 +1338,9 @@ async def search_users_all( params.cursor = cursor response = await self.search_users(params=params) items.extend(response.data) - cursor = response.meta.paginate.next_page if response.meta and response.meta.paginate else None - if not cursor: + if not response.data: break + cursor = response.meta.paginate.next_page return items @@ -1335,9 +1382,9 @@ async def list_tasks_all( params.cursor = cursor response = await self.list_tasks(params=params) items.extend(response.data) - cursor = response.meta.paginate.next_page if response.meta and response.meta.paginate else None - if not cursor: + if not response.data: break + cursor = response.meta.paginate.next_page return items async def get_task( @@ -1414,7 +1461,7 @@ def __init__(self, client: httpx.AsyncClient) -> None: async def list_users( self, params: ListUsersParams | None = None, - ) -> ListMembersResponse: + ) -> ListUsersResponse: query: dict[str, str] = {} if params is not None and params.query is not None: query["query"] = params.query @@ -1429,7 +1476,7 @@ async def list_users( body = response.json() match response.status_code: case 200: - return deserialize(ListMembersResponse, body) + return deserialize(ListUsersResponse, body) case 401: raise deserialize(OAuthError, body) case _: @@ -1447,9 +1494,9 @@ async def list_users_all( params.cursor = cursor response = await self.list_users(params=params) items.extend(response.data) - cursor = response.meta.paginate.next_page if response.meta and response.meta.paginate else None - if not cursor: + if not response.data: break + cursor = response.meta.paginate.next_page return items async def get_user( @@ -1519,6 +1566,26 @@ async def update_user( case _: raise deserialize(ApiError, body) + async def update_user_avatar( + self, + user_id: int, + image: bytes, + ) -> AvatarData: + data: dict[str, str] = {} + response = await self._client.post( + f"/users/{user_id}/avatar", + data=data, + files={"image": image}, + ) + body = response.json() + match response.status_code: + case 200: + return deserialize(AvatarData, body["data"]) + case 401: + raise deserialize(OAuthError, body) + case _: + raise deserialize(ApiError, body) + async def update_user_status( self, user_id: int, @@ -1552,6 +1619,21 @@ async def delete_user( case _: raise deserialize(ApiError, response.json()) + async def delete_user_avatar( + self, + user_id: int, + ) -> None: + response = await self._client.delete( + f"/users/{user_id}/avatar", + ) + match response.status_code: + case 204: + return + case 401: + raise deserialize(OAuthError, response.json()) + case _: + raise deserialize(ApiError, response.json()) + async def delete_user_status( self, user_id: int, diff --git a/sdk/python/generated/pachca/examples.json b/sdk/python/generated/pachca/examples.json index 339733ff..09ca3b42 100644 --- a/sdk/python/generated/pachca/examples.json +++ b/sdk/python/generated/pachca/examples.json @@ -6,8 +6,8 @@ ] }, "SecurityOperations_getAuditEvents": { - "usage": "params = GetAuditEventsParams(\n start_time=\"2025-05-01T09:11:00Z\",\n end_time=\"2025-05-02T09:11:00Z\",\n event_key=AuditEventKey.USER_LOGIN,\n actor_id=\"98765\",\n actor_type=\"User\",\n entity_id=\"98765\",\n entity_type=\"User\",\n limit=1,\n cursor=\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"\n)\nresponse = await client.security.get_audit_events(params=params)", - "output": "GetAuditEventsResponse(data: list[AuditEvent], meta: PaginationMeta | None)", + "usage": "params = GetAuditEventsParams(\n start_time=datetime.fromisoformat(\"2025-05-01T09:11:00Z\"),\n end_time=datetime.fromisoformat(\"2025-05-02T09:11:00Z\"),\n event_key=AuditEventKey.USER_LOGIN,\n actor_id=\"98765\",\n actor_type=\"User\",\n entity_id=\"98765\",\n entity_type=\"User\",\n limit=1,\n cursor=\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"\n)\nresponse = await client.security.get_audit_events(params=params)", + "output": "GetAuditEventsResponse(data: list[AuditEvent], meta: PaginationMeta)", "imports": [ "AuditEventKey", "GetAuditEventsParams" @@ -15,7 +15,7 @@ }, "BotOperations_getWebhookEvents": { "usage": "params = GetWebhookEventsParams(limit=1, cursor=\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")\nresponse = await client.bots.get_webhook_events(params=params)", - "output": "GetWebhookEventsResponse(data: list[WebhookEvent], meta: PaginationMeta | None)", + "output": "GetWebhookEventsResponse(data: list[WebhookEvent], meta: PaginationMeta)", "imports": [ "GetWebhookEventsParams" ] @@ -33,21 +33,22 @@ "usage": "await client.bots.delete_webhook_event(id=\"01KAJZ2XDSS2S3DSW9EXJZ0TBV\")" }, "ChatOperations_listChats": { - "usage": "params = ListChatsParams(\n sort_id=SortOrder.DESC,\n availability=ChatAvailability.IS_MEMBER,\n last_message_at_after=\"2025-01-01T00:00:00.000Z\",\n last_message_at_before=\"2025-02-01T00:00:00.000Z\",\n personal=False,\n limit=1,\n cursor=\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"\n)\nresponse = await client.chats.list_chats(params=params)", - "output": "ListChatsResponse(data: list[Chat], meta: PaginationMeta | None)", + "usage": "params = ListChatsParams(\n sort=ChatSortField.ID,\n order=SortOrder.DESC,\n availability=ChatAvailability.IS_MEMBER,\n last_message_at_after=datetime.fromisoformat(\"2025-01-01T00:00:00.000Z\"),\n last_message_at_before=datetime.fromisoformat(\"2025-02-01T00:00:00.000Z\"),\n personal=False,\n limit=1,\n cursor=\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"\n)\nresponse = await client.chats.list_chats(params=params)", + "output": "ListChatsResponse(data: list[Chat], meta: PaginationMeta)", "imports": [ "ChatAvailability", + "ChatSortField", "ListChatsParams", "SortOrder" ] }, "ChatOperations_getChat": { "usage": "response = await client.chats.get_chat(id=334)", - "output": "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)" + "output": "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)" }, "ChatOperations_createChat": { "usage": "request = ChatCreateRequest(\n chat=ChatCreateRequestChat(\n name=\"🤿 aqua\",\n member_ids=[123],\n group_tag_ids=[123],\n channel=True,\n public=False\n )\n)\nresponse = await client.chats.create_chat(request=request)", - "output": "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)", + "output": "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)", "imports": [ "ChatCreateRequest", "ChatCreateRequestChat" @@ -55,7 +56,7 @@ }, "ChatOperations_updateChat": { "usage": "request = ChatUpdateRequest(chat=ChatUpdateRequestChat(name=\"Бассейн\", public=True))\nresponse = await client.chats.update_chat(id=334, request=request)", - "output": "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)", + "output": "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)", "imports": [ "ChatUpdateRequest", "ChatUpdateRequestChat" @@ -97,7 +98,7 @@ }, "ChatMemberOperations_listMembers": { "usage": "params = ListMembersParams(\n role=ChatMemberRoleFilter.ALL,\n limit=1,\n cursor=\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"\n)\nresponse = await client.members.list_members(id=334, params=params)", - "output": "ListMembersResponse(data: list[User], meta: PaginationMeta | None)", + "output": "ListMembersResponse(data: list[User], meta: PaginationMeta)", "imports": [ "ChatMemberRoleFilter", "ListMembersParams" @@ -128,11 +129,10 @@ "usage": "await client.members.remove_member(id=334, user_id=186)" }, "GroupTagOperations_listTags": { - "usage": "params = ListTagsParams(\n names=TagNamesFilter(),\n limit=1,\n cursor=\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"\n)\nresponse = await client.group_tags.list_tags(params=params)", - "output": "ListTagsResponse(data: list[GroupTag], meta: PaginationMeta | None)", + "usage": "params = ListTagsParams(\n names=[\"example\"],\n limit=1,\n cursor=\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"\n)\nresponse = await client.group_tags.list_tags(params=params)", + "output": "ListTagsResponse(data: list[GroupTag], meta: PaginationMeta)", "imports": [ - "ListTagsParams", - "TagNamesFilter" + "ListTagsParams" ] }, "GroupTagOperations_getTag": { @@ -141,7 +141,7 @@ }, "GroupTagOperations_getTagUsers": { "usage": "params = GetTagUsersParams(limit=1, cursor=\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")\nresponse = await client.group_tags.get_tag_users(id=9111, params=params)", - "output": "GetTagUsersResponse(data: list[User], meta: PaginationMeta | None)", + "output": "GetTagUsersResponse(data: list[User], meta: PaginationMeta)", "imports": [ "GetTagUsersParams" ] @@ -166,20 +166,21 @@ "usage": "await client.group_tags.delete_tag(id=9111)" }, "ChatMessageOperations_listChatMessages": { - "usage": "params = ListChatMessagesParams(\n chat_id=198,\n sort_id=SortOrder.DESC,\n limit=1,\n cursor=\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"\n)\nresponse = await client.messages.list_chat_messages(params=params)", - "output": "ListChatMessagesResponse(data: list[Message], meta: PaginationMeta | None)", + "usage": "params = ListChatMessagesParams(\n chat_id=198,\n sort=MessageSortField.ID,\n order=SortOrder.DESC,\n limit=1,\n cursor=\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"\n)\nresponse = await client.messages.list_chat_messages(params=params)", + "output": "ListChatMessagesResponse(data: list[Message], meta: PaginationMeta)", "imports": [ "ListChatMessagesParams", + "MessageSortField", "SortOrder" ] }, "MessageOperations_getMessage": { "usage": "response = await client.messages.get_message(id=194275)", - "output": "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)" + "output": "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)" }, "MessageOperations_createMessage": { "usage": "request = MessageCreateRequest(\n message=MessageCreateRequestMessage(\n entity_type=MessageEntityType.DISCUSSION,\n entity_id=334,\n content=\"Вчера мы продали 756 футболок (что на 10% больше, чем в прошлое воскресенье)\",\n files=[MessageCreateRequestFile(\n key=\"attaches/files/93746/e354fd79-4f3e-4b5a-9c8d-1a2b3c4d5e6f/logo.png\",\n name=\"logo.png\",\n file_type=FileType.IMAGE,\n size=12345,\n width=800,\n height=600\n )],\n buttons=[[Button(\n text=\"Подробнее\",\n url=\"https://example.com/details\",\n data=\"awesome\"\n )]],\n parent_message_id=194270,\n display_avatar_url=\"https://example.com/avatar.png\",\n display_name=\"Бот Поддержки\",\n skip_invite_mentions=False\n ),\n link_preview=False\n)\nresponse = await client.messages.create_message(request=request)", - "output": "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)", + "output": "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)", "imports": [ "Button", "FileType", @@ -194,7 +195,7 @@ }, "MessageOperations_updateMessage": { "usage": "request = MessageUpdateRequest(\n message=MessageUpdateRequestMessage(\n content=\"Вот попробуйте написать правильно это с первого раза: Будущий, Полощи, Прийти, Грейпфрут, Мозаика, Бюллетень, Дуршлаг, Винегрет.\",\n files=[MessageUpdateRequestFile(\n key=\"attaches/files/93746/e354fd79-4f3e-4b5a-9c8d-1a2b3c4d5e6f/logo.png\",\n name=\"logo.png\",\n file_type=\"image\",\n size=12345,\n width=800,\n height=600\n )],\n buttons=[[Button(\n text=\"Подробнее\",\n url=\"https://example.com/details\",\n data=\"awesome\"\n )]],\n display_avatar_url=\"https://example.com/avatar.png\",\n display_name=\"Бот Поддержки\"\n )\n)\nresponse = await client.messages.update_message(id=194275, request=request)", - "output": "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)", + "output": "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)", "imports": [ "Button", "MessageUpdateRequest", @@ -218,14 +219,14 @@ }, "ReactionOperations_listReactions": { "usage": "params = ListReactionsParams(limit=1, cursor=\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")\nresponse = await client.reactions.list_reactions(id=194275, params=params)", - "output": "ListReactionsResponse(data: list[Reaction], meta: PaginationMeta | None)", + "output": "ListReactionsResponse(data: list[Reaction], meta: PaginationMeta)", "imports": [ "ListReactionsParams" ] }, "ReactionOperations_addReaction": { "usage": "request = ReactionRequest(code=\"👍\", name=\":+1:\")\nresponse = await client.reactions.add_reaction(id=7231942, request=request)", - "output": "Reaction(user_id: int, created_at: str, code: str, name: str | None)", + "output": "Reaction(user_id: int, created_at: datetime, code: str, name: str | None)", "imports": [ "ReactionRequest" ] @@ -245,37 +246,44 @@ }, "ThreadOperations_getThread": { "usage": "response = await client.threads.get_thread(id=265142)", - "output": "Thread(id: int, chat_id: int, message_id: int, message_chat_id: int, updated_at: str)" + "output": "Thread(id: int, chat_id: int, message_id: int, message_chat_id: int, updated_at: datetime)" }, "ThreadOperations_createThread": { "usage": "response = await client.threads.create_thread(id=154332686)", - "output": "Thread(id: int, chat_id: int, message_id: int, message_chat_id: int, updated_at: str)" + "output": "Thread(id: int, chat_id: int, message_id: int, message_chat_id: int, updated_at: datetime)" }, "OAuthOperations_getTokenInfo": { "usage": "response = await client.profile.get_token_info()", - "output": "AccessTokenInfo(id: int, token: str, name: str | None, user_id: int, scopes: list[OAuthScope], created_at: str, revoked_at: str | None, expires_in: int | None, last_used_at: str | None)" + "output": "AccessTokenInfo(id: int, token: str, name: str | None, user_id: int, scopes: list[OAuthScope], created_at: datetime, revoked_at: datetime | None, expires_in: int | None, last_used_at: datetime | None)" }, "ProfileOperations_getProfile": { "usage": "response = await client.profile.get_profile()", - "output": "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)" + "output": "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)" }, "ProfileOperations_getStatus": { "usage": "response = await client.profile.get_status()", "output": "object" }, + "ProfileAvatarOperations_updateProfileAvatar": { + "usage": "response = await client.profile.update_profile_avatar(image=b\"\")", + "output": "AvatarData(image_url: str)" + }, "ProfileOperations_updateStatus": { - "usage": "request = StatusUpdateRequest(\n status=StatusUpdateRequestStatus(\n emoji=\"🎮\",\n title=\"Очень занят\",\n expires_at=\"2024-04-08T10:00:00.000Z\",\n is_away=True,\n away_message=\"Вернусь после 15:00\"\n )\n)\nresponse = await client.profile.update_status(request=request)", - "output": "UserStatus(emoji: str, title: str, expires_at: str | None, is_away: bool, away_message: UserStatusAwayMessage(text: str) | None)", + "usage": "request = StatusUpdateRequest(\n status=StatusUpdateRequestStatus(\n emoji=\"🎮\",\n title=\"Очень занят\",\n expires_at=datetime.fromisoformat(\"2024-04-08T10:00:00.000Z\"),\n is_away=True,\n away_message=\"Вернусь после 15:00\"\n )\n)\nresponse = await client.profile.update_status(request=request)", + "output": "UserStatus(emoji: str, title: str, expires_at: datetime | None, is_away: bool, away_message: UserStatusAwayMessage(text: str) | None)", "imports": [ "StatusUpdateRequest", "StatusUpdateRequestStatus" ] }, + "ProfileAvatarOperations_deleteProfileAvatar": { + "usage": "await client.profile.delete_profile_avatar()" + }, "ProfileOperations_deleteStatus": { "usage": "await client.profile.delete_status()" }, "SearchOperations_searchChats": { - "usage": "params = SearchChatsParams(\n query=\"Разработка\",\n limit=10,\n cursor=\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\",\n order=SortOrder.DESC,\n created_from=\"2025-01-01T00:00:00.000Z\",\n created_to=\"2025-02-01T00:00:00.000Z\",\n active=True,\n chat_subtype=ChatSubtype.DISCUSSION,\n personal=False\n)\nresponse = await client.search.search_chats(params=params)", + "usage": "params = SearchChatsParams(\n query=\"Разработка\",\n limit=10,\n cursor=\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\",\n order=SortOrder.DESC,\n created_from=datetime.fromisoformat(\"2025-01-01T00:00:00.000Z\"),\n created_to=datetime.fromisoformat(\"2025-02-01T00:00:00.000Z\"),\n active=True,\n chat_subtype=ChatSubtype.DISCUSSION,\n personal=False\n)\nresponse = await client.search.search_chats(params=params)", "output": "SearchChatsResponse(data: list[Chat], meta: SearchPaginationMeta)", "imports": [ "ChatSubtype", @@ -284,7 +292,7 @@ ] }, "SearchOperations_searchMessages": { - "usage": "params = SearchMessagesParams(\n query=\"футболки\",\n limit=10,\n cursor=\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\",\n order=SortOrder.DESC,\n created_from=\"2025-01-01T00:00:00.000Z\",\n created_to=\"2025-02-01T00:00:00.000Z\",\n chat_ids=[123],\n user_ids=[123],\n active=True\n)\nresponse = await client.search.search_messages(params=params)", + "usage": "params = SearchMessagesParams(\n query=\"футболки\",\n limit=10,\n cursor=\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\",\n order=SortOrder.DESC,\n created_from=datetime.fromisoformat(\"2025-01-01T00:00:00.000Z\"),\n created_to=datetime.fromisoformat(\"2025-02-01T00:00:00.000Z\"),\n chat_ids=[123],\n user_ids=[123],\n active=True\n)\nresponse = await client.search.search_messages(params=params)", "output": "SearchMessagesResponse(data: list[Message], meta: SearchPaginationMeta)", "imports": [ "SearchMessagesParams", @@ -292,7 +300,7 @@ ] }, "SearchOperations_searchUsers": { - "usage": "params = SearchUsersParams(\n query=\"Олег\",\n limit=10,\n cursor=\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\",\n sort=SearchSortOrder.BY_SCORE,\n order=SortOrder.DESC,\n created_from=\"2025-01-01T00:00:00.000Z\",\n created_to=\"2025-02-01T00:00:00.000Z\",\n company_roles=[UserRole.ADMIN]\n)\nresponse = await client.search.search_users(params=params)", + "usage": "params = SearchUsersParams(\n query=\"Олег\",\n limit=10,\n cursor=\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\",\n sort=SearchSortOrder.BY_SCORE,\n order=SortOrder.DESC,\n created_from=datetime.fromisoformat(\"2025-01-01T00:00:00.000Z\"),\n created_to=datetime.fromisoformat(\"2025-02-01T00:00:00.000Z\"),\n company_roles=[UserRole.ADMIN]\n)\nresponse = await client.search.search_users(params=params)", "output": "SearchUsersResponse(data: list[User], meta: SearchPaginationMeta)", "imports": [ "SearchSortOrder", @@ -303,18 +311,18 @@ }, "TaskOperations_listTasks": { "usage": "params = ListTasksParams(limit=1, cursor=\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")\nresponse = await client.tasks.list_tasks(params=params)", - "output": "ListTasksResponse(data: list[Task], meta: PaginationMeta | None)", + "output": "ListTasksResponse(data: list[Task], meta: PaginationMeta)", "imports": [ "ListTasksParams" ] }, "TaskOperations_getTask": { "usage": "response = await client.tasks.get_task(id=22283)", - "output": "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)])" + "output": "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)])" }, "TaskOperations_createTask": { - "usage": "request = TaskCreateRequest(\n task=TaskCreateRequestTask(\n kind=TaskKind.REMINDER,\n content=\"Забрать со склада 21 заказ\",\n due_at=\"2020-06-05T12:00:00.000+03:00\",\n priority=2,\n performer_ids=[123],\n chat_id=456,\n all_day=False,\n custom_properties=[TaskCreateRequestCustomProperty(id=78, value=\"Синий склад\")]\n )\n)\nresponse = await client.tasks.create_task(request=request)", - "output": "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)])", + "usage": "request = TaskCreateRequest(\n task=TaskCreateRequestTask(\n kind=TaskKind.REMINDER,\n content=\"Забрать со склада 21 заказ\",\n due_at=datetime.fromisoformat(\"2020-06-05T12:00:00.000+03:00\"),\n priority=2,\n performer_ids=[123],\n chat_id=456,\n all_day=False,\n custom_properties=[TaskCreateRequestCustomProperty(id=78, value=\"Синий склад\")]\n )\n)\nresponse = await client.tasks.create_task(request=request)", + "output": "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)])", "imports": [ "TaskCreateRequest", "TaskCreateRequestCustomProperty", @@ -323,8 +331,8 @@ ] }, "TaskOperations_updateTask": { - "usage": "request = TaskUpdateRequest(\n task=TaskUpdateRequestTask(\n kind=TaskKind.REMINDER,\n content=\"Забрать со склада 21 заказ\",\n due_at=\"2020-06-05T12:00:00.000+03:00\",\n priority=2,\n performer_ids=[123],\n status=TaskStatus.DONE,\n all_day=False,\n done_at=\"2020-06-05T12:00:00.000Z\",\n custom_properties=[TaskUpdateRequestCustomProperty(id=78, value=\"Синий склад\")]\n )\n)\nresponse = await client.tasks.update_task(id=22283, request=request)", - "output": "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)])", + "usage": "request = TaskUpdateRequest(\n task=TaskUpdateRequestTask(\n kind=TaskKind.REMINDER,\n content=\"Забрать со склада 21 заказ\",\n due_at=datetime.fromisoformat(\"2020-06-05T12:00:00.000+03:00\"),\n priority=2,\n performer_ids=[123],\n status=TaskStatus.DONE,\n all_day=False,\n done_at=datetime.fromisoformat(\"2020-06-05T12:00:00.000Z\"),\n custom_properties=[TaskUpdateRequestCustomProperty(id=78, value=\"Синий склад\")]\n )\n)\nresponse = await client.tasks.update_task(id=22283, request=request)", + "output": "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)])", "imports": [ "TaskKind", "TaskStatus", @@ -338,14 +346,14 @@ }, "UserOperations_listUsers": { "usage": "params = ListUsersParams(\n query=\"Олег\",\n limit=1,\n cursor=\"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"\n)\nresponse = await client.users.list_users(params=params)", - "output": "ListUsersResponse(data: list[User], meta: PaginationMeta | None)", + "output": "ListUsersResponse(data: list[User], meta: PaginationMeta)", "imports": [ "ListUsersParams" ] }, "UserOperations_getUser": { "usage": "response = await client.users.get_user(id=12)", - "output": "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)" + "output": "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)" }, "UserStatusOperations_getUserStatus": { "usage": "response = await client.users.get_user_status(user_id=12)", @@ -353,7 +361,7 @@ }, "UserOperations_createUser": { "usage": "request = UserCreateRequest(\n user=UserCreateRequestUser(\n first_name=\"Олег\",\n last_name=\"Петров\",\n email=\"olegp@example.com\",\n phone_number=\"+79001234567\",\n nickname=\"olegpetrov\",\n department=\"Продукт\",\n title=\"CIO\",\n role=UserRoleInput.USER,\n suspended=False,\n list_tags=[\"example\"],\n custom_properties=[UserCreateRequestCustomProperty(id=1678, value=\"Санкт-Петербург\")]\n ),\n skip_email_notify=True\n)\nresponse = await client.users.create_user(request=request)", - "output": "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)", + "output": "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)", "imports": [ "UserCreateRequest", "UserCreateRequestCustomProperty", @@ -363,7 +371,7 @@ }, "UserOperations_updateUser": { "usage": "request = UserUpdateRequest(\n user=UserUpdateRequestUser(\n first_name=\"Олег\",\n last_name=\"Петров\",\n email=\"olegpetrov@example.com\",\n phone_number=\"+79001234567\",\n nickname=\"olegpetrov\",\n department=\"Отдел разработки\",\n title=\"Старший разработчик\",\n role=UserRoleInput.USER,\n suspended=False,\n list_tags=[\"example\"],\n custom_properties=[UserUpdateRequestCustomProperty(id=1678, value=\"Санкт-Петербург\")]\n )\n)\nresponse = await client.users.update_user(id=12, request=request)", - "output": "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)", + "output": "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)", "imports": [ "UserRoleInput", "UserUpdateRequest", @@ -371,9 +379,13 @@ "UserUpdateRequestUser" ] }, + "UserAvatarOperations_updateUserAvatar": { + "usage": "response = await client.users.update_user_avatar(user_id=12, image=b\"\")", + "output": "AvatarData(image_url: str)" + }, "UserStatusOperations_updateUserStatus": { - "usage": "request = StatusUpdateRequest(\n status=StatusUpdateRequestStatus(\n emoji=\"🎮\",\n title=\"Очень занят\",\n expires_at=\"2024-04-08T10:00:00.000Z\",\n is_away=True,\n away_message=\"Вернусь после 15:00\"\n )\n)\nresponse = await client.users.update_user_status(user_id=12, request=request)", - "output": "UserStatus(emoji: str, title: str, expires_at: str | None, is_away: bool, away_message: UserStatusAwayMessage(text: str) | None)", + "usage": "request = StatusUpdateRequest(\n status=StatusUpdateRequestStatus(\n emoji=\"🎮\",\n title=\"Очень занят\",\n expires_at=datetime.fromisoformat(\"2024-04-08T10:00:00.000Z\"),\n is_away=True,\n away_message=\"Вернусь после 15:00\"\n )\n)\nresponse = await client.users.update_user_status(user_id=12, request=request)", + "output": "UserStatus(emoji: str, title: str, expires_at: datetime | None, is_away: bool, away_message: UserStatusAwayMessage(text: str) | None)", "imports": [ "StatusUpdateRequest", "StatusUpdateRequestStatus" @@ -382,6 +394,9 @@ "UserOperations_deleteUser": { "usage": "await client.users.delete_user(id=12)" }, + "UserAvatarOperations_deleteUserAvatar": { + "usage": "await client.users.delete_user_avatar(user_id=12)" + }, "UserStatusOperations_deleteUserStatus": { "usage": "await client.users.delete_user_status(user_id=12)" }, diff --git a/sdk/python/generated/pachca/models.py b/sdk/python/generated/pachca/models.py index d42901ac..dc77adc3 100644 --- a/sdk/python/generated/pachca/models.py +++ b/sdk/python/generated/pachca/models.py @@ -1,5 +1,6 @@ from __future__ import annotations +from datetime import datetime from dataclasses import dataclass from enum import StrEnum from typing import Union @@ -69,6 +70,13 @@ class ChatMemberRoleFilter(StrEnum): MEMBER = "member" # Участник/подписчик +class ChatSortField(StrEnum): + """Поле сортировки чатов""" + + ID = "id" # По идентификатору чата + LAST_MESSAGE_AT = "last_message_at" # По дате и времени создания последнего сообщения + + class ChatSubtype(StrEnum): """Тип чата""" @@ -114,6 +122,10 @@ class MessageEntityType(StrEnum): USER = "user" # Пользователь +class MessageSortField(StrEnum): + ID = "id" # По идентификатору сообщения + + class OAuthScope(StrEnum): """Скоуп доступа OAuth токена""" @@ -146,8 +158,10 @@ class OAuthScope(StrEnum): PROFILE_READ = "profile:read" # Просмотр информации о своем профиле PROFILE_STATUS_READ = "profile_status:read" # Просмотр статуса профиля PROFILE_STATUS_WRITE = "profile_status:write" # Изменение и удаление статуса профиля + PROFILE_AVATAR_WRITE = "profile_avatar:write" # Изменение и удаление аватара профиля USER_STATUS_READ = "user_status:read" # Просмотр статуса сотрудника USER_STATUS_WRITE = "user_status:write" # Изменение и удаление статуса сотрудника + USER_AVATAR_WRITE = "user_avatar:write" # Изменение и удаление аватара сотрудника CUSTOM_PROPERTIES_READ = "custom_properties:read" # Просмотр дополнительных полей AUDIT_EVENTS_READ = "audit_events:read" # Просмотр журнала аудита TASKS_READ = "tasks:read" # Просмотр задач @@ -295,11 +309,11 @@ class AccessTokenInfo: token: str user_id: int scopes: list[OAuthScope] - created_at: str + created_at: datetime name: str | None = None - revoked_at: str | None = None + revoked_at: datetime | None = None expires_in: int | None = None - last_used_at: str | None = None + last_used_at: datetime | None = None @dataclass @@ -416,7 +430,7 @@ class AuditDetailsUserUpdated: @dataclass class AuditEvent: id: str - created_at: str + created_at: datetime event_key: AuditEventKey entity_id: str entity_type: str @@ -427,6 +441,11 @@ class AuditEvent: user_agent: str +@dataclass +class AvatarData: + image_url: str + + @dataclass class BotResponseWebhook: outgoing_url: str @@ -476,14 +495,14 @@ class ButtonWebhookPayload: class Chat: id: int name: str - created_at: 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: str + last_message_at: datetime meet_room_url: str @@ -507,7 +526,7 @@ class ChatMemberWebhookPayload: event: MemberEventType chat_id: int user_ids: list[int] - created_at: str + created_at: datetime webhook_timestamp: int thread_id: int | None = None @@ -528,7 +547,7 @@ class CompanyMemberWebhookPayload: type: str # literal "company_member" event: UserEventType user_ids: list[int] - created_at: str + created_at: datetime webhook_timestamp: int @@ -585,7 +604,7 @@ class Forwarding: original_message_id: int original_chat_id: int author_id: int - original_created_at: str + original_created_at: datetime original_thread_id: int | None = None original_thread_message_id: int | None = None original_thread_parent_chat_id: int | None = None @@ -636,7 +655,7 @@ class LinkSharedWebhookPayload: message_id: int links: list[WebhookLink] user_id: int - created_at: str + created_at: datetime webhook_timestamp: int @@ -655,7 +674,7 @@ class Message: root_chat_id: int content: str user_id: int - created_at: str + created_at: datetime url: str files: list[File] buttons: list[list[Button]] | None = None @@ -664,8 +683,8 @@ class Message: parent_message_id: int | None = None display_avatar_url: str | None = None display_name: str | None = None - changed_at: str | None = None - deleted_at: str | None = None + changed_at: datetime | None = None + deleted_at: datetime | None = None @dataclass @@ -730,7 +749,7 @@ class MessageWebhookPayload: entity_id: int content: str user_id: int - created_at: str + created_at: datetime url: str chat_id: int webhook_timestamp: int @@ -763,18 +782,18 @@ class OpenViewRequest: @dataclass class PaginationMetaPaginate: - next_page: str | None = None + next_page: str @dataclass class PaginationMeta: - paginate: PaginationMetaPaginate | None = None + paginate: PaginationMetaPaginate @dataclass class Reaction: user_id: int - created_at: str + created_at: datetime code: str name: str | None = None @@ -793,7 +812,7 @@ class ReactionWebhookPayload: code: str name: str user_id: int - created_at: str + created_at: datetime webhook_timestamp: int @@ -812,7 +831,7 @@ class SearchPaginationMeta: class StatusUpdateRequestStatus: emoji: str title: str - expires_at: str | None = None + expires_at: datetime | None = None is_away: bool | None = None away_message: str | None = None @@ -822,11 +841,6 @@ class StatusUpdateRequest: status: StatusUpdateRequestStatus -@dataclass -class TagNamesFilter: - pass - - @dataclass class Task: id: int @@ -835,11 +849,11 @@ class Task: priority: int user_id: int status: TaskStatus - created_at: str + created_at: datetime performer_ids: list[int] all_day: bool custom_properties: list[CustomProperty] - due_at: str | None = None + due_at: datetime | None = None chat_id: int | None = None @@ -853,7 +867,7 @@ class TaskCreateRequestCustomProperty: class TaskCreateRequestTask: kind: TaskKind content: str | None = None - due_at: str | None = None + due_at: datetime | None = None priority: int | None = 1 performer_ids: list[int] | None = None chat_id: int | None = None @@ -876,12 +890,12 @@ class TaskUpdateRequestCustomProperty: class TaskUpdateRequestTask: kind: TaskKind | None = None content: str | None = None - due_at: str | None = None + due_at: datetime | None = None priority: int | None = None performer_ids: list[int] | None = None status: TaskStatus | None = None all_day: bool | None = None - done_at: str | None = None + done_at: datetime | None = None custom_properties: list[TaskUpdateRequestCustomProperty] | None = None @@ -896,7 +910,7 @@ class Thread: chat_id: int message_id: int message_chat_id: int - updated_at: str + updated_at: datetime @dataclass @@ -934,8 +948,8 @@ class User: custom_properties: list[CustomProperty] bot: bool sso: bool - created_at: str - last_activity_at: str + created_at: datetime + last_activity_at: datetime time_zone: str user_status: UserStatus | None = None image_url: str | None = None @@ -978,7 +992,7 @@ class UserStatus: emoji: str title: str is_away: bool - expires_at: str | None = None + expires_at: datetime | None = None away_message: UserStatusAwayMessage | None = None @@ -1014,7 +1028,7 @@ class ViewBlock: text: str | None = None name: str | None = None label: str | None = None - initial_date: str | None = None + initial_date: datetime | None = None @dataclass @@ -1131,12 +1145,23 @@ class ViewBlockTime: hint: str | None = None +@dataclass +class ViewSubmitWebhookPayload: + type: str # literal "view" + event: str # literal "submit" + user_id: int + data: dict[str, str] + webhook_timestamp: int + callback_id: str | None = None + private_metadata: str | None = None + + @dataclass class WebhookEvent: id: str event_type: str payload: WebhookPayloadUnion - created_at: str + created_at: datetime @dataclass @@ -1151,19 +1176,29 @@ class WebhookMessageThread: message_chat_id: int +@dataclass +class UpdateProfileAvatarRequest: + image: bytes + + +@dataclass +class UpdateUserAvatarRequest: + image: bytes + + AuditEventDetailsUnion = Union[AuditDetailsEmpty, AuditDetailsUserUpdated, AuditDetailsRoleChanged, AuditDetailsTagName, AuditDetailsInitiator, AuditDetailsInviter, AuditDetailsChatRenamed, AuditDetailsChatPermission, AuditDetailsTagChat, AuditDetailsChatId, AuditDetailsTokenScopes, AuditDetailsKms, AuditDetailsDlp, AuditDetailsSearch] ViewBlockUnion = Union[ViewBlockHeader, ViewBlockPlainText, ViewBlockMarkdown, ViewBlockDivider, ViewBlockInput, ViewBlockSelect, ViewBlockRadio, ViewBlockCheckbox, ViewBlockDate, ViewBlockTime, ViewBlockFileInput] -WebhookPayloadUnion = Union[MessageWebhookPayload, ReactionWebhookPayload, ButtonWebhookPayload, ChatMemberWebhookPayload, CompanyMemberWebhookPayload, LinkSharedWebhookPayload] +WebhookPayloadUnion = Union[MessageWebhookPayload, ReactionWebhookPayload, ButtonWebhookPayload, ViewSubmitWebhookPayload, ChatMemberWebhookPayload, CompanyMemberWebhookPayload, LinkSharedWebhookPayload] @dataclass class GetAuditEventsParams: - start_time: str | None = None - end_time: str | None = None + start_time: datetime | None = None + end_time: datetime | None = None event_key: AuditEventKey | None = None actor_id: str | None = None actor_type: str | None = None @@ -1175,10 +1210,11 @@ class GetAuditEventsParams: @dataclass class ListChatsParams: - sort_id: SortOrder | None = None + sort: ChatSortField | None = None + order: SortOrder | None = None availability: ChatAvailability | None = None - last_message_at_after: str | None = None - last_message_at_before: str | None = None + last_message_at_after: datetime | None = None + last_message_at_before: datetime | None = None personal: bool | None = None limit: int | None = None cursor: str | None = None @@ -1198,7 +1234,7 @@ class ListPropertiesParams: @dataclass class ListTagsParams: - names: TagNamesFilter | None = None + names: list[str] | None = None limit: int | None = None cursor: str | None = None @@ -1212,7 +1248,8 @@ class GetTagUsersParams: @dataclass class ListChatMessagesParams: chat_id: int - sort_id: SortOrder | None = None + sort: MessageSortField | None = None + order: SortOrder | None = None limit: int | None = None cursor: str | None = None @@ -1241,8 +1278,8 @@ class SearchChatsParams: limit: int | None = None cursor: str | None = None order: SortOrder | None = None - created_from: str | None = None - created_to: str | None = None + created_from: datetime | None = None + created_to: datetime | None = None active: bool | None = None chat_subtype: ChatSubtype | None = None personal: bool | None = None @@ -1254,8 +1291,8 @@ class SearchMessagesParams: limit: int | None = None cursor: str | None = None order: SortOrder | None = None - created_from: str | None = None - created_to: str | None = None + created_from: datetime | None = None + created_to: datetime | None = None chat_ids: list[int] | None = None user_ids: list[int] | None = None active: bool | None = None @@ -1268,8 +1305,8 @@ class SearchUsersParams: cursor: str | None = None sort: SearchSortOrder | None = None order: SortOrder | None = None - created_from: str | None = None - created_to: str | None = None + created_from: datetime | None = None + created_to: datetime | None = None company_roles: list[UserRole] | None = None @@ -1295,19 +1332,19 @@ class GetWebhookEventsParams: @dataclass class GetAuditEventsResponse: data: list[AuditEvent] - meta: PaginationMeta | None = None + meta: PaginationMeta @dataclass class ListChatsResponse: data: list[Chat] - meta: PaginationMeta | None = None + meta: PaginationMeta @dataclass class ListMembersResponse: data: list[User] - meta: PaginationMeta | None = None + meta: PaginationMeta @dataclass @@ -1318,25 +1355,25 @@ class ListPropertiesResponse: @dataclass class ListTagsResponse: data: list[GroupTag] - meta: PaginationMeta | None = None + meta: PaginationMeta @dataclass class GetTagUsersResponse: data: list[User] - meta: PaginationMeta | None = None + meta: PaginationMeta @dataclass class ListChatMessagesResponse: data: list[Message] - meta: PaginationMeta | None = None + meta: PaginationMeta @dataclass class ListReactionsResponse: data: list[Reaction] - meta: PaginationMeta | None = None + meta: PaginationMeta @dataclass @@ -1360,16 +1397,16 @@ class SearchUsersResponse: @dataclass class ListTasksResponse: data: list[Task] - meta: PaginationMeta | None = None + meta: PaginationMeta @dataclass class ListUsersResponse: data: list[User] - meta: PaginationMeta | None = None + meta: PaginationMeta @dataclass class GetWebhookEventsResponse: data: list[WebhookEvent] - meta: PaginationMeta | None = None + meta: PaginationMeta diff --git a/sdk/python/generated/pachca/utils.py b/sdk/python/generated/pachca/utils.py index d1494873..9288f902 100644 --- a/sdk/python/generated/pachca/utils.py +++ b/sdk/python/generated/pachca/utils.py @@ -3,6 +3,7 @@ import dataclasses import keyword from dataclasses import asdict, fields +from datetime import datetime from typing import Type, TypeVar, get_args, get_origin, get_type_hints import httpx @@ -58,6 +59,16 @@ def deserialize(cls: Type[T], data: dict) -> T: item_tp = _resolve_list_item_type(hints[f.name]) if item_tp is not None and _is_dataclass_type(item_tp): v = [deserialize(item_tp, i) if isinstance(i, dict) else i for i in v] + elif isinstance(v, str): + hint = hints.get(f.name) + raw_hint = hint + if get_origin(hint) is not None: + for a in get_args(hint): + if a is not type(None): + raw_hint = a + break + if raw_hint is datetime: + v = datetime.fromisoformat(v) kwargs[k] = v return cls(**kwargs) @@ -70,6 +81,8 @@ def _strip_nones(val: object) -> object: } if isinstance(val, list): return [_strip_nones(v) for v in val] + if isinstance(val, datetime): + return val.isoformat() return val diff --git a/sdk/swift/README.md b/sdk/swift/README.md index 3296d6fc..d723fc75 100644 --- a/sdk/swift/README.md +++ b/sdk/swift/README.md @@ -12,7 +12,7 @@ Swift клиент для [Pachca API](https://dev.pachca.com). ```swift // Package.swift dependencies: [ - .package(url: "https://github.com/pachca/openapi", from: "1.0.1") + .package(url: "https://github.com/pachca/openapi", from: "1.0.0") ] ``` @@ -70,9 +70,10 @@ var chats: [Chat] = [] var cursor: String? = nil repeat { let response = try await pachca.chats.listChats(cursor: cursor) + if response.data.isEmpty { break } chats.append(contentsOf: response.data) - cursor = response.meta?.paginate?.nextPage -} while cursor != nil + cursor = response.meta.paginate.nextPage +} while true // Автоматически let allChats = try await pachca.chats.listChatsAll() diff --git a/sdk/swift/examples/Sources/EchoBot/main.swift b/sdk/swift/examples/Sources/EchoBot/main.swift index ca9b77f5..d4dd52c7 100644 --- a/sdk/swift/examples/Sources/EchoBot/main.swift +++ b/sdk/swift/examples/Sources/EchoBot/main.swift @@ -14,6 +14,12 @@ guard let chatIdStr = ProcessInfo.processInfo.environment["PACHCA_CHAT_ID"], let client = PachcaClient(token: token) print("Echo Bot started for chat \(chatId)") +// ── Step 0: Fetch chat (verifies datetime deserialization) ────── + +print("Step 0: Fetching chat...") +let chat = try await client.chats.getChat(id: chatId) +print(" Chat: \(chat.name), createdAt=\(chat.createdAt) (\(type(of: chat.createdAt))), lastMessageAt=\(chat.lastMessageAt) (\(type(of: chat.lastMessageAt)))") + // ── Step 1: Send a message ───────────────────────────────────── print("Step 1: Sending message...") diff --git a/sdk/swift/generated/Sources/Pachca/GeneratedSources/Client.swift b/sdk/swift/generated/Sources/Pachca/GeneratedSources/Client.swift index 9f45447f..72355317 100644 --- a/sdk/swift/generated/Sources/Pachca/GeneratedSources/Client.swift +++ b/sdk/swift/generated/Sources/Pachca/GeneratedSources/Client.swift @@ -17,8 +17,8 @@ public struct SecurityService { public func getAuditEvents(startTime: String? = nil, endTime: String? = nil, eventKey: AuditEventKey? = nil, actorId: String? = nil, actorType: String? = nil, entityId: String? = nil, entityType: String? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> GetAuditEventsResponse { var components = URLComponents(string: "\(baseURL)/audit_events")! var queryItems: [URLQueryItem] = [] - if let startTime { queryItems.append(URLQueryItem(name: "start_time", value: startTime)) } - if let endTime { queryItems.append(URLQueryItem(name: "end_time", value: endTime)) } + if let startTime { queryItems.append(URLQueryItem(name: "start_time", value: String(startTime))) } + if let endTime { queryItems.append(URLQueryItem(name: "end_time", value: String(endTime))) } if let eventKey { queryItems.append(URLQueryItem(name: "event_key", value: eventKey.rawValue)) } if let actorId { queryItems.append(URLQueryItem(name: "actor_id", value: String(actorId))) } if let actorType { queryItems.append(URLQueryItem(name: "actor_type", value: String(actorType))) } @@ -47,8 +47,9 @@ public struct SecurityService { repeat { let response = try await getAuditEvents(startTime: startTime, endTime: endTime, eventKey: eventKey, actorId: actorId, actorType: actorType, entityId: entityId, entityType: entityType, limit: limit, cursor: cursor) items.append(contentsOf: response.data) - cursor = response.meta?.paginate?.nextPage - } while cursor != nil + if response.data.isEmpty { break } + cursor = response.meta.paginate.nextPage + } while true return items } } @@ -90,8 +91,9 @@ public struct BotsService { repeat { let response = try await getWebhookEvents(limit: limit, cursor: cursor) items.append(contentsOf: response.data) - cursor = response.meta?.paginate?.nextPage - } while cursor != nil + if response.data.isEmpty { break } + cursor = response.meta.paginate.nextPage + } while true return items } @@ -141,13 +143,14 @@ public struct ChatsService { self.session = session } - public func listChats(sortId: SortOrder? = nil, availability: ChatAvailability? = nil, lastMessageAtAfter: String? = nil, lastMessageAtBefore: String? = nil, personal: Bool? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListChatsResponse { + public func listChats(sort: ChatSortField? = nil, order: SortOrder? = nil, availability: ChatAvailability? = nil, lastMessageAtAfter: String? = nil, lastMessageAtBefore: String? = nil, personal: Bool? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListChatsResponse { var components = URLComponents(string: "\(baseURL)/chats")! var queryItems: [URLQueryItem] = [] - if let sortId { queryItems.append(URLQueryItem(name: "sort[{field}]", value: sortId.rawValue)) } + if let sort { queryItems.append(URLQueryItem(name: "sort", value: sort.rawValue)) } + if let order { queryItems.append(URLQueryItem(name: "order", value: order.rawValue)) } if let availability { queryItems.append(URLQueryItem(name: "availability", value: availability.rawValue)) } - if let lastMessageAtAfter { queryItems.append(URLQueryItem(name: "last_message_at_after", value: lastMessageAtAfter)) } - if let lastMessageAtBefore { queryItems.append(URLQueryItem(name: "last_message_at_before", value: lastMessageAtBefore)) } + if let lastMessageAtAfter { queryItems.append(URLQueryItem(name: "last_message_at_after", value: String(lastMessageAtAfter))) } + if let lastMessageAtBefore { queryItems.append(URLQueryItem(name: "last_message_at_before", value: String(lastMessageAtBefore))) } if let personal { queryItems.append(URLQueryItem(name: "personal", value: String(personal))) } if let limit { queryItems.append(URLQueryItem(name: "limit", value: String(limit))) } if let cursor { queryItems.append(URLQueryItem(name: "cursor", value: String(cursor))) } @@ -166,14 +169,15 @@ public struct ChatsService { } } - public func listChatsAll(sortId: SortOrder? = nil, availability: ChatAvailability? = nil, lastMessageAtAfter: String? = nil, lastMessageAtBefore: String? = nil, personal: Bool? = nil, limit: Int? = nil) async throws -> [Chat] { + public func listChatsAll(sort: ChatSortField? = nil, order: SortOrder? = nil, availability: ChatAvailability? = nil, lastMessageAtAfter: String? = nil, lastMessageAtBefore: String? = nil, personal: Bool? = nil, limit: Int? = nil) async throws -> [Chat] { var items: [Chat] = [] var cursor: String? = nil repeat { - let response = try await listChats(sortId: sortId, availability: availability, lastMessageAtAfter: lastMessageAtAfter, lastMessageAtBefore: lastMessageAtBefore, personal: personal, limit: limit, cursor: cursor) + let response = try await listChats(sort: sort, order: order, availability: availability, lastMessageAtAfter: lastMessageAtAfter, lastMessageAtBefore: lastMessageAtBefore, personal: personal, limit: limit, cursor: cursor) items.append(contentsOf: response.data) - cursor = response.meta?.paginate?.nextPage - } while cursor != nil + if response.data.isEmpty { break } + cursor = response.meta.paginate.nextPage + } while true return items } @@ -393,7 +397,7 @@ public struct MembersService { } public func listMembers(id: Int, role: ChatMemberRoleFilter? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListMembersResponse { - var components = URLComponents(string: "\(baseURL)/chats/{id}/members")! + var components = URLComponents(string: "\(baseURL)/chats/\(id)/members")! var queryItems: [URLQueryItem] = [] if let role { queryItems.append(URLQueryItem(name: "role", value: role.rawValue)) } if let limit { queryItems.append(URLQueryItem(name: "limit", value: String(limit))) } @@ -419,8 +423,9 @@ public struct MembersService { repeat { let response = try await listMembers(id: id, role: role, limit: limit, cursor: cursor) items.append(contentsOf: response.data) - cursor = response.meta?.paginate?.nextPage - } while cursor != nil + if response.data.isEmpty { break } + cursor = response.meta.paginate.nextPage + } while true return items } @@ -465,7 +470,7 @@ public struct MembersService { request.httpMethod = "PUT" headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = try JSONSerialization.data(withJSONObject: ["role": role]) + request.httpBody = try JSONSerialization.data(withJSONObject: ["role": role.rawValue]) let (data, urlResponse) = try await dataWithRetry(session: session, for: request) let statusCode = (urlResponse as! HTTPURLResponse).statusCode switch statusCode { @@ -538,10 +543,10 @@ public struct GroupTagsService { self.session = session } - public func listTags(names: TagNamesFilter? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListTagsResponse { + public func listTags(names: [String]? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListTagsResponse { var components = URLComponents(string: "\(baseURL)/group_tags")! var queryItems: [URLQueryItem] = [] - if let names { queryItems.append(URLQueryItem(name: "names", value: String(data: try serialize(names), encoding: .utf8)!)) } + if let names { names.forEach { queryItems.append(URLQueryItem(name: "names[]", value: String($0))) } } if let limit { queryItems.append(URLQueryItem(name: "limit", value: String(limit))) } if let cursor { queryItems.append(URLQueryItem(name: "cursor", value: String(cursor))) } if !queryItems.isEmpty { components.queryItems = queryItems } @@ -559,14 +564,15 @@ public struct GroupTagsService { } } - public func listTagsAll(names: TagNamesFilter? = nil, limit: Int? = nil) async throws -> [GroupTag] { + public func listTagsAll(names: [String]? = nil, limit: Int? = nil) async throws -> [GroupTag] { var items: [GroupTag] = [] var cursor: String? = nil repeat { let response = try await listTags(names: names, limit: limit, cursor: cursor) items.append(contentsOf: response.data) - cursor = response.meta?.paginate?.nextPage - } while cursor != nil + if response.data.isEmpty { break } + cursor = response.meta.paginate.nextPage + } while true return items } @@ -585,8 +591,8 @@ public struct GroupTagsService { } } - public func getTagUsers(id: Int, limit: Int? = nil, cursor: String? = nil) async throws -> ListMembersResponse { - var components = URLComponents(string: "\(baseURL)/group_tags/{id}/users")! + public func getTagUsers(id: Int, limit: Int? = nil, cursor: String? = nil) async throws -> GetTagUsersResponse { + var components = URLComponents(string: "\(baseURL)/group_tags/\(id)/users")! var queryItems: [URLQueryItem] = [] if let limit { queryItems.append(URLQueryItem(name: "limit", value: String(limit))) } if let cursor { queryItems.append(URLQueryItem(name: "cursor", value: String(cursor))) } @@ -597,7 +603,7 @@ public struct GroupTagsService { let statusCode = (urlResponse as! HTTPURLResponse).statusCode switch statusCode { case 200: - return try deserialize(ListMembersResponse.self, from: data) + return try deserialize(GetTagUsersResponse.self, from: data) case 401: throw try deserialize(OAuthError.self, from: data) default: @@ -611,8 +617,9 @@ public struct GroupTagsService { repeat { let response = try await getTagUsers(id: id, limit: limit, cursor: cursor) items.append(contentsOf: response.data) - cursor = response.meta?.paginate?.nextPage - } while cursor != nil + if response.data.isEmpty { break } + cursor = response.meta.paginate.nextPage + } while true return items } @@ -680,11 +687,12 @@ public struct MessagesService { self.session = session } - public func listChatMessages(chatId: Int, sortId: SortOrder? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListChatMessagesResponse { + public func listChatMessages(chatId: Int, sort: MessageSortField? = nil, order: SortOrder? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListChatMessagesResponse { var components = URLComponents(string: "\(baseURL)/messages")! var queryItems: [URLQueryItem] = [] queryItems.append(URLQueryItem(name: "chat_id", value: String(chatId))) - if let sortId { queryItems.append(URLQueryItem(name: "sort[{field}]", value: sortId.rawValue)) } + if let sort { queryItems.append(URLQueryItem(name: "sort", value: sort.rawValue)) } + if let order { queryItems.append(URLQueryItem(name: "order", value: order.rawValue)) } if let limit { queryItems.append(URLQueryItem(name: "limit", value: String(limit))) } if let cursor { queryItems.append(URLQueryItem(name: "cursor", value: String(cursor))) } if !queryItems.isEmpty { components.queryItems = queryItems } @@ -702,14 +710,15 @@ public struct MessagesService { } } - public func listChatMessagesAll(chatId: Int, sortId: SortOrder? = nil, limit: Int? = nil) async throws -> [Message] { + public func listChatMessagesAll(chatId: Int, sort: MessageSortField? = nil, order: SortOrder? = nil, limit: Int? = nil) async throws -> [Message] { var items: [Message] = [] var cursor: String? = nil repeat { - let response = try await listChatMessages(chatId: chatId, sortId: sortId, limit: limit, cursor: cursor) + let response = try await listChatMessages(chatId: chatId, sort: sort, order: order, limit: limit, cursor: cursor) items.append(contentsOf: response.data) - cursor = response.meta?.paginate?.nextPage - } while cursor != nil + if response.data.isEmpty { break } + cursor = response.meta.paginate.nextPage + } while true return items } @@ -855,7 +864,7 @@ public struct ReactionsService { } public func listReactions(id: Int, limit: Int? = nil, cursor: String? = nil) async throws -> ListReactionsResponse { - var components = URLComponents(string: "\(baseURL)/messages/{id}/reactions")! + var components = URLComponents(string: "\(baseURL)/messages/\(id)/reactions")! var queryItems: [URLQueryItem] = [] if let limit { queryItems.append(URLQueryItem(name: "limit", value: String(limit))) } if let cursor { queryItems.append(URLQueryItem(name: "cursor", value: String(cursor))) } @@ -880,8 +889,9 @@ public struct ReactionsService { repeat { let response = try await listReactions(id: id, limit: limit, cursor: cursor) items.append(contentsOf: response.data) - cursor = response.meta?.paginate?.nextPage - } while cursor != nil + if response.data.isEmpty { break } + cursor = response.meta.paginate.nextPage + } while true return items } @@ -904,7 +914,7 @@ public struct ReactionsService { } public func removeReaction(id: Int, code: String, name: String? = nil) async throws -> Void { - var components = URLComponents(string: "\(baseURL)/messages/{id}/reactions")! + var components = URLComponents(string: "\(baseURL)/messages/\(id)/reactions")! var queryItems: [URLQueryItem] = [] queryItems.append(URLQueryItem(name: "code", value: String(code))) if let name { queryItems.append(URLQueryItem(name: "name", value: String(name))) } @@ -937,7 +947,7 @@ public struct ReadMembersService { } public func listReadMembers(id: Int, limit: Int? = nil, cursor: String? = nil) async throws -> String { - var components = URLComponents(string: "\(baseURL)/messages/{id}/read_member_ids")! + var components = URLComponents(string: "\(baseURL)/messages/\(id)/read_member_ids")! var queryItems: [URLQueryItem] = [] if let limit { queryItems.append(URLQueryItem(name: "limit", value: String(limit))) } if let cursor { queryItems.append(URLQueryItem(name: "cursor", value: String(cursor))) } @@ -1056,6 +1066,37 @@ public struct ProfileService { } } + public func updateProfileAvatar(image: Data) async throws -> AvatarData { + var request = URLRequest(url: URL(string: "\(baseURL)/profile/avatar")!) + request.httpMethod = "PUT" + headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } + let boundary = UUID().uuidString + request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + var data = Data() + func appendField(_ name: String, _ value: String) { + data.append("--\(boundary)\r\n".data(using: .utf8)!) + data.append("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n".data(using: .utf8)!) + data.append("\(value)\r\n".data(using: .utf8)!) + } + data.append("--\(boundary)\r\n".data(using: .utf8)!) + data.append("Content-Disposition: form-data; name=\"image\"; filename=\"upload\"\r\n".data(using: .utf8)!) + data.append("Content-Type: application/octet-stream\r\n\r\n".data(using: .utf8)!) + data.append(image) + data.append("\r\n".data(using: .utf8)!) + data.append("--\(boundary)--\r\n".data(using: .utf8)!) + request.httpBody = data + let (responseData, urlResponse) = try await dataWithRetry(session: session, for: request) + let statusCode = (urlResponse as! HTTPURLResponse).statusCode + switch statusCode { + case 200: + return try deserialize(AvatarDataDataWrapper.self, from: responseData).data + case 401: + throw try deserialize(OAuthError.self, from: responseData) + default: + throw try deserialize(ApiError.self, from: responseData) + } + } + public func updateStatus(request body: StatusUpdateRequest) async throws -> UserStatus { var request = URLRequest(url: URL(string: "\(baseURL)/profile/status")!) request.httpMethod = "PUT" @@ -1074,6 +1115,22 @@ public struct ProfileService { } } + public func deleteProfileAvatar() async throws -> Void { + var request = URLRequest(url: URL(string: "\(baseURL)/profile/avatar")!) + request.httpMethod = "DELETE" + headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } + let (data, urlResponse) = try await dataWithRetry(session: session, for: request) + let statusCode = (urlResponse as! HTTPURLResponse).statusCode + switch statusCode { + case 204: + return + case 401: + throw try deserialize(OAuthError.self, from: data) + default: + throw try deserialize(ApiError.self, from: data) + } + } + public func deleteStatus() async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/profile/status")!) request.httpMethod = "DELETE" @@ -1102,15 +1159,15 @@ public struct SearchService { self.session = session } - public func searchChats(query: String? = nil, limit: Int? = nil, cursor: String? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, active: Bool? = nil, chatSubtype: ChatSubtype? = nil, personal: Bool? = nil) async throws -> ListChatsResponse { + public func searchChats(query: String? = nil, limit: Int? = nil, cursor: String? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, active: Bool? = nil, chatSubtype: ChatSubtype? = nil, personal: Bool? = nil) async throws -> SearchChatsResponse { var components = URLComponents(string: "\(baseURL)/search/chats")! var queryItems: [URLQueryItem] = [] if let query { queryItems.append(URLQueryItem(name: "query", value: String(query))) } if let limit { queryItems.append(URLQueryItem(name: "limit", value: String(limit))) } if let cursor { queryItems.append(URLQueryItem(name: "cursor", value: String(cursor))) } if let order { queryItems.append(URLQueryItem(name: "order", value: order.rawValue)) } - if let createdFrom { queryItems.append(URLQueryItem(name: "created_from", value: createdFrom)) } - if let createdTo { queryItems.append(URLQueryItem(name: "created_to", value: createdTo)) } + if let createdFrom { queryItems.append(URLQueryItem(name: "created_from", value: String(createdFrom))) } + if let createdTo { queryItems.append(URLQueryItem(name: "created_to", value: String(createdTo))) } if let active { queryItems.append(URLQueryItem(name: "active", value: String(active))) } if let chatSubtype { queryItems.append(URLQueryItem(name: "chat_subtype", value: chatSubtype.rawValue)) } if let personal { queryItems.append(URLQueryItem(name: "personal", value: String(personal))) } @@ -1121,7 +1178,7 @@ public struct SearchService { let statusCode = (urlResponse as! HTTPURLResponse).statusCode switch statusCode { case 200: - return try deserialize(ListChatsResponse.self, from: data) + return try deserialize(SearchChatsResponse.self, from: data) case 401: throw try deserialize(OAuthError.self, from: data) default: @@ -1135,22 +1192,23 @@ public struct SearchService { repeat { let response = try await searchChats(query: query, limit: limit, cursor: cursor, order: order, createdFrom: createdFrom, createdTo: createdTo, active: active, chatSubtype: chatSubtype, personal: personal) items.append(contentsOf: response.data) - cursor = response.meta?.paginate?.nextPage - } while cursor != nil + if response.data.isEmpty { break } + cursor = response.meta.paginate.nextPage + } while true return items } - public func searchMessages(query: String? = nil, limit: Int? = nil, cursor: String? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, chatIds: [Int]? = nil, userIds: [Int]? = nil, active: Bool? = nil) async throws -> ListChatMessagesResponse { + public func searchMessages(query: String? = nil, limit: Int? = nil, cursor: String? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, chatIds: [Int]? = nil, userIds: [Int]? = nil, active: Bool? = nil) async throws -> SearchMessagesResponse { var components = URLComponents(string: "\(baseURL)/search/messages")! var queryItems: [URLQueryItem] = [] if let query { queryItems.append(URLQueryItem(name: "query", value: String(query))) } if let limit { queryItems.append(URLQueryItem(name: "limit", value: String(limit))) } if let cursor { queryItems.append(URLQueryItem(name: "cursor", value: String(cursor))) } if let order { queryItems.append(URLQueryItem(name: "order", value: order.rawValue)) } - if let createdFrom { queryItems.append(URLQueryItem(name: "created_from", value: createdFrom)) } - if let createdTo { queryItems.append(URLQueryItem(name: "created_to", value: createdTo)) } - if let chatIds { chatIds.forEach { queryItems.append(URLQueryItem(name: "chat_ids", value: String($0))) } } - if let userIds { userIds.forEach { queryItems.append(URLQueryItem(name: "user_ids", value: String($0))) } } + if let createdFrom { queryItems.append(URLQueryItem(name: "created_from", value: String(createdFrom))) } + if let createdTo { queryItems.append(URLQueryItem(name: "created_to", value: String(createdTo))) } + if let chatIds { chatIds.forEach { queryItems.append(URLQueryItem(name: "chat_ids[]", value: String($0))) } } + if let userIds { userIds.forEach { queryItems.append(URLQueryItem(name: "user_ids[]", value: String($0))) } } if let active { queryItems.append(URLQueryItem(name: "active", value: String(active))) } if !queryItems.isEmpty { components.queryItems = queryItems } var request = URLRequest(url: components.url!) @@ -1159,7 +1217,7 @@ public struct SearchService { let statusCode = (urlResponse as! HTTPURLResponse).statusCode switch statusCode { case 200: - return try deserialize(ListChatMessagesResponse.self, from: data) + return try deserialize(SearchMessagesResponse.self, from: data) case 401: throw try deserialize(OAuthError.self, from: data) default: @@ -1173,12 +1231,13 @@ public struct SearchService { repeat { let response = try await searchMessages(query: query, limit: limit, cursor: cursor, order: order, createdFrom: createdFrom, createdTo: createdTo, chatIds: chatIds, userIds: userIds, active: active) items.append(contentsOf: response.data) - cursor = response.meta?.paginate?.nextPage - } while cursor != nil + if response.data.isEmpty { break } + cursor = response.meta.paginate.nextPage + } while true return items } - public func searchUsers(query: String? = nil, limit: Int? = nil, cursor: String? = nil, sort: SearchSortOrder? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, companyRoles: [UserRole]? = nil) async throws -> ListMembersResponse { + public func searchUsers(query: String? = nil, limit: Int? = nil, cursor: String? = nil, sort: SearchSortOrder? = nil, order: SortOrder? = nil, createdFrom: String? = nil, createdTo: String? = nil, companyRoles: [UserRole]? = nil) async throws -> SearchUsersResponse { var components = URLComponents(string: "\(baseURL)/search/users")! var queryItems: [URLQueryItem] = [] if let query { queryItems.append(URLQueryItem(name: "query", value: String(query))) } @@ -1186,9 +1245,9 @@ public struct SearchService { if let cursor { queryItems.append(URLQueryItem(name: "cursor", value: String(cursor))) } if let sort { queryItems.append(URLQueryItem(name: "sort", value: sort.rawValue)) } if let order { queryItems.append(URLQueryItem(name: "order", value: order.rawValue)) } - if let createdFrom { queryItems.append(URLQueryItem(name: "created_from", value: createdFrom)) } - if let createdTo { queryItems.append(URLQueryItem(name: "created_to", value: createdTo)) } - if let companyRoles { companyRoles.forEach { queryItems.append(URLQueryItem(name: "company_roles", value: $0.rawValue)) } } + if let createdFrom { queryItems.append(URLQueryItem(name: "created_from", value: String(createdFrom))) } + if let createdTo { queryItems.append(URLQueryItem(name: "created_to", value: String(createdTo))) } + if let companyRoles { companyRoles.forEach { queryItems.append(URLQueryItem(name: "company_roles[]", value: $0.rawValue)) } } if !queryItems.isEmpty { components.queryItems = queryItems } var request = URLRequest(url: components.url!) headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } @@ -1196,7 +1255,7 @@ public struct SearchService { let statusCode = (urlResponse as! HTTPURLResponse).statusCode switch statusCode { case 200: - return try deserialize(ListMembersResponse.self, from: data) + return try deserialize(SearchUsersResponse.self, from: data) case 401: throw try deserialize(OAuthError.self, from: data) default: @@ -1210,8 +1269,9 @@ public struct SearchService { repeat { let response = try await searchUsers(query: query, limit: limit, cursor: cursor, sort: sort, order: order, createdFrom: createdFrom, createdTo: createdTo, companyRoles: companyRoles) items.append(contentsOf: response.data) - cursor = response.meta?.paginate?.nextPage - } while cursor != nil + if response.data.isEmpty { break } + cursor = response.meta.paginate.nextPage + } while true return items } } @@ -1253,8 +1313,9 @@ public struct TasksService { repeat { let response = try await listTasks(limit: limit, cursor: cursor) items.append(contentsOf: response.data) - cursor = response.meta?.paginate?.nextPage - } while cursor != nil + if response.data.isEmpty { break } + cursor = response.meta.paginate.nextPage + } while true return items } @@ -1337,7 +1398,7 @@ public struct UsersService { self.session = session } - public func listUsers(query: String? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListMembersResponse { + public func listUsers(query: String? = nil, limit: Int? = nil, cursor: String? = nil) async throws -> ListUsersResponse { var components = URLComponents(string: "\(baseURL)/users")! var queryItems: [URLQueryItem] = [] if let query { queryItems.append(URLQueryItem(name: "query", value: String(query))) } @@ -1350,7 +1411,7 @@ public struct UsersService { let statusCode = (urlResponse as! HTTPURLResponse).statusCode switch statusCode { case 200: - return try deserialize(ListMembersResponse.self, from: data) + return try deserialize(ListUsersResponse.self, from: data) case 401: throw try deserialize(OAuthError.self, from: data) default: @@ -1364,8 +1425,9 @@ public struct UsersService { repeat { let response = try await listUsers(query: query, limit: limit, cursor: cursor) items.append(contentsOf: response.data) - cursor = response.meta?.paginate?.nextPage - } while cursor != nil + if response.data.isEmpty { break } + cursor = response.meta.paginate.nextPage + } while true return items } @@ -1435,6 +1497,37 @@ public struct UsersService { } } + public func updateUserAvatar(userId: Int, image: Data) async throws -> AvatarData { + var request = URLRequest(url: URL(string: "\(baseURL)/users/\(userId)/avatar")!) + request.httpMethod = "PUT" + headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } + let boundary = UUID().uuidString + request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + var data = Data() + func appendField(_ name: String, _ value: String) { + data.append("--\(boundary)\r\n".data(using: .utf8)!) + data.append("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n".data(using: .utf8)!) + data.append("\(value)\r\n".data(using: .utf8)!) + } + data.append("--\(boundary)\r\n".data(using: .utf8)!) + data.append("Content-Disposition: form-data; name=\"image\"; filename=\"upload\"\r\n".data(using: .utf8)!) + data.append("Content-Type: application/octet-stream\r\n\r\n".data(using: .utf8)!) + data.append(image) + data.append("\r\n".data(using: .utf8)!) + data.append("--\(boundary)--\r\n".data(using: .utf8)!) + request.httpBody = data + let (responseData, urlResponse) = try await dataWithRetry(session: session, for: request) + let statusCode = (urlResponse as! HTTPURLResponse).statusCode + switch statusCode { + case 200: + return try deserialize(AvatarDataDataWrapper.self, from: responseData).data + case 401: + throw try deserialize(OAuthError.self, from: responseData) + default: + throw try deserialize(ApiError.self, from: responseData) + } + } + public func updateUserStatus(userId: Int, request body: StatusUpdateRequest) async throws -> UserStatus { var request = URLRequest(url: URL(string: "\(baseURL)/users/\(userId)/status")!) request.httpMethod = "PUT" @@ -1469,6 +1562,22 @@ public struct UsersService { } } + public func deleteUserAvatar(userId: Int) async throws -> Void { + var request = URLRequest(url: URL(string: "\(baseURL)/users/\(userId)/avatar")!) + request.httpMethod = "DELETE" + headers.forEach { request.setValue($1, forHTTPHeaderField: $0) } + let (data, urlResponse) = try await dataWithRetry(session: session, for: request) + let statusCode = (urlResponse as! HTTPURLResponse).statusCode + switch statusCode { + case 204: + return + case 401: + throw try deserialize(OAuthError.self, from: data) + default: + throw try deserialize(ApiError.self, from: data) + } + } + public func deleteUserStatus(userId: Int) async throws -> Void { var request = URLRequest(url: URL(string: "\(baseURL)/users/\(userId)/status")!) request.httpMethod = "DELETE" diff --git a/sdk/swift/generated/Sources/Pachca/GeneratedSources/Models.swift b/sdk/swift/generated/Sources/Pachca/GeneratedSources/Models.swift index 87c3e94a..23233a51 100644 --- a/sdk/swift/generated/Sources/Pachca/GeneratedSources/Models.swift +++ b/sdk/swift/generated/Sources/Pachca/GeneratedSources/Models.swift @@ -105,6 +105,13 @@ public enum ChatMemberRoleFilter: String, Codable, CaseIterable { case member } +public enum ChatSortField: String, Codable, CaseIterable { + /// По идентификатору чата + case id + /// По дате и времени создания последнего сообщения + case lastMessageAt = "last_message_at" +} + public enum ChatSubtype: String, Codable, CaseIterable { /// Канал или беседа case discussion @@ -153,6 +160,11 @@ public enum MessageEntityType: String, Codable, CaseIterable { case user } +public enum MessageSortField: String, Codable, CaseIterable { + /// По идентификатору сообщения + case id +} + public enum OAuthScope: String, Codable, CaseIterable { /// Просмотр чатов и списка чатов case chatsRead = "chats:read" @@ -212,10 +224,14 @@ public enum OAuthScope: String, Codable, CaseIterable { case profileStatusRead = "profile_status:read" /// Изменение и удаление статуса профиля case profileStatusWrite = "profile_status:write" + /// Изменение и удаление аватара профиля + case profileAvatarWrite = "profile_avatar:write" /// Просмотр статуса сотрудника case userStatusRead = "user_status:read" /// Изменение и удаление статуса сотрудника case userStatusWrite = "user_status:write" + /// Изменение и удаление аватара сотрудника + case userAvatarWrite = "user_avatar:write" /// Просмотр дополнительных полей case customPropertiesRead = "custom_properties:read" /// Просмотр журнала аудита @@ -425,12 +441,12 @@ public struct AccessTokenInfo: Codable { public let name: String? public let userId: Int64 public let scopes: [OAuthScope] - public let createdAt: Date + public let createdAt: String public let revokedAt: String? public let expiresIn: Int? public let lastUsedAt: String? - public init(id: Int64, token: String, name: String? = nil, userId: Int64, scopes: [OAuthScope], createdAt: Date, revokedAt: String? = nil, expiresIn: Int? = nil, lastUsedAt: String? = nil) { + public init(id: Int64, token: String, name: String? = nil, userId: Int64, scopes: [OAuthScope], createdAt: String, revokedAt: String? = nil, expiresIn: Int? = nil, lastUsedAt: String? = nil) { self.id = id self.token = token self.name = name @@ -707,7 +723,7 @@ public struct AuditDetailsUserUpdated: Codable { public struct AuditEvent: Codable { public let id: String - public let createdAt: Date + public let createdAt: String public let eventKey: AuditEventKey public let entityId: String public let entityType: String @@ -717,7 +733,7 @@ public struct AuditEvent: Codable { public let ipAddress: String public let userAgent: String - public init(id: String, createdAt: Date, eventKey: AuditEventKey, entityId: String, entityType: String, actorId: String, actorType: String, details: AuditEventDetailsUnion, ipAddress: String, userAgent: String) { + public init(id: String, createdAt: String, eventKey: AuditEventKey, entityId: String, entityType: String, actorId: String, actorType: String, details: AuditEventDetailsUnion, ipAddress: String, userAgent: String) { self.id = id self.createdAt = createdAt self.eventKey = eventKey @@ -744,6 +760,18 @@ public struct AuditEvent: Codable { } } +public struct AvatarData: Codable { + public let imageUrl: String + + public init(imageUrl: String) { + self.imageUrl = imageUrl + } + + enum CodingKeys: String, CodingKey { + case imageUrl = "image_url" + } +} + public struct BotResponseWebhook: Codable { public let outgoingUrl: String @@ -842,17 +870,17 @@ public struct ButtonWebhookPayload: Codable { public struct Chat: Codable { public let id: Int public let name: String - public let createdAt: Date + public let createdAt: String public let ownerId: Int public let memberIds: [Int] public let groupTagIds: [Int] public let channel: Bool public let personal: Bool public let `public`: Bool - public let lastMessageAt: Date + public let lastMessageAt: String public let meetRoomUrl: String - public init(id: Int, name: String, createdAt: Date, ownerId: Int, memberIds: [Int], groupTagIds: [Int], channel: Bool, personal: Bool, `public`: Bool, lastMessageAt: Date, meetRoomUrl: String) { + public init(id: Int, name: String, createdAt: String, ownerId: Int, memberIds: [Int], groupTagIds: [Int], channel: Bool, personal: Bool, `public`: Bool, lastMessageAt: String, meetRoomUrl: String) { self.id = id self.name = name self.createdAt = createdAt @@ -919,10 +947,10 @@ public struct ChatMemberWebhookPayload: Codable { public let chatId: Int public let threadId: Int? public let userIds: [Int] - public let createdAt: Date + public let createdAt: String public let webhookTimestamp: Int - public init(type: String, event: MemberEventType, chatId: Int, threadId: Int? = nil, userIds: [Int], createdAt: Date, webhookTimestamp: Int) { + public init(type: String, event: MemberEventType, chatId: Int, threadId: Int? = nil, userIds: [Int], createdAt: String, webhookTimestamp: Int) { self.type = type self.event = event self.chatId = chatId @@ -970,10 +998,10 @@ public struct CompanyMemberWebhookPayload: Codable { public let type: String public let event: UserEventType public let userIds: [Int] - public let createdAt: Date + public let createdAt: String public let webhookTimestamp: Int - public init(type: String, event: UserEventType, userIds: [Int], createdAt: Date, webhookTimestamp: Int) { + public init(type: String, event: UserEventType, userIds: [Int], createdAt: String, webhookTimestamp: Int) { self.type = type self.event = event self.userIds = userIds @@ -1030,13 +1058,13 @@ public struct CustomPropertyDefinition: Codable { } public struct ExportRequest: Codable { - public let startAt: Date - public let endAt: Date + public let startAt: String + public let endAt: String public let webhookUrl: String public let chatIds: [Int]? public let skipChatsFile: Bool? - public init(startAt: Date, endAt: Date, webhookUrl: String, chatIds: [Int]? = nil, skipChatsFile: Bool? = nil) { + public init(startAt: String, endAt: String, webhookUrl: String, chatIds: [Int]? = nil, skipChatsFile: Bool? = nil) { self.startAt = startAt self.endAt = endAt self.webhookUrl = webhookUrl @@ -1123,12 +1151,12 @@ public struct Forwarding: Codable { public let originalMessageId: Int public let originalChatId: Int public let authorId: Int - public let originalCreatedAt: Date + public let originalCreatedAt: String public let originalThreadId: Int? public let originalThreadMessageId: Int? public let originalThreadParentChatId: Int? - public init(originalMessageId: Int, originalChatId: Int, authorId: Int, originalCreatedAt: Date, originalThreadId: Int? = nil, originalThreadMessageId: Int? = nil, originalThreadParentChatId: Int? = nil) { + public init(originalMessageId: Int, originalChatId: Int, authorId: Int, originalCreatedAt: String, originalThreadId: Int? = nil, originalThreadMessageId: Int? = nil, originalThreadParentChatId: Int? = nil) { self.originalMessageId = originalMessageId self.originalChatId = originalChatId self.authorId = authorId @@ -1239,10 +1267,10 @@ public struct LinkSharedWebhookPayload: Codable { public let messageId: Int public let links: [WebhookLink] public let userId: Int - public let createdAt: Date + public let createdAt: String public let webhookTimestamp: Int - public init(type: String, event: String, chatId: Int, messageId: Int, links: [WebhookLink], userId: Int, createdAt: Date, webhookTimestamp: Int) { + public init(type: String, event: String, chatId: Int, messageId: Int, links: [WebhookLink], userId: Int, createdAt: String, webhookTimestamp: Int) { self.type = type self.event = event self.chatId = chatId @@ -1288,7 +1316,7 @@ public struct Message: Codable { public let rootChatId: Int public let content: String public let userId: Int - public let createdAt: Date + public let createdAt: String public let url: String public let files: [File] public let buttons: [[Button]]? @@ -1300,7 +1328,7 @@ public struct Message: Codable { public let changedAt: String? public let deletedAt: String? - public init(id: Int, entityType: MessageEntityType, entityId: Int, chatId: Int, rootChatId: Int, content: String, userId: Int, createdAt: Date, url: String, files: [File], buttons: [[Button]]? = nil, thread: MessageThread? = nil, forwarding: Forwarding? = nil, parentMessageId: Int? = nil, displayAvatarUrl: String? = nil, displayName: String? = nil, changedAt: String? = nil, deletedAt: String? = nil) { + public init(id: Int, entityType: MessageEntityType, entityId: Int, chatId: Int, rootChatId: Int, content: String, userId: Int, createdAt: String, url: String, files: [File], buttons: [[Button]]? = nil, thread: MessageThread? = nil, forwarding: Forwarding? = nil, parentMessageId: Int? = nil, displayAvatarUrl: String? = nil, displayName: String? = nil, changedAt: String? = nil, deletedAt: String? = nil) { self.id = id self.entityType = entityType self.entityId = entityId @@ -1488,14 +1516,14 @@ public struct MessageWebhookPayload: Codable { public let entityId: Int public let content: String public let userId: Int - public let createdAt: Date + public let createdAt: String public let url: String public let chatId: Int public let parentMessageId: Int? public let thread: WebhookMessageThread? public let webhookTimestamp: Int - public init(type: String, id: Int, event: WebhookEventType, entityType: MessageEntityType, entityId: Int, content: String, userId: Int, createdAt: Date, url: String, chatId: Int, parentMessageId: Int? = nil, thread: WebhookMessageThread? = nil, webhookTimestamp: Int) { + public init(type: String, id: Int, event: WebhookEventType, entityType: MessageEntityType, entityId: Int, content: String, userId: Int, createdAt: String, url: String, chatId: Int, parentMessageId: Int? = nil, thread: WebhookMessageThread? = nil, webhookTimestamp: Int) { self.type = type self.id = id self.event = event @@ -1589,9 +1617,9 @@ public struct OpenViewRequest: Codable { } public struct PaginationMetaPaginate: Codable { - public let nextPage: String? + public let nextPage: String - public init(nextPage: String? = nil) { + public init(nextPage: String) { self.nextPage = nextPage } @@ -1601,20 +1629,20 @@ public struct PaginationMetaPaginate: Codable { } public struct PaginationMeta: Codable { - public let paginate: PaginationMetaPaginate? + public let paginate: PaginationMetaPaginate - public init(paginate: PaginationMetaPaginate? = nil) { + public init(paginate: PaginationMetaPaginate) { self.paginate = paginate } } public struct Reaction: Codable { public let userId: Int - public let createdAt: Date + public let createdAt: String public let code: String public let name: String? - public init(userId: Int, createdAt: Date, code: String, name: String? = nil) { + public init(userId: Int, createdAt: String, code: String, name: String? = nil) { self.userId = userId self.createdAt = createdAt self.code = code @@ -1646,10 +1674,10 @@ public struct ReactionWebhookPayload: Codable { public let code: String public let name: String public let userId: Int - public let createdAt: Date + public let createdAt: String public let webhookTimestamp: Int - public init(type: String, event: ReactionEventType, messageId: Int, code: String, name: String, userId: Int, createdAt: Date, webhookTimestamp: Int) { + public init(type: String, event: ReactionEventType, messageId: Int, code: String, name: String, userId: Int, createdAt: String, webhookTimestamp: Int) { self.type = type self.event = event self.messageId = messageId @@ -1726,9 +1754,6 @@ public struct StatusUpdateRequest: Codable { } } -public struct TagNamesFilter: Codable { -} - public struct Task: Codable { public let id: Int public let kind: TaskKind @@ -1738,12 +1763,12 @@ public struct Task: Codable { public let userId: Int public let chatId: Int? public let status: TaskStatus - public let createdAt: Date + public let createdAt: String public let performerIds: [Int] public let allDay: Bool public let customProperties: [CustomProperty] - public init(id: Int, kind: TaskKind, content: String, dueAt: String? = nil, priority: Int, userId: Int, chatId: Int? = nil, status: TaskStatus, createdAt: Date, performerIds: [Int], allDay: Bool, customProperties: [CustomProperty]) { + public init(id: Int, kind: TaskKind, content: String, dueAt: String? = nil, priority: Int, userId: Int, chatId: Int? = nil, status: TaskStatus, createdAt: String, performerIds: [Int], allDay: Bool, customProperties: [CustomProperty]) { self.id = id self.kind = kind self.content = content @@ -1884,9 +1909,9 @@ public struct Thread: Codable { public let chatId: Int64 public let messageId: Int64 public let messageChatId: Int64 - public let updatedAt: Date + public let updatedAt: String - public init(id: Int64, chatId: Int64, messageId: Int64, messageChatId: Int64, updatedAt: Date) { + public init(id: Int64, chatId: Int64, messageId: Int64, messageChatId: Int64, updatedAt: String) { self.id = id self.chatId = chatId self.messageId = messageId @@ -1964,12 +1989,12 @@ public struct User: Codable { public let userStatus: UserStatus? public let bot: Bool public let sso: Bool - public let createdAt: Date - public let lastActivityAt: Date + public let createdAt: String + public let lastActivityAt: String public let timeZone: String public let imageUrl: String? - public init(id: Int, firstName: String, lastName: String, nickname: String, email: String, phoneNumber: String, department: String, title: String, role: UserRole, suspended: Bool, inviteStatus: InviteStatus, listTags: [String], customProperties: [CustomProperty], userStatus: UserStatus? = nil, bot: Bool, sso: Bool, createdAt: Date, lastActivityAt: Date, timeZone: String, imageUrl: String? = nil) { + public init(id: Int, firstName: String, lastName: String, nickname: String, email: String, phoneNumber: String, department: String, title: String, role: UserRole, suspended: Bool, inviteStatus: InviteStatus, listTags: [String], customProperties: [CustomProperty], userStatus: UserStatus? = nil, bot: Bool, sso: Bool, createdAt: String, lastActivityAt: String, timeZone: String, imageUrl: String? = nil) { self.id = id self.firstName = firstName self.lastName = lastName @@ -2442,13 +2467,43 @@ public struct ViewBlockTime: Codable { } } +public struct ViewSubmitWebhookPayload: Codable { + public let type: String + public let event: String + public let callbackId: String? + public let privateMetadata: String? + public let userId: Int + public let data: [String: String] + public let webhookTimestamp: Int + + public init(type: String, event: String, callbackId: String? = nil, privateMetadata: String? = nil, userId: Int, data: [String: String], webhookTimestamp: Int) { + self.type = type + self.event = event + self.callbackId = callbackId + self.privateMetadata = privateMetadata + self.userId = userId + self.data = data + self.webhookTimestamp = webhookTimestamp + } + + enum CodingKeys: String, CodingKey { + case type + case event + case callbackId = "callback_id" + case privateMetadata = "private_metadata" + case userId = "user_id" + case data + case webhookTimestamp = "webhook_timestamp" + } +} + public struct WebhookEvent: Codable { public let id: String public let eventType: String public let payload: WebhookPayloadUnion - public let createdAt: Date + public let createdAt: String - public init(id: String, eventType: String, payload: WebhookPayloadUnion, createdAt: Date) { + public init(id: String, eventType: String, payload: WebhookPayloadUnion, createdAt: String) { self.id = id self.eventType = eventType self.payload = payload @@ -2488,6 +2543,22 @@ public struct WebhookMessageThread: Codable { } } +public struct UpdateProfileAvatarRequest: Codable { + public var image: Data + + public init(image: Data) { + self.image = image + } +} + +public struct UpdateUserAvatarRequest: Codable { + public var image: Data + + public init(image: Data) { + self.image = image + } +} + public enum AuditEventDetailsUnion: Codable { case auditDetailsEmpty(AuditDetailsEmpty) case auditDetailsUserUpdated(AuditDetailsUserUpdated) @@ -2663,6 +2734,7 @@ public enum WebhookPayloadUnion: Codable { case messageWebhookPayload(MessageWebhookPayload) case reactionWebhookPayload(ReactionWebhookPayload) case buttonWebhookPayload(ButtonWebhookPayload) + case viewSubmitWebhookPayload(ViewSubmitWebhookPayload) case chatMemberWebhookPayload(ChatMemberWebhookPayload) case companyMemberWebhookPayload(CompanyMemberWebhookPayload) case linkSharedWebhookPayload(LinkSharedWebhookPayload) @@ -2681,6 +2753,8 @@ public enum WebhookPayloadUnion: Codable { self = .reactionWebhookPayload(try ReactionWebhookPayload(from: decoder)) case "button": self = .buttonWebhookPayload(try ButtonWebhookPayload(from: decoder)) + case "view": + self = .viewSubmitWebhookPayload(try ViewSubmitWebhookPayload(from: decoder)) case "chat_member": self = .chatMemberWebhookPayload(try ChatMemberWebhookPayload(from: decoder)) case "company_member": @@ -2700,6 +2774,8 @@ public enum WebhookPayloadUnion: Codable { try value.encode(to: encoder) case .buttonWebhookPayload(let value): try value.encode(to: encoder) + case .viewSubmitWebhookPayload(let value): + try value.encode(to: encoder) case .chatMemberWebhookPayload(let value): try value.encode(to: encoder) case .companyMemberWebhookPayload(let value): @@ -2712,17 +2788,17 @@ public enum WebhookPayloadUnion: Codable { public struct GetAuditEventsResponse: Codable { public let data: [AuditEvent] - public let meta: PaginationMeta? = nil + public let meta: PaginationMeta } public struct ListChatsResponse: Codable { public let data: [Chat] - public let meta: PaginationMeta? = nil + public let meta: PaginationMeta } public struct ListMembersResponse: Codable { public let data: [User] - public let meta: PaginationMeta? = nil + public let meta: PaginationMeta } public struct ListPropertiesResponse: Codable { @@ -2731,22 +2807,22 @@ public struct ListPropertiesResponse: Codable { public struct ListTagsResponse: Codable { public let data: [GroupTag] - public let meta: PaginationMeta? = nil + public let meta: PaginationMeta } public struct GetTagUsersResponse: Codable { public let data: [User] - public let meta: PaginationMeta? = nil + public let meta: PaginationMeta } public struct ListChatMessagesResponse: Codable { public let data: [Message] - public let meta: PaginationMeta? = nil + public let meta: PaginationMeta } public struct ListReactionsResponse: Codable { public let data: [Reaction] - public let meta: PaginationMeta? = nil + public let meta: PaginationMeta } public struct SearchChatsResponse: Codable { @@ -2766,17 +2842,17 @@ public struct SearchUsersResponse: Codable { public struct ListTasksResponse: Codable { public let data: [Task] - public let meta: PaginationMeta? = nil + public let meta: PaginationMeta } public struct ListUsersResponse: Codable { public let data: [User] - public let meta: PaginationMeta? = nil + public let meta: PaginationMeta } public struct GetWebhookEventsResponse: Codable { public let data: [WebhookEvent] - public let meta: PaginationMeta? = nil + public let meta: PaginationMeta } struct BotResponseDataWrapper: Codable { @@ -2807,6 +2883,10 @@ struct UserDataWrapper: Codable { let data: User } +struct AvatarDataDataWrapper: Codable { + let data: AvatarData +} + struct UserStatusDataWrapper: Codable { let data: UserStatus } diff --git a/sdk/swift/generated/Sources/Pachca/GeneratedSources/Utils.swift b/sdk/swift/generated/Sources/Pachca/GeneratedSources/Utils.swift index d65d2a00..8df90b02 100644 --- a/sdk/swift/generated/Sources/Pachca/GeneratedSources/Utils.swift +++ b/sdk/swift/generated/Sources/Pachca/GeneratedSources/Utils.swift @@ -5,13 +5,11 @@ import FoundationNetworking let pachcaDecoder: JSONDecoder = { let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 return decoder }() let pachcaEncoder: JSONEncoder = { let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 return encoder }() diff --git a/sdk/swift/generated/examples.json b/sdk/swift/generated/examples.json index 4deae97b..d6665875 100644 --- a/sdk/swift/generated/examples.json +++ b/sdk/swift/generated/examples.json @@ -7,14 +7,14 @@ }, "SecurityOperations_getAuditEvents": { "usage": "let response = try await client.security.getAuditEvents(startTime: \"2025-05-01T09:11:00Z\", endTime: \"2025-05-02T09:11:00Z\", eventKey: .userLogin, actorId: \"98765\", actorType: \"User\", entityId: \"98765\", entityType: \"User\", limit: 1, cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", - "output": "GetAuditEventsResponse(data: [AuditEvent], meta: PaginationMeta?)", + "output": "GetAuditEventsResponse(data: [AuditEvent], meta: PaginationMeta)", "imports": [ "AuditEventKey" ] }, "BotOperations_getWebhookEvents": { "usage": "let response = try await client.bots.getWebhookEvents(limit: 1, cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", - "output": "GetWebhookEventsResponse(data: [WebhookEvent], meta: PaginationMeta?)" + "output": "GetWebhookEventsResponse(data: [WebhookEvent], meta: PaginationMeta)" }, "BotOperations_updateBot": { "usage": "let body = BotUpdateRequest(bot: BotUpdateRequestBot(webhook: BotUpdateRequestBotWebhook(outgoingUrl: \"https://www.website.com/tasks/new\")))\nlet response = try await client.bots.updateBot(id: 1738816, body: body)", @@ -29,10 +29,11 @@ "usage": "try await client.bots.deleteWebhookEvent(id: \"01KAJZ2XDSS2S3DSW9EXJZ0TBV\")" }, "ChatOperations_listChats": { - "usage": "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\")", - "output": "ListChatsResponse(data: [Chat], meta: PaginationMeta?)", + "usage": "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\")", + "output": "ListChatsResponse(data: [Chat], meta: PaginationMeta)", "imports": [ "ChatAvailability", + "ChatSortField", "SortOrder" ] }, @@ -91,7 +92,7 @@ }, "ChatMemberOperations_listMembers": { "usage": "let response = try await client.members.listMembers(id: 334, role: .all, limit: 1, cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", - "output": "ListMembersResponse(data: [User], meta: PaginationMeta?)", + "output": "ListMembersResponse(data: [User], meta: PaginationMeta)", "imports": [ "ChatMemberRoleFilter" ] @@ -121,11 +122,8 @@ "usage": "try await client.members.removeMember(id: 334, userId: 186)" }, "GroupTagOperations_listTags": { - "usage": "let names = TagNamesFilter()\nlet response = try await client.groupTags.listTags(names: names, limit: 1, cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", - "output": "ListTagsResponse(data: [GroupTag], meta: PaginationMeta?)", - "imports": [ - "TagNamesFilter" - ] + "usage": "let names = [\"example\"]\nlet response = try await client.groupTags.listTags(names: names, limit: 1, cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", + "output": "ListTagsResponse(data: [GroupTag], meta: PaginationMeta)" }, "GroupTagOperations_getTag": { "usage": "let response = try await client.groupTags.getTag(id: 9111)", @@ -133,7 +131,7 @@ }, "GroupTagOperations_getTagUsers": { "usage": "let response = try await client.groupTags.getTagUsers(id: 9111, limit: 1, cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", - "output": "GetTagUsersResponse(data: [User], meta: PaginationMeta?)" + "output": "GetTagUsersResponse(data: [User], meta: PaginationMeta)" }, "GroupTagOperations_createTag": { "usage": "let body = GroupTagRequest(groupTag: GroupTagRequestGroupTag(name: \"Новое название тега\"))\nlet response = try await client.groupTags.createTag(body: body)", @@ -155,9 +153,10 @@ "usage": "try await client.groupTags.deleteTag(id: 9111)" }, "ChatMessageOperations_listChatMessages": { - "usage": "let response = try await client.messages.listChatMessages(chatId: 198, sortId: .desc, limit: 1, cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", - "output": "ListChatMessagesResponse(data: [Message], meta: PaginationMeta?)", + "usage": "let response = try await client.messages.listChatMessages(chatId: 198, sort: .id, order: .desc, limit: 1, cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", + "output": "ListChatMessagesResponse(data: [Message], meta: PaginationMeta)", "imports": [ + "MessageSortField", "SortOrder" ] }, @@ -206,7 +205,7 @@ }, "ReactionOperations_listReactions": { "usage": "let response = try await client.reactions.listReactions(id: 194275, limit: 1, cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", - "output": "ListReactionsResponse(data: [Reaction], meta: PaginationMeta?)" + "output": "ListReactionsResponse(data: [Reaction], meta: PaginationMeta)" }, "ReactionOperations_addReaction": { "usage": "let body = ReactionRequest(code: \"👍\", name: \":+1:\")\nlet response = try await client.reactions.addReaction(id: 7231942, body: body)", @@ -242,6 +241,10 @@ "usage": "let response = try await client.profile.getStatus()", "output": "String" }, + "ProfileAvatarOperations_updateProfileAvatar": { + "usage": "let image = Data()\nlet response = try await client.profile.updateProfileAvatar(image: image)", + "output": "AvatarData(imageUrl: String)" + }, "ProfileOperations_updateStatus": { "usage": "let body = StatusUpdateRequest(\n status: StatusUpdateRequestStatus(\n emoji: \"🎮\",\n title: \"Очень занят\",\n expiresAt: \"2024-04-08T10:00:00.000Z\",\n isAway: true,\n awayMessage: \"Вернусь после 15:00\"\n )\n)\nlet response = try await client.profile.updateStatus(body: body)", "output": "UserStatus(emoji: String, title: String, expiresAt: String?, isAway: Bool, awayMessage: UserStatusAwayMessage(text: String)?)", @@ -250,6 +253,9 @@ "StatusUpdateRequestStatus" ] }, + "ProfileAvatarOperations_deleteProfileAvatar": { + "usage": "try await client.profile.deleteProfileAvatar()" + }, "ProfileOperations_deleteStatus": { "usage": "try await client.profile.deleteStatus()" }, @@ -279,7 +285,7 @@ }, "TaskOperations_listTasks": { "usage": "let response = try await client.tasks.listTasks(limit: 1, cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", - "output": "ListTasksResponse(data: [Task], meta: PaginationMeta?)" + "output": "ListTasksResponse(data: [Task], meta: PaginationMeta)" }, "TaskOperations_getTask": { "usage": "let response = try await client.tasks.getTask(id: 22283)", @@ -311,7 +317,7 @@ }, "UserOperations_listUsers": { "usage": "let response = try await client.users.listUsers(query: \"Олег\", limit: 1, cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\")", - "output": "ListUsersResponse(data: [User], meta: PaginationMeta?)" + "output": "ListUsersResponse(data: [User], meta: PaginationMeta)" }, "UserOperations_getUser": { "usage": "let response = try await client.users.getUser(id: 12)", @@ -341,6 +347,10 @@ "UserUpdateRequestUser" ] }, + "UserAvatarOperations_updateUserAvatar": { + "usage": "let image = Data()\nlet response = try await client.users.updateUserAvatar(userId: 12, image: image)", + "output": "AvatarData(imageUrl: String)" + }, "UserStatusOperations_updateUserStatus": { "usage": "let body = StatusUpdateRequest(\n status: StatusUpdateRequestStatus(\n emoji: \"🎮\",\n title: \"Очень занят\",\n expiresAt: \"2024-04-08T10:00:00.000Z\",\n isAway: true,\n awayMessage: \"Вернусь после 15:00\"\n )\n)\nlet response = try await client.users.updateUserStatus(userId: 12, body: body)", "output": "UserStatus(emoji: String, title: String, expiresAt: String?, isAway: Bool, awayMessage: UserStatusAwayMessage(text: String)?)", @@ -352,6 +362,9 @@ "UserOperations_deleteUser": { "usage": "try await client.users.deleteUser(id: 12)" }, + "UserAvatarOperations_deleteUserAvatar": { + "usage": "try await client.users.deleteUserAvatar(userId: 12)" + }, "UserStatusOperations_deleteUserStatus": { "usage": "try await client.users.deleteUserStatus(userId: 12)" }, diff --git a/sdk/typescript/README.md b/sdk/typescript/README.md index 43c26520..26966a5c 100644 --- a/sdk/typescript/README.md +++ b/sdk/typescript/README.md @@ -5,7 +5,7 @@ ## Установка ```bash -npm install @pachca/sdk@1.0.1 +npm install @pachca/sdk ``` ## Использование @@ -49,11 +49,12 @@ const message = await pachca.messages.createMessage(...); // Message, не { dat // Вручную let cursor: string | undefined; const chats: Chat[] = []; -do { +for (;;) { const response = await pachca.chats.listChats({ cursor }); + if (response.data.length === 0) break; chats.push(...response.data); - cursor = response.meta?.paginate?.nextPage; -} while (cursor); + cursor = response.meta.paginate.nextPage; +} // Автоматически const allChats = await pachca.chats.listChatsAll(); diff --git a/sdk/typescript/examples/main.ts b/sdk/typescript/examples/main.ts index 455b539d..cb468989 100644 --- a/sdk/typescript/examples/main.ts +++ b/sdk/typescript/examples/main.ts @@ -22,6 +22,11 @@ if (!token || !chatIdStr) { const chatId = Number(chatIdStr); const client = new PachcaClient(token); +// ── Step 0: GET — Fetch chat (verifies datetime deserialization) ─ +console.log("0. Fetching chat..."); +const chat = await client.chats.getChat(chatId); +console.log(` Chat: ${chat.name}, createdAt=${chat.createdAt} (${typeof chat.createdAt}), lastMessageAt=${chat.lastMessageAt} (${typeof chat.lastMessageAt})`); + // ── Step 1: POST — Create a message ────────────────────────────── console.log("1. Creating message..."); const created = await client.messages.createMessage({ diff --git a/sdk/typescript/src/generated/client.ts b/sdk/typescript/src/generated/client.ts index 466a0ad8..a8f706fb 100644 --- a/sdk/typescript/src/generated/client.ts +++ b/sdk/typescript/src/generated/client.ts @@ -28,6 +28,7 @@ import { ListTagsResponse, GroupTag, GetTagUsersParams, + GetTagUsersResponse, GroupTagRequest, ListChatMessagesParams, ListChatMessagesResponse, @@ -43,17 +44,22 @@ import { ListReadMembersParams, Thread, AccessTokenInfo, + AvatarData, StatusUpdateRequest, UserStatus, SearchChatsParams, + SearchChatsResponse, SearchMessagesParams, + SearchMessagesResponse, SearchUsersParams, + SearchUsersResponse, ListTasksParams, ListTasksResponse, Task, TaskCreateRequest, TaskUpdateRequest, ListUsersParams, + ListUsersResponse, UserCreateRequest, UserUpdateRequest, OpenViewRequest, @@ -98,8 +104,9 @@ class SecurityService { do { const response = await this.getAuditEvents({ ...params, cursor } as GetAuditEventsParams); items.push(...response.data); - cursor = response.meta?.paginate?.nextPage; - } while (cursor); + if (response.data.length === 0) break; + cursor = response.meta.paginate.nextPage; + } while (true); return items; } } @@ -135,8 +142,9 @@ class BotsService { do { const response = await this.getWebhookEvents({ ...params, cursor } as GetWebhookEventsParams); items.push(...response.data); - cursor = response.meta?.paginate?.nextPage; - } while (cursor); + if (response.data.length === 0) break; + cursor = response.meta.paginate.nextPage; + } while (true); return items; } @@ -181,7 +189,8 @@ class ChatsService { async listChats(params?: ListChatsParams): Promise { const query = new URLSearchParams(); - if (params?.sortId !== undefined) query.set("sort[{field}]", params.sortId); + if (params?.sort !== undefined) query.set("sort", params.sort); + if (params?.order !== undefined) query.set("order", params.order); if (params?.availability !== undefined) query.set("availability", params.availability); if (params?.lastMessageAtAfter !== undefined) query.set("last_message_at_after", params.lastMessageAtAfter); if (params?.lastMessageAtBefore !== undefined) query.set("last_message_at_before", params.lastMessageAtBefore); @@ -209,8 +218,9 @@ class ChatsService { do { const response = await this.listChats({ ...params, cursor } as ListChatsParams); items.push(...response.data); - cursor = response.meta?.paginate?.nextPage; - } while (cursor); + if (response.data.length === 0) break; + cursor = response.meta.paginate.nextPage; + } while (true); return items; } @@ -425,8 +435,9 @@ class MembersService { do { const response = await this.listMembers(id, { ...params, cursor } as ListMembersParams); items.push(...response.data); - cursor = response.meta?.paginate?.nextPage; - } while (cursor); + if (response.data.length === 0) break; + cursor = response.meta.paginate.nextPage; + } while (true); return items; } @@ -532,7 +543,9 @@ class GroupTagsService { async listTags(params?: ListTagsParams): Promise { const query = new URLSearchParams(); - if (params?.names !== undefined) query.set("names", String(params.names)); + if (params?.names !== undefined) { + params.names.forEach((v) => query.append("names[]", String(v))); + } if (params?.limit !== undefined) query.set("limit", String(params.limit)); if (params?.cursor !== undefined) query.set("cursor", params.cursor); const url = `${this.baseUrl}/group_tags${query.toString() ? `?${query}` : ""}`; @@ -556,8 +569,9 @@ class GroupTagsService { do { const response = await this.listTags({ ...params, cursor } as ListTagsParams); items.push(...response.data); - cursor = response.meta?.paginate?.nextPage; - } while (cursor); + if (response.data.length === 0) break; + cursor = response.meta.paginate.nextPage; + } while (true); return items; } @@ -576,7 +590,7 @@ class GroupTagsService { } } - async getTagUsers(id: number, params?: GetTagUsersParams): Promise { + async getTagUsers(id: number, params?: GetTagUsersParams): Promise { const query = new URLSearchParams(); if (params?.limit !== undefined) query.set("limit", String(params.limit)); if (params?.cursor !== undefined) query.set("cursor", params.cursor); @@ -587,7 +601,7 @@ class GroupTagsService { const body = await response.json(); switch (response.status) { case 200: - return deserialize(body) as ListMembersResponse; + return deserialize(body) as GetTagUsersResponse; case 401: throw new OAuthError(body.error); default: @@ -601,8 +615,9 @@ class GroupTagsService { do { const response = await this.getTagUsers(id, { ...params, cursor } as GetTagUsersParams); items.push(...response.data); - cursor = response.meta?.paginate?.nextPage; - } while (cursor); + if (response.data.length === 0) break; + cursor = response.meta.paginate.nextPage; + } while (true); return items; } @@ -665,7 +680,8 @@ class MessagesService { async listChatMessages(params: ListChatMessagesParams): Promise { const query = new URLSearchParams(); query.set("chat_id", String(params.chatId)); - if (params?.sortId !== undefined) query.set("sort[{field}]", params.sortId); + if (params?.sort !== undefined) query.set("sort", params.sort); + if (params?.order !== undefined) query.set("order", params.order); if (params?.limit !== undefined) query.set("limit", String(params.limit)); if (params?.cursor !== undefined) query.set("cursor", params.cursor); const response = await fetchWithRetry(`${this.baseUrl}/messages?${query}`, { @@ -688,8 +704,9 @@ class MessagesService { do { const response = await this.listChatMessages({ ...params, cursor } as ListChatMessagesParams); items.push(...response.data); - cursor = response.meta?.paginate?.nextPage; - } while (cursor); + if (response.data.length === 0) break; + cursor = response.meta.paginate.nextPage; + } while (true); return items; } @@ -842,8 +859,9 @@ class ReactionsService { do { const response = await this.listReactions(id, { ...params, cursor } as ListReactionsParams); items.push(...response.data); - cursor = response.meta?.paginate?.nextPage; - } while (cursor); + if (response.data.length === 0) break; + cursor = response.meta.paginate.nextPage; + } while (true); return items; } @@ -998,6 +1016,25 @@ class ProfileService { } } + async updateProfileAvatar(image: Blob): Promise { + const form = new FormData(); + form.set("image", image, "upload"); + const response = await fetchWithRetry(`${this.baseUrl}/profile/avatar`, { + method: "PUT", + headers: this.headers, + body: form, + }); + const body = await response.json(); + switch (response.status) { + case 200: + return deserialize(body.data) as AvatarData; + case 401: + throw new OAuthError(body.error); + default: + throw new ApiError(body.errors); + } + } + async updateStatus(request: StatusUpdateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/profile/status`, { method: "PUT", @@ -1015,6 +1052,21 @@ class ProfileService { } } + async deleteProfileAvatar(): Promise { + const response = await fetchWithRetry(`${this.baseUrl}/profile/avatar`, { + method: "DELETE", + headers: this.headers, + }); + switch (response.status) { + case 204: + return; + case 401: + throw new OAuthError(((await response.json()) as any).error); + default: + throw new ApiError(((await response.json()) as any).errors); + } + } + async deleteStatus(): Promise { const response = await fetchWithRetry(`${this.baseUrl}/profile/status`, { method: "DELETE", @@ -1037,7 +1089,7 @@ class SearchService { private headers: Record, ) {} - async searchChats(params?: SearchChatsParams): Promise { + async searchChats(params?: SearchChatsParams): Promise { const query = new URLSearchParams(); if (params?.query !== undefined) query.set("query", params.query); if (params?.limit !== undefined) query.set("limit", String(params.limit)); @@ -1055,7 +1107,7 @@ class SearchService { const body = await response.json(); switch (response.status) { case 200: - return deserialize(body) as ListChatsResponse; + return deserialize(body) as SearchChatsResponse; case 401: throw new OAuthError(body.error); default: @@ -1069,12 +1121,13 @@ class SearchService { do { const response = await this.searchChats({ ...params, cursor } as SearchChatsParams); items.push(...response.data); - cursor = response.meta?.paginate?.nextPage; - } while (cursor); + if (response.data.length === 0) break; + cursor = response.meta.paginate.nextPage; + } while (true); return items; } - async searchMessages(params?: SearchMessagesParams): Promise { + async searchMessages(params?: SearchMessagesParams): Promise { const query = new URLSearchParams(); if (params?.query !== undefined) query.set("query", params.query); if (params?.limit !== undefined) query.set("limit", String(params.limit)); @@ -1082,8 +1135,12 @@ class SearchService { if (params?.order !== undefined) query.set("order", params.order); if (params?.createdFrom !== undefined) query.set("created_from", params.createdFrom); if (params?.createdTo !== undefined) query.set("created_to", params.createdTo); - if (params?.chatIds !== undefined) query.set("chat_ids", String(params.chatIds)); - if (params?.userIds !== undefined) query.set("user_ids", String(params.userIds)); + if (params?.chatIds !== undefined) { + params.chatIds.forEach((v) => query.append("chat_ids[]", String(v))); + } + if (params?.userIds !== undefined) { + params.userIds.forEach((v) => query.append("user_ids[]", String(v))); + } if (params?.active !== undefined) query.set("active", String(params.active)); const url = `${this.baseUrl}/search/messages${query.toString() ? `?${query}` : ""}`; const response = await fetchWithRetry(url, { @@ -1092,7 +1149,7 @@ class SearchService { const body = await response.json(); switch (response.status) { case 200: - return deserialize(body) as ListChatMessagesResponse; + return deserialize(body) as SearchMessagesResponse; case 401: throw new OAuthError(body.error); default: @@ -1106,12 +1163,13 @@ class SearchService { do { const response = await this.searchMessages({ ...params, cursor } as SearchMessagesParams); items.push(...response.data); - cursor = response.meta?.paginate?.nextPage; - } while (cursor); + if (response.data.length === 0) break; + cursor = response.meta.paginate.nextPage; + } while (true); return items; } - async searchUsers(params?: SearchUsersParams): Promise { + async searchUsers(params?: SearchUsersParams): Promise { const query = new URLSearchParams(); if (params?.query !== undefined) query.set("query", params.query); if (params?.limit !== undefined) query.set("limit", String(params.limit)); @@ -1120,7 +1178,9 @@ class SearchService { if (params?.order !== undefined) query.set("order", params.order); if (params?.createdFrom !== undefined) query.set("created_from", params.createdFrom); if (params?.createdTo !== undefined) query.set("created_to", params.createdTo); - if (params?.companyRoles !== undefined) query.set("company_roles", String(params.companyRoles)); + if (params?.companyRoles !== undefined) { + params.companyRoles.forEach((v) => query.append("company_roles[]", String(v))); + } const url = `${this.baseUrl}/search/users${query.toString() ? `?${query}` : ""}`; const response = await fetchWithRetry(url, { headers: this.headers, @@ -1128,7 +1188,7 @@ class SearchService { const body = await response.json(); switch (response.status) { case 200: - return deserialize(body) as ListMembersResponse; + return deserialize(body) as SearchUsersResponse; case 401: throw new OAuthError(body.error); default: @@ -1142,8 +1202,9 @@ class SearchService { do { const response = await this.searchUsers({ ...params, cursor } as SearchUsersParams); items.push(...response.data); - cursor = response.meta?.paginate?.nextPage; - } while (cursor); + if (response.data.length === 0) break; + cursor = response.meta.paginate.nextPage; + } while (true); return items; } } @@ -1179,8 +1240,9 @@ class TasksService { do { const response = await this.listTasks({ ...params, cursor } as ListTasksParams); items.push(...response.data); - cursor = response.meta?.paginate?.nextPage; - } while (cursor); + if (response.data.length === 0) break; + cursor = response.meta.paginate.nextPage; + } while (true); return items; } @@ -1255,7 +1317,7 @@ class UsersService { private headers: Record, ) {} - async listUsers(params?: ListUsersParams): Promise { + async listUsers(params?: ListUsersParams): Promise { const query = new URLSearchParams(); if (params?.query !== undefined) query.set("query", params.query); if (params?.limit !== undefined) query.set("limit", String(params.limit)); @@ -1267,7 +1329,7 @@ class UsersService { const body = await response.json(); switch (response.status) { case 200: - return deserialize(body) as ListMembersResponse; + return deserialize(body) as ListUsersResponse; case 401: throw new OAuthError(body.error); default: @@ -1281,8 +1343,9 @@ class UsersService { do { const response = await this.listUsers({ ...params, cursor } as ListUsersParams); items.push(...response.data); - cursor = response.meta?.paginate?.nextPage; - } while (cursor); + if (response.data.length === 0) break; + cursor = response.meta.paginate.nextPage; + } while (true); return items; } @@ -1350,6 +1413,25 @@ class UsersService { } } + async updateUserAvatar(userId: number, image: Blob): Promise { + const form = new FormData(); + form.set("image", image, "upload"); + const response = await fetchWithRetry(`${this.baseUrl}/users/${userId}/avatar`, { + method: "PUT", + headers: this.headers, + body: form, + }); + const body = await response.json(); + switch (response.status) { + case 200: + return deserialize(body.data) as AvatarData; + case 401: + throw new OAuthError(body.error); + default: + throw new ApiError(body.errors); + } + } + async updateUserStatus(userId: number, request: StatusUpdateRequest): Promise { const response = await fetchWithRetry(`${this.baseUrl}/users/${userId}/status`, { method: "PUT", @@ -1382,6 +1464,21 @@ class UsersService { } } + async deleteUserAvatar(userId: number): Promise { + const response = await fetchWithRetry(`${this.baseUrl}/users/${userId}/avatar`, { + method: "DELETE", + headers: this.headers, + }); + switch (response.status) { + case 204: + return; + case 401: + throw new OAuthError(((await response.json()) as any).error); + default: + throw new ApiError(((await response.json()) as any).errors); + } + } + async deleteUserStatus(userId: number): Promise { const response = await fetchWithRetry(`${this.baseUrl}/users/${userId}/status`, { method: "DELETE", diff --git a/sdk/typescript/src/generated/examples.json b/sdk/typescript/src/generated/examples.json index d0aef1ba..268e58f5 100644 --- a/sdk/typescript/src/generated/examples.json +++ b/sdk/typescript/src/generated/examples.json @@ -7,14 +7,14 @@ }, "SecurityOperations_getAuditEvents": { "usage": "const response = client.security.getAuditEvents({\n startTime: \"2025-05-01T09:11:00Z\",\n endTime: \"2025-05-02T09:11:00Z\",\n eventKey: AuditEventKey.UserLogin,\n actorId: \"98765\",\n actorType: \"User\",\n entityId: \"98765\",\n entityType: \"User\",\n limit: 1,\n cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"\n})", - "output": "GetAuditEventsResponse({ data: AuditEvent[], meta?: PaginationMeta })", + "output": "GetAuditEventsResponse({ data: AuditEvent[], meta: PaginationMeta })", "imports": [ "AuditEventKey" ] }, "BotOperations_getWebhookEvents": { "usage": "const response = client.bots.getWebhookEvents({ limit: 1, cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\" })", - "output": "GetWebhookEventsResponse({ data: WebhookEvent[], meta?: PaginationMeta })" + "output": "GetWebhookEventsResponse({ data: WebhookEvent[], meta: PaginationMeta })" }, "BotOperations_updateBot": { "usage": "const request: BotUpdateRequest = { bot: { webhook: { outgoingUrl: \"https://www.website.com/tasks/new\" } } }\nconst response = client.bots.updateBot(1738816, request)", @@ -29,10 +29,11 @@ "usage": "client.bots.deleteWebhookEvent(\"01KAJZ2XDSS2S3DSW9EXJZ0TBV\")" }, "ChatOperations_listChats": { - "usage": "const response = client.chats.listChats({\n sortId: SortOrder.Desc,\n availability: ChatAvailability.IsMember,\n lastMessageAtAfter: \"2025-01-01T00:00:00.000Z\",\n lastMessageAtBefore: \"2025-02-01T00:00:00.000Z\",\n personal: false,\n limit: 1,\n cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"\n})", - "output": "ListChatsResponse({ data: Chat[], meta?: PaginationMeta })", + "usage": "const response = client.chats.listChats({\n sort: ChatSortField.Id,\n order: SortOrder.Desc,\n availability: ChatAvailability.IsMember,\n lastMessageAtAfter: \"2025-01-01T00:00:00.000Z\",\n lastMessageAtBefore: \"2025-02-01T00:00:00.000Z\",\n personal: false,\n limit: 1,\n cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"\n})", + "output": "ListChatsResponse({ data: Chat[], meta: PaginationMeta })", "imports": [ "ChatAvailability", + "ChatSortField", "SortOrder" ] }, @@ -91,7 +92,7 @@ }, "ChatMemberOperations_listMembers": { "usage": "const response = client.members.listMembers(334, {\n role: ChatMemberRoleFilter.All,\n limit: 1,\n cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"\n})", - "output": "ListMembersResponse({ data: User[], meta?: PaginationMeta })", + "output": "ListMembersResponse({ data: User[], meta: PaginationMeta })", "imports": [ "ChatMemberRoleFilter" ] @@ -121,11 +122,8 @@ "usage": "client.members.removeMember(334, 186)" }, "GroupTagOperations_listTags": { - "usage": "const response = client.groupTags.listTags({\n names: {},\n limit: 1,\n cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"\n})", - "output": "ListTagsResponse({ data: GroupTag[], meta?: PaginationMeta })", - "imports": [ - "TagNamesFilter" - ] + "usage": "const response = client.groupTags.listTags({\n names: [\"example\"],\n limit: 1,\n cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"\n})", + "output": "ListTagsResponse({ data: GroupTag[], meta: PaginationMeta })" }, "GroupTagOperations_getTag": { "usage": "const response = client.groupTags.getTag(9111)", @@ -133,7 +131,7 @@ }, "GroupTagOperations_getTagUsers": { "usage": "const response = client.groupTags.getTagUsers(9111, { limit: 1, cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\" })", - "output": "GetTagUsersResponse({ data: User[], meta?: PaginationMeta })" + "output": "GetTagUsersResponse({ data: User[], meta: PaginationMeta })" }, "GroupTagOperations_createTag": { "usage": "const request: GroupTagRequest = { groupTag: { name: \"Новое название тега\" } }\nconst response = client.groupTags.createTag(request)", @@ -155,9 +153,10 @@ "usage": "client.groupTags.deleteTag(9111)" }, "ChatMessageOperations_listChatMessages": { - "usage": "const response = client.messages.listChatMessages({\n chatId: 198,\n sortId: SortOrder.Desc,\n limit: 1,\n cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"\n})", - "output": "ListChatMessagesResponse({ data: Message[], meta?: PaginationMeta })", + "usage": "const response = client.messages.listChatMessages({\n chatId: 198,\n sort: MessageSortField.Id,\n order: SortOrder.Desc,\n limit: 1,\n cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"\n})", + "output": "ListChatMessagesResponse({ data: Message[], meta: PaginationMeta })", "imports": [ + "MessageSortField", "SortOrder" ] }, @@ -206,7 +205,7 @@ }, "ReactionOperations_listReactions": { "usage": "const response = client.reactions.listReactions(194275, { limit: 1, cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\" })", - "output": "ListReactionsResponse({ data: Reaction[], meta?: PaginationMeta })" + "output": "ListReactionsResponse({ data: Reaction[], meta: PaginationMeta })" }, "ReactionOperations_addReaction": { "usage": "const request: ReactionRequest = { code: \"👍\", name: \":+1:\" }\nconst response = client.reactions.addReaction(7231942, request)", @@ -242,6 +241,10 @@ "usage": "const response = client.profile.getStatus()", "output": "unknown" }, + "ProfileAvatarOperations_updateProfileAvatar": { + "usage": "const image = new Blob([])\nconst response = client.profile.updateProfileAvatar(image)", + "output": "AvatarData({ imageUrl: string })" + }, "ProfileOperations_updateStatus": { "usage": "const request: StatusUpdateRequest = {\n status: {\n emoji: \"🎮\",\n title: \"Очень занят\",\n expiresAt: \"2024-04-08T10:00:00.000Z\",\n isAway: true,\n awayMessage: \"Вернусь после 15:00\"\n }\n}\nconst response = client.profile.updateStatus(request)", "output": "UserStatus({ emoji: string, title: string, expiresAt: string | null, isAway: boolean, awayMessage: UserStatusAwayMessage({ text: string }) | null })", @@ -250,6 +253,9 @@ "StatusUpdateRequestStatus" ] }, + "ProfileAvatarOperations_deleteProfileAvatar": { + "usage": "client.profile.deleteProfileAvatar()" + }, "ProfileOperations_deleteStatus": { "usage": "client.profile.deleteStatus()" }, @@ -279,7 +285,7 @@ }, "TaskOperations_listTasks": { "usage": "const response = client.tasks.listTasks({ limit: 1, cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\" })", - "output": "ListTasksResponse({ data: Task[], meta?: PaginationMeta })" + "output": "ListTasksResponse({ data: Task[], meta: PaginationMeta })" }, "TaskOperations_getTask": { "usage": "const response = client.tasks.getTask(22283)", @@ -311,7 +317,7 @@ }, "UserOperations_listUsers": { "usage": "const response = client.users.listUsers({\n query: \"Олег\",\n limit: 1,\n cursor: \"eyJpZCI6MTAsImRpciI6ImFzYyJ9\"\n})", - "output": "ListUsersResponse({ data: User[], meta?: PaginationMeta })" + "output": "ListUsersResponse({ data: User[], meta: PaginationMeta })" }, "UserOperations_getUser": { "usage": "const response = client.users.getUser(12)", @@ -341,6 +347,10 @@ "UserUpdateRequestUser" ] }, + "UserAvatarOperations_updateUserAvatar": { + "usage": "const image = new Blob([])\nconst response = client.users.updateUserAvatar(12, image)", + "output": "AvatarData({ imageUrl: string })" + }, "UserStatusOperations_updateUserStatus": { "usage": "const request: StatusUpdateRequest = {\n status: {\n emoji: \"🎮\",\n title: \"Очень занят\",\n expiresAt: \"2024-04-08T10:00:00.000Z\",\n isAway: true,\n awayMessage: \"Вернусь после 15:00\"\n }\n}\nconst response = client.users.updateUserStatus(12, request)", "output": "UserStatus({ emoji: string, title: string, expiresAt: string | null, isAway: boolean, awayMessage: UserStatusAwayMessage({ text: string }) | null })", @@ -352,6 +362,9 @@ "UserOperations_deleteUser": { "usage": "client.users.deleteUser(12)" }, + "UserAvatarOperations_deleteUserAvatar": { + "usage": "client.users.deleteUserAvatar(12)" + }, "UserStatusOperations_deleteUserStatus": { "usage": "client.users.deleteUserStatus(12)" }, diff --git a/sdk/typescript/src/generated/types.ts b/sdk/typescript/src/generated/types.ts index 0321ea26..0c17b10a 100644 --- a/sdk/typescript/src/generated/types.ts +++ b/sdk/typescript/src/generated/types.ts @@ -104,6 +104,14 @@ export enum ChatMemberRoleFilter { Member = "member", } +/** Поле сортировки чатов */ +export enum ChatSortField { + /** По идентификатору чата */ + Id = "id", + /** По дате и времени создания последнего сообщения */ + LastMessageAt = "last_message_at", +} + /** Тип чата */ export enum ChatSubtype { /** Канал или беседа */ @@ -158,6 +166,11 @@ export enum MessageEntityType { User = "user", } +export enum MessageSortField { + /** По идентификатору сообщения */ + Id = "id", +} + /** Скоуп доступа OAuth токена */ export enum OAuthScope { /** Просмотр чатов и списка чатов */ @@ -218,10 +231,14 @@ export enum OAuthScope { ProfileStatusRead = "profile_status:read", /** Изменение и удаление статуса профиля */ ProfileStatusWrite = "profile_status:write", + /** Изменение и удаление аватара профиля */ + ProfileAvatarWrite = "profile_avatar:write", /** Просмотр статуса сотрудника */ UserStatusRead = "user_status:read", /** Изменение и удаление статуса сотрудника */ UserStatusWrite = "user_status:write", + /** Изменение и удаление аватара сотрудника */ + UserAvatarWrite = "user_avatar:write", /** Просмотр дополнительных полей */ CustomPropertiesRead = "custom_properties:read", /** Просмотр журнала аудита */ @@ -557,6 +574,10 @@ export interface AuditEvent { userAgent: string; } +export interface AvatarData { + imageUrl: string; +} + export interface BotResponse { id: number; webhook: { @@ -843,8 +864,8 @@ export interface OpenViewRequest { } export interface PaginationMeta { - paginate?: { - nextPage?: string; + paginate: { + nextPage: string; }; } @@ -888,9 +909,6 @@ export interface StatusUpdateRequest { }; } -export interface TagNamesFilter { -} - export interface Task { id: number; kind: TaskKind; @@ -1154,6 +1172,16 @@ export interface ViewBlockTime { hint?: string; } +export interface ViewSubmitWebhookPayload { + type: "view"; + event: "submit"; + callbackId: string | null; + privateMetadata: string | null; + userId: number; + data: Record; + webhookTimestamp: number; +} + export interface WebhookEvent { id: string; eventType: string; @@ -1171,11 +1199,19 @@ export interface WebhookMessageThread { messageChatId: number; } +export interface UpdateProfileAvatarRequest { + image: Blob; +} + +export interface UpdateUserAvatarRequest { + image: Blob; +} + export type AuditEventDetailsUnion = AuditDetailsEmpty | AuditDetailsUserUpdated | AuditDetailsRoleChanged | AuditDetailsTagName | AuditDetailsInitiator | AuditDetailsInviter | AuditDetailsChatRenamed | AuditDetailsChatPermission | AuditDetailsTagChat | AuditDetailsChatId | AuditDetailsTokenScopes | AuditDetailsKms | AuditDetailsDlp | AuditDetailsSearch; export type ViewBlockUnion = ViewBlockHeader | ViewBlockPlainText | ViewBlockMarkdown | ViewBlockDivider | ViewBlockInput | ViewBlockSelect | ViewBlockRadio | ViewBlockCheckbox | ViewBlockDate | ViewBlockTime | ViewBlockFileInput; -export type WebhookPayloadUnion = MessageWebhookPayload | ReactionWebhookPayload | ButtonWebhookPayload | ChatMemberWebhookPayload | CompanyMemberWebhookPayload | LinkSharedWebhookPayload; +export type WebhookPayloadUnion = MessageWebhookPayload | ReactionWebhookPayload | ButtonWebhookPayload | ViewSubmitWebhookPayload | ChatMemberWebhookPayload | CompanyMemberWebhookPayload | LinkSharedWebhookPayload; export interface GetAuditEventsParams { startTime?: string; @@ -1190,7 +1226,8 @@ export interface GetAuditEventsParams { } export interface ListChatsParams { - sortId?: SortOrder; + sort?: ChatSortField; + order?: SortOrder; availability?: ChatAvailability; lastMessageAtAfter?: string; lastMessageAtBefore?: string; @@ -1210,7 +1247,7 @@ export interface ListPropertiesParams { } export interface ListTagsParams { - names?: TagNamesFilter; + names?: string[]; limit?: number; cursor?: string; } @@ -1222,7 +1259,8 @@ export interface GetTagUsersParams { export interface ListChatMessagesParams { chatId: number; - sortId?: SortOrder; + sort?: MessageSortField; + order?: SortOrder; limit?: number; cursor?: string; } @@ -1295,17 +1333,17 @@ export interface GetWebhookEventsParams { export interface GetAuditEventsResponse { data: AuditEvent[]; - meta?: PaginationMeta; + meta: PaginationMeta; } export interface ListChatsResponse { data: Chat[]; - meta?: PaginationMeta; + meta: PaginationMeta; } export interface ListMembersResponse { data: User[]; - meta?: PaginationMeta; + meta: PaginationMeta; } export interface ListPropertiesResponse { @@ -1314,22 +1352,22 @@ export interface ListPropertiesResponse { export interface ListTagsResponse { data: GroupTag[]; - meta?: PaginationMeta; + meta: PaginationMeta; } export interface GetTagUsersResponse { data: User[]; - meta?: PaginationMeta; + meta: PaginationMeta; } export interface ListChatMessagesResponse { data: Message[]; - meta?: PaginationMeta; + meta: PaginationMeta; } export interface ListReactionsResponse { data: Reaction[]; - meta?: PaginationMeta; + meta: PaginationMeta; } export interface SearchChatsResponse { @@ -1349,15 +1387,15 @@ export interface SearchUsersResponse { export interface ListTasksResponse { data: Task[]; - meta?: PaginationMeta; + meta: PaginationMeta; } export interface ListUsersResponse { data: User[]; - meta?: PaginationMeta; + meta: PaginationMeta; } export interface GetWebhookEventsResponse { data: WebhookEvent[]; - meta?: PaginationMeta; + meta: PaginationMeta; } diff --git a/sdk/typescript/src/generated/utils.ts b/sdk/typescript/src/generated/utils.ts index e265ad6f..55e4476f 100644 --- a/sdk/typescript/src/generated/utils.ts +++ b/sdk/typescript/src/generated/utils.ts @@ -10,7 +10,7 @@ function camelToSnake(str: string): string { .toLowerCase(); } -const RECORD_KEYS = new Set(["payload", "filters", "link_previews", "linkPreviews"]); +const RECORD_KEYS = new Set(["payload", "filters", "link_previews", "linkPreviews", "data"]); function deserializeRecord(obj: unknown): unknown { if (obj !== null && typeof obj === "object" && !Array.isArray(obj)) { diff --git a/skills/pachca-profile/SKILL.md b/skills/pachca-profile/SKILL.md index 8a4df339..a5fd95df 100644 --- a/skills/pachca-profile/SKILL.md +++ b/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/skills/pachca-users/SKILL.md b/skills/pachca-users/SKILL.md index 8d28d6b5..b11fc1ae 100644 --- a/skills/pachca-users/SKILL.md +++ b/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/turbo.json b/turbo.json index 2808a2a0..7c43fe47 100644 --- a/turbo.json +++ b/turbo.json @@ -34,9 +34,37 @@ "outputs": ["src/commands/*/*.ts", "!src/commands/auth/*.ts", "!src/commands/config/*.ts", "src/data/*.json", "!src/data/changelog.json", "README.md"], "cache": true }, + "overlay:apply": { + "dependsOn": ["@pachca/spec#generate"], + "inputs": ["overlay.en.yaml", "openapi.yaml", "scripts/apply-overlay.ts"], + "outputs": ["openapi.en.yaml"], + "cache": true + }, + "overlay:validate": { + "dependsOn": ["@pachca/spec#generate", "@pachca/spec#overlay:apply"], + "inputs": ["overlay.en.yaml", "openapi.yaml", "scripts/validate-overlay.ts"], + "cache": true + }, + "generate-n8n": { + "dependsOn": ["^build", "@pachca/spec#generate", "@pachca/spec#overlay:apply"], + "inputs": [ + "../../packages/spec/openapi.yaml", + "../../packages/spec/openapi.en.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", + "../../apps/docs/scripts/skills/config.ts", + "scripts/**" + ], + "outputs": ["nodes/**/*.ts", "credentials/**/*.ts"], + "cache": true + }, "build": { - "dependsOn": ["^build", "generate", "generate-llms", "generate-cli"], - "outputs": [".next/**", "!.next/cache/**", ".build/**", "dist/**", "oclif.manifest.json"], + "dependsOn": ["^build", "generate", "overlay:apply", "generate-llms", "generate-cli", "generate-n8n"], + "outputs": [".next/**", "!.next/cache/**", ".build/**", "dist/**", "oclif.manifest.json", "generated/bin/**", "generated/obj/**"], "cache": true }, "dev": { @@ -58,7 +86,7 @@ "cache": true }, "check": { - "dependsOn": ["lint", "typecheck", "knip", "format:check", "test"], + "dependsOn": ["lint", "typecheck", "knip", "format:check", "test", "@pachca/spec#overlay:validate"], "cache": false }, "check-urls": { @@ -66,7 +94,7 @@ "cache": true }, "test": { - "dependsOn": ["^build", "generate-cli"], + "dependsOn": ["^build", "generate-cli", "generate-n8n"], "inputs": ["src/**", "tests/**"], "cache": true },
{command} - + {item.title} - + {row.title}