Compare commits

..
Author SHA1 Message Date
Félix MalfaitandClaude Opus 4.7 f7f9e829b6 fix(address-form): sync stale defaultSortSubField to form state
The select previously displayed a computed fallback when the stored
defaultSortSubField was no longer in the enabled subFields, but the form
state was untouched — submitting could persist the stale value. Add an
effect that keeps form state in sync with the resolved fallback whenever
subFields or the stored value changes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 12:06:03 +02:00
Félix MalfaitandClaude Opus 4.7 634977e00e feat(field-metadata): configurable default sort sub-field for FullName and Address
Adds a `defaultSortSubField` advanced-mode setting on FullName and Address
composite fields so admins can pick which sub-field a column sort defaults
to (e.g. lastName for FullName, addressCity for Address). Address becomes
sortable for the first time; FullName previously sorted by firstName+lastName
unconditionally and now uses the configured sub-field as the primary key
with the other as a tie-breaker.

The Address sub-field dropdown is constrained to the currently-enabled
sub-fields — disabling a sub-field removes it from the sort options too,
and the runtime falls back gracefully if a previously-saved default is
later disabled.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 11:55:56 +02:00
395 changed files with 3243 additions and 12408 deletions
-72
View File
@@ -1,72 +0,0 @@
---
description: GitHub Actions security guidelines for supply chain protection
globs: **/.github/**/*.yml, **/.github/**/*.yaml
alwaysApply: false
---
# GitHub Actions Security
## Pin Third-Party Actions to Commit SHAs
Always reference external actions and reusable workflows by their full commit SHA, never by a mutable tag or branch. Tags can be force-pushed by a compromised maintainer account.
```yaml
# ❌ Mutable tag — vulnerable to supply chain attacks
uses: actions/checkout@v4
uses: actions/setup-node@v4
# ✅ Pinned to commit SHA with tag comment for readability
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
```
## Prefer `gh api` Over Third-Party Dispatch Actions
For repository dispatch calls, use `gh api` directly instead of third-party actions like `peter-evans/repository-dispatch`. This eliminates a supply-chain dependency entirely.
```yaml
# ✅ Use env vars + bracket notation to prevent injection
- name: Dispatch to target repo
env:
GH_TOKEN: ${{ secrets.DISPATCH_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
BRANCH: ${{ github.event.workflow_run.head_branch }}
run: |
gh api repos/org/repo/dispatches \
-f event_type=my-event \
-f "client_payload[pr_number]=$PR_NUMBER" \
-f "client_payload[branch]=$BRANCH"
# ✅ Simple dispatch without payload
- name: Trigger workflow
env:
GH_TOKEN: ${{ secrets.DISPATCH_TOKEN }}
run: |
gh api repos/org/repo/dispatches -f event_type=my-event
# ❌ Third-party action dependency
- uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.DISPATCH_TOKEN }}
repository: org/repo
event-type: my-event
# ❌ Inline ${{ }} in shell — vulnerable to injection
- run: |
gh api repos/org/repo/dispatches --input - <<EOF
{"event_type": "x", "client_payload": {"branch": "${{ github.head_ref }}"}}
EOF
```
## Minimal Permissions
Always declare explicit `permissions` at the job level with the least privilege required. Never rely on the default `GITHUB_TOKEN` permissions.
```yaml
# ✅ Explicit minimal permissions
permissions:
contents: read
# ❌ Overly broad or implicit permissions
permissions: write-all
```
-6
View File
@@ -1,6 +0,0 @@
/.github/ @charlesBochet @FelixMalfait @Weiko @prastoin @bosiraphael @etiennejouan @ijreilly @martmull @thomtrp
/.github/CODEOWNERS @charlesBochet @FelixMalfait @Weiko @prastoin @bosiraphael @etiennejouan @ijreilly @martmull @thomtrp
/.github/workflows/ @charlesBochet @FelixMalfait @Weiko @prastoin @bosiraphael @etiennejouan @ijreilly @martmull @thomtrp
/.github/actions/ @charlesBochet @FelixMalfait @Weiko @prastoin @bosiraphael @etiennejouan @ijreilly @martmull @thomtrp
/.github/dependabot.yml @charlesBochet @FelixMalfait @Weiko @prastoin @bosiraphael @etiennejouan @ijreilly @martmull @thomtrp
/.yarnrc.yml @charlesBochet @FelixMalfait @Weiko @prastoin @bosiraphael @etiennejouan @ijreilly @martmull @thomtrp
+1 -1
View File
@@ -21,7 +21,7 @@ runs:
run: corepack enable
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
uses: actions/setup-node@v4
with:
node-version-file: '${{ inputs.app-path }}/.nvmrc'
cache: yarn
@@ -21,7 +21,7 @@ runs:
run: corepack enable
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
uses: actions/setup-node@v4
with:
node-version-file: '${{ inputs.app-path }}/.nvmrc'
cache: yarn
+1 -1
View File
@@ -20,7 +20,7 @@ runs:
run: git fetch origin main --depth=1
- name: Get last successful commit
if: env.NX_BASE == ''
uses: nrwl/nx-set-shas@3e9ad7370203c1e93d109be57f3b72eb0eb511b1 # v4
uses: nrwl/nx-set-shas@v4
- name: Fallback to origin/main if no base found
if: env.NX_BASE == ''
shell: bash
+1 -1
View File
@@ -25,7 +25,7 @@ runs:
run: |
echo "CACHE_PRIMARY_KEY_PREFIX=v4-${CACHE_KEY}-${REF_NAME}" >> "${GITHUB_OUTPUT}"
- name: Restore cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 (restore)
uses: actions/cache/restore@v4
id: restore-cache
with:
key: ${{ steps.cache-primary-key-builder.outputs.CACHE_PRIMARY_KEY_PREFIX }}-${{ github.sha }}
+1 -4
View File
@@ -9,11 +9,8 @@ inputs:
runs:
using: "composite"
steps:
# Fork PRs on pull_request already can't write to the base repo's cache (GitHub built-in).
# The fork guard is defense-in-depth for pull_request_target, which does have write access.
- name: Save cache
if: ${{ format('{0}', github.event.pull_request.head.repo.fork) != 'true' }}
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 (save)
uses: actions/cache/save@v4
with:
key: ${{ inputs.key }}
path: |
@@ -48,7 +48,7 @@ runs:
fi
- name: Checkout docker compose files
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
repository: ${{ inputs.twenty-repository }}
ref: ${{ steps.resolve.outputs.git-ref }}
+4 -7
View File
@@ -29,12 +29,12 @@ runs:
echo "packages/*/node_modules" >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
- name: Setup Node.js and get yarn cache
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Restore node_modules
id: cache-node-modules
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 (restore)
uses: actions/cache/restore@v4
with:
key: v4-${{ steps.globals.outputs.CACHE_KEY_PREFIX }}-${{github.sha}}
restore-keys: v4-${{ steps.globals.outputs.CACHE_KEY_PREFIX }}-
@@ -44,13 +44,10 @@ runs:
shell: ${{ steps.globals.outputs.ACTION_SHELL }}
run: |
yarn config set enableHardenedMode true
yarn config set enableScripts false
yarn --immutable --check-cache
# Fork PRs on pull_request already can't write to the base repo's cache (GitHub built-in).
# The fork guard is defense-in-depth for pull_request_target, which does have write access.
- name: Save cache
if: ${{ steps.cache-node-modules.outputs.cache-hit != 'true' && steps.cache-node-modules.outputs.cache-matched-key == '' && format('{0}', github.event.pull_request.head.repo.fork) != 'true' }}
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 (save)
if: ${{ steps.cache-node-modules.outputs.cache-hit != 'true' && steps.cache-node-modules.outputs.cache-matched-key == '' }}
uses: actions/cache/save@v4
with:
key: ${{ steps.cache-node-modules.outputs.cache-primary-key }}
path: ${{ steps.globals.outputs.PATH_TO_CACHE }}
+6 -5
View File
@@ -14,8 +14,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Repository Dispatch
env:
GH_TOKEN: ${{ secrets.TWENTY_INFRA_TOKEN }}
run: |
gh api repos/twentyhq/twenty-infra/dispatches \
-f event_type=auto-deploy-main
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.TWENTY_INFRA_TOKEN }}
repository: twentyhq/twenty-infra
event-type: auto-deploy-main
client-payload: '{"github": ${{ toJson(github) }}}' # Passes the entire github context to the downstream workflow
+6 -7
View File
@@ -14,10 +14,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Repository Dispatch
env:
GH_TOKEN: ${{ secrets.TWENTY_INFRA_TOKEN }}
REF_NAME: ${{ github.ref_name }}
run: |
gh api repos/twentyhq/twenty-infra/dispatches \
-f event_type=auto-deploy-tag \
-f "client_payload[github][ref_name]=$REF_NAME"
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.TWENTY_INFRA_TOKEN }}
repository: twentyhq/twenty-infra
event-type: auto-deploy-tag
client-payload: '{"github": ${{ toJson(github) }}}' # Passes the entire github context to the downstream workflow
+2 -2
View File
@@ -21,11 +21,11 @@ jobs:
any_changed: ${{ steps.changed-files.outputs.any_changed }}
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Check for changed files
id: changed-files
uses: tj-actions/changed-files@48d8f15b2aaa3d255ca5af3eba4870f807ce6b3c # v45.0.9
uses: tj-actions/changed-files@v45
with:
files: ${{ inputs.files }}
+7 -6
View File
@@ -17,7 +17,7 @@ jobs:
NODE_OPTIONS: '--max-old-space-size=4096'
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
ref: main
@@ -41,7 +41,7 @@ jobs:
- name: Create pull request
if: steps.changes.outputs.changed == 'true'
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.6
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: sync AI model catalog from models.dev'
@@ -61,7 +61,8 @@ jobs:
- name: Trigger automerge
if: steps.changes.outputs.changed == 'true'
env:
GH_TOKEN: ${{ secrets.TWENTY_INFRA_TOKEN }}
run: |
gh api repos/twentyhq/twenty-infra/dispatches -f event_type=automated-pr-ready
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.TWENTY_INFRA_TOKEN }}
repository: twentyhq/twenty-infra
event-type: automated-pr-ready
+14 -40
View File
@@ -66,7 +66,7 @@ jobs:
steps:
- name: Checkout current branch
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
@@ -164,17 +164,9 @@ jobs:
interval=5
elapsed=0
ADMIN_TOKEN=$(jq -r '.APPLE_JANE_ADMIN_ACCESS_TOKEN' packages/twenty-server/test/integration/constants/test-tokens.json)
while [ $elapsed -lt $timeout ]; do
GRAPHQL_RESPONSE=$(curl -s -X POST "http://localhost:${{ env.CURRENT_SERVER_PORT }}/graphql" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-d '{"query":"{ __schema { queryType { name } } }"}' 2>/dev/null || echo '{}')
if echo "$GRAPHQL_RESPONSE" | jq -e '.data.__schema' > /dev/null 2>&1 && \
curl -fsS "http://localhost:${{ env.CURRENT_SERVER_PORT }}/rest/open-api/core" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" > /dev/null 2>&1; then
if curl -s "http://localhost:${{ env.CURRENT_SERVER_PORT }}/graphql" > /dev/null 2>&1 && \
curl -s "http://localhost:${{ env.CURRENT_SERVER_PORT }}/rest/open-api/core" > /dev/null 2>&1; then
echo "Current branch server is ready!"
break
fi
@@ -185,9 +177,10 @@ jobs:
done
if [ $elapsed -ge $timeout ]; then
echo "::warning::Timed out waiting for current branch server to serve a valid schema. Validation will skip the API diff."
echo "Timeout waiting for current branch server to start"
echo "Current server log:"
cat /tmp/current-server.log || echo "No current server log found"
exit 1
fi
- name: Download GraphQL and REST responses from current branch
@@ -331,17 +324,9 @@ jobs:
interval=5
elapsed=0
ADMIN_TOKEN=$(jq -r '.APPLE_JANE_ADMIN_ACCESS_TOKEN' packages/twenty-server/test/integration/constants/test-tokens.json)
while [ $elapsed -lt $timeout ]; do
GRAPHQL_RESPONSE=$(curl -s -X POST "http://localhost:${{ env.MAIN_SERVER_PORT }}/graphql" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-d '{"query":"{ __schema { queryType { name } } }"}' 2>/dev/null || echo '{}')
if echo "$GRAPHQL_RESPONSE" | jq -e '.data.__schema' > /dev/null 2>&1 && \
curl -fsS "http://localhost:${{ env.MAIN_SERVER_PORT }}/rest/open-api/core" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" > /dev/null 2>&1; then
if curl -s "http://localhost:${{ env.MAIN_SERVER_PORT }}/graphql" > /dev/null 2>&1 && \
curl -s "http://localhost:${{ env.MAIN_SERVER_PORT }}/rest/open-api/core" > /dev/null 2>&1; then
echo "Main branch server is ready!"
break
fi
@@ -352,9 +337,10 @@ jobs:
done
if [ $elapsed -ge $timeout ]; then
echo "::warning::Timed out waiting for main branch server to serve a valid schema. Validation will skip the API diff."
echo "Timeout waiting for main branch server to start"
echo "Main server log:"
cat /tmp/main-server.log || echo "No main server log found"
exit 1
fi
- name: Download GraphQL and REST responses from main branch
@@ -421,23 +407,11 @@ jobs:
valid=true
for file in main-schema-introspection.json current-schema-introspection.json \
main-metadata-schema-introspection.json current-metadata-schema-introspection.json; do
if [ ! -f "$file" ]; then
echo "::warning::Missing GraphQL schema file: $file"
valid=false
elif ! jq -e '.data.__schema' "$file" >/dev/null 2>&1; then
echo "::warning::File $file is not a valid GraphQL introspection result. First 200 bytes: $(head -c 200 "$file")"
valid=false
fi
done
for file in main-rest-api.json current-rest-api.json \
main-metadata-schema-introspection.json current-metadata-schema-introspection.json \
main-rest-api.json current-rest-api.json \
main-rest-metadata-api.json current-rest-metadata-api.json; do
if [ ! -f "$file" ]; then
echo "::warning::Missing OpenAPI spec file: $file"
valid=false
elif ! jq -e '.openapi // .swagger' "$file" >/dev/null 2>&1; then
echo "::warning::File $file is not a valid OpenAPI spec. First 200 bytes: $(head -c 200 "$file")"
if [ ! -f "$file" ] || ! jq empty "$file" 2>/dev/null; then
echo "::warning::Invalid or missing schema file: $file"
valid=false
fi
done
@@ -634,7 +608,7 @@ jobs:
- name: Upload breaking changes report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@v4
with:
name: breaking-changes-report
path: |
@@ -57,7 +57,7 @@ jobs:
TWENTY_API_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
@@ -55,7 +55,7 @@ jobs:
TWENTY_API_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
@@ -57,7 +57,7 @@ jobs:
TWENTY_API_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
+5 -1
View File
@@ -30,8 +30,12 @@ jobs:
matrix:
task: [lint, typecheck, test]
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
+6 -1
View File
@@ -28,8 +28,13 @@ jobs:
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Fetch local actions
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
@@ -54,7 +54,7 @@ jobs:
TWENTY_API_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
- name: Install dependencies
uses: ./.github/actions/yarn-install
@@ -54,7 +54,7 @@ jobs:
TWENTY_API_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
- name: Install dependencies
uses: ./.github/actions/yarn-install
@@ -32,8 +32,12 @@ jobs:
matrix:
task: [build, typecheck, lint]
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
@@ -46,8 +50,12 @@ jobs:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
@@ -55,7 +63,7 @@ jobs:
- name: Build storybook
run: npx nx storybook:build twenty-front-component-renderer
- name: Upload storybook build
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@v4
with:
name: storybook-twenty-front-component-renderer
path: packages/twenty-front-component-renderer/storybook-static
@@ -68,7 +76,7 @@ jobs:
STORYBOOK_URL: http://localhost:6008
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
@@ -76,7 +84,7 @@ jobs:
- name: Build dependencies
run: npx nx build twenty-sdk
- name: Download storybook build
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
uses: actions/download-artifact@v4
with:
name: storybook-twenty-front-component-renderer
path: packages/twenty-front-component-renderer/storybook-static
+22 -10
View File
@@ -38,8 +38,12 @@ jobs:
env:
REACT_APP_SERVER_BASE_URL: http://localhost:3000
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Fetch local actions
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
@@ -51,7 +55,7 @@ jobs:
- name: Front / Build storybook
run: npx nx storybook:build twenty-front
- name: Upload storybook build
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@v4
with:
name: storybook-static
path: packages/twenty-front/storybook-static
@@ -75,7 +79,7 @@ jobs:
STORYBOOK_URL: http://localhost:6006
steps:
- name: Fetch local actions
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
@@ -92,7 +96,7 @@ jobs:
npx nx build twenty-ui
npx nx build twenty-front-component-renderer
- name: Download storybook build
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
uses: actions/download-artifact@v4
with:
name: storybook-static
path: packages/twenty-front/storybook-static
@@ -117,7 +121,7 @@ jobs:
# exit 1
# fi
# - name: Upload coverage artifact
# uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
# uses: actions/upload-artifact@v4
# with:
# retention-days: 1
# name: coverage-artifacts-${{ matrix.storybook_scope }}-${{ github.run_id }}-${{ matrix.shard }}
@@ -132,12 +136,12 @@ jobs:
# matrix:
# storybook_scope: [modules, pages, performance]
# steps:
# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
# - uses: actions/checkout@v4
# with:
# fetch-depth: 10
# - name: Install dependencies
# uses: ./.github/actions/yarn-install
# - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
# - uses: actions/download-artifact@v4
# with:
# pattern: coverage-artifacts-${{ matrix.storybook_scope }}-${{ github.run_id }}-*
# merge-multiple: true
@@ -160,8 +164,12 @@ jobs:
matrix:
task: [lint, typecheck, test]
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
@@ -195,8 +203,12 @@ jobs:
NODE_OPTIONS: "--max-old-space-size=10240"
ANALYZE: "true"
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
@@ -206,7 +218,7 @@ jobs:
- name: Build frontend
run: npx nx build twenty-front
# - name: Upload frontend build artifact
# uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
# uses: actions/upload-artifact@v4
# with:
# name: frontend-build
# path: packages/twenty-front/build
+5 -5
View File
@@ -41,11 +41,11 @@ jobs:
ports:
- 6379:6379
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@v4
with:
fetch-depth: 10
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
- uses: actions/setup-node@v4
with:
node-version: lts/*
@@ -53,7 +53,7 @@ jobs:
uses: ./.github/actions/yarn-install
- name: Restore Nx build cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 (restore)
uses: actions/cache/restore@v4
with:
key: v4-e2e-build-${{ github.ref_name }}-${{ github.sha }}
restore-keys: |
@@ -83,7 +83,7 @@ jobs:
- name: Save Nx build cache
if: always()
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 (save)
uses: actions/cache/save@v4
with:
key: v4-e2e-build-${{ github.ref_name }}-${{ github.sha }}
path: |
@@ -119,7 +119,7 @@ jobs:
- name: Upload Playwright results
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@v4
with:
name: playwright-results
path: |
+2 -2
View File
@@ -28,7 +28,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.ref }}
@@ -47,7 +47,7 @@ jobs:
printf '%s\n' "$VERSION" > version.txt
- name: Create Pull Request
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
uses: peter-evans/create-pull-request@v6
with:
branch: release/${{ steps.sanitize.outputs.version }}
commit-message: "chore: release v${{ steps.sanitize.outputs.version }}"
+2 -2
View File
@@ -30,7 +30,7 @@ jobs:
fi
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
ref: main
@@ -55,7 +55,7 @@ jobs:
git tag "v${{ env.VERSION }}"
git push origin "v${{ env.VERSION }}"
- uses: release-drafter/release-drafter@09c613e259eb8d4e7c81c2cb00618eb5fc4575a7 # v5
- uses: release-drafter/release-drafter@v5
if: contains(github.event.pull_request.labels.*.name, 'create_release')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+6 -2
View File
@@ -30,8 +30,12 @@ jobs:
matrix:
task: [lint, typecheck, test:unit, test:integration]
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
@@ -70,7 +74,7 @@ jobs:
TWENTY_API_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
+7 -7
View File
@@ -37,7 +37,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
@@ -65,7 +65,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
@@ -83,12 +83,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Get changed upgrade-version-command files
id: changed-files
uses: tj-actions/changed-files@48d8f15b2aaa3d255ca5af3eba4870f807ce6b3c # v45.0.9
uses: tj-actions/changed-files@v45
with:
files: |
packages/twenty-server/src/database/commands/upgrade-version-command/**
@@ -178,7 +178,7 @@ jobs:
- 6379:6379
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
@@ -278,7 +278,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
@@ -341,7 +341,7 @@ jobs:
SHARD_COUNTER: 10
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
+5 -1
View File
@@ -29,8 +29,12 @@ jobs:
matrix:
task: [lint, typecheck, test]
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
@@ -26,9 +26,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
- name: Login to Docker Hub
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
@@ -101,9 +101,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
- name: Login to Docker Hub
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
+13 -5
View File
@@ -30,8 +30,12 @@ jobs:
matrix:
task: [lint, typecheck, test]
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
@@ -44,8 +48,12 @@ jobs:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
@@ -53,7 +61,7 @@ jobs:
- name: Build storybook
run: npx nx storybook:build twenty-ui
- name: Upload storybook build
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@v4
with:
name: storybook-twenty-ui
path: packages/twenty-ui/storybook-static
@@ -66,7 +74,7 @@ jobs:
STORYBOOK_URL: http://localhost:6007
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
@@ -74,7 +82,7 @@ jobs:
- name: Build dependencies
run: npx nx build twenty-shared
- name: Download storybook build
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
uses: actions/download-artifact@v4
with:
name: storybook-twenty-ui
path: packages/twenty-ui/storybook-static
+2 -2
View File
@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
if: github.event.action != 'closed'
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@v4
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Utils / Run Danger.js
@@ -38,7 +38,7 @@ jobs:
runs-on: ubuntu-latest
if: github.event.action == 'closed' && github.event.pull_request.merged == true
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@v4
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Run congratulate-dangerfile.js
+5 -1
View File
@@ -32,8 +32,12 @@ jobs:
matrix:
task: [lint, typecheck, test]
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
+6 -2
View File
@@ -46,7 +46,7 @@ jobs:
- 6379:6379
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
@@ -97,8 +97,12 @@ jobs:
matrix:
task: [lint, typecheck, validate]
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
+18 -45
View File
@@ -8,12 +8,10 @@ on:
pull_request_review:
types: [submitted]
issues:
types: [opened]
types: [opened, assigned]
repository_dispatch:
types: [claude-core-team-issues]
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.event.issue.number || github.event.pull_request.number || github.event.client_payload.issue_number }}
cancel-in-progress: false
@@ -21,36 +19,17 @@ concurrency:
jobs:
claude:
if: |
(
github.event_name == 'issue_comment' &&
contains(github.event.comment.body, '@claude') &&
github.event.comment.user.type != 'Bot' &&
contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
) ||
(
github.event_name == 'pull_request_review_comment' &&
contains(github.event.comment.body, '@claude') &&
github.event.comment.user.type != 'Bot' &&
contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
) ||
(
github.event_name == 'pull_request_review' &&
contains(github.event.review.body, '@claude') &&
github.event.review.user.type != 'Bot' &&
contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association)
) ||
(
github.event_name == 'issues' &&
github.event.action == 'opened' &&
(contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) &&
contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association)
)
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') && github.event.comment.user.type != 'Bot') ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') && github.event.comment.user.type != 'Bot') ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && github.event.review.user.type != 'Bot') ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
services:
postgres:
image: postgres:16
@@ -71,14 +50,14 @@ jobs:
- 6379:6379
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Run Claude Code
id: claude-code
uses: anthropics/claude-code-action@dde2242db6af13460b916652159b6ba19a598f30 # v1
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
additional_permissions: |
@@ -123,6 +102,7 @@ jobs:
contents: write
pull-requests: write
issues: write
id-token: write
services:
postgres:
image: postgres:16
@@ -142,14 +122,14 @@ jobs:
ports:
- 6379:6379
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build prompt from dispatch payload
id: prompt
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
uses: actions/github-script@v7
with:
script: |
const p = context.payload.client_payload;
@@ -164,7 +144,7 @@ jobs:
core.setOutput('issue_number', p.issue_number);
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@dde2242db6af13460b916652159b6ba19a598f30 # v1
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
prompt: ${{ steps.prompt.outputs.prompt }}
@@ -179,16 +159,9 @@ jobs:
}
- name: Dispatch response to ci-privileged
if: always()
env:
GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
REPO: ${{ steps.prompt.outputs.repo }}
ISSUE_NUMBER: ${{ steps.prompt.outputs.issue_number }}
RUN_ID: ${{ github.run_id }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
gh api repos/twentyhq/ci-privileged/dispatches \
-f event_type=claude-cross-repo-response \
-f "client_payload[repo]=$REPO" \
-f "client_payload[issue_number]=$ISSUE_NUMBER" \
-f "client_payload[run_id]=$RUN_ID" \
-f "client_payload[run_url]=$RUN_URL"
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
repository: twentyhq/ci-privileged
event-type: claude-cross-repo-response
client-payload: '{"repo": ${{ toJSON(steps.prompt.outputs.repo) }}, "issue_number": ${{ toJSON(steps.prompt.outputs.issue_number) }}, "run_id": ${{ toJSON(github.run_id) }}, "run_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}'
+6 -5
View File
@@ -37,7 +37,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
token: ${{ github.token }}
repository: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || github.repository }}
@@ -153,7 +153,8 @@ jobs:
- name: Trigger i18n automerge
if: github.event_name != 'pull_request' && steps.check_changes.outputs.changes_detected == 'true'
env:
GH_TOKEN: ${{ secrets.TWENTY_INFRA_TOKEN }}
run: |
gh api repos/twentyhq/twenty-infra/dispatches -f event_type=i18n-pr-ready
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.TWENTY_INFRA_TOKEN }}
repository: twentyhq/twenty-infra
event-type: i18n-pr-ready
+2 -2
View File
@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
token: ${{ github.token }}
ref: ${{ github.ref }}
@@ -36,7 +36,7 @@ jobs:
run: yarn docs:generate-navigation-template
- name: Upload docs to Crowdin
uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2
uses: crowdin/github-action@v2
with:
upload_sources: true
upload_translations: false
+7 -6
View File
@@ -35,7 +35,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
token: ${{ github.token }}
ref: ${{ github.head_ref || github.ref_name }}
@@ -69,7 +69,7 @@ jobs:
- name: Pull translations from Crowdin
if: inputs.force_pull || steps.compile_translations_strict.outcome == 'failure'
uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2
uses: crowdin/github-action@v2
with:
upload_sources: false
upload_translations: false
@@ -139,7 +139,8 @@ jobs:
- name: Trigger i18n automerge
if: steps.compile_translations.outputs.changes_detected == 'true'
env:
GH_TOKEN: ${{ secrets.TWENTY_INFRA_TOKEN }}
run: |
gh api repos/twentyhq/twenty-infra/dispatches -f event_type=i18n-pr-ready
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.TWENTY_INFRA_TOKEN }}
repository: twentyhq/twenty-infra
event-type: i18n-pr-ready
+7 -6
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
token: ${{ github.token }}
ref: main
@@ -80,7 +80,7 @@ jobs:
- name: Upload missing translations
if: steps.check_extract_changes.outputs.changes_detected == 'true'
uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2
uses: crowdin/github-action@v2
with:
upload_sources: true
upload_translations: true
@@ -105,7 +105,8 @@ jobs:
- name: Trigger i18n automerge
if: steps.check_extract_changes.outputs.changes_detected == 'true' || steps.check_compile_changes.outputs.changes_detected == 'true'
env:
GH_TOKEN: ${{ secrets.TWENTY_INFRA_TOKEN }}
run: |
gh api repos/twentyhq/twenty-infra/dispatches -f event_type=i18n-pr-ready
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.TWENTY_INFRA_TOKEN }}
repository: twentyhq/twenty-infra
event-type: i18n-pr-ready
+7 -14
View File
@@ -16,7 +16,7 @@ jobs:
steps:
- name: Get PR number from workflow run
id: pr-info
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
uses: actions/github-script@v7
with:
script: |
const runId = context.payload.workflow_run.id;
@@ -63,16 +63,9 @@ jobs:
- name: Dispatch to ci-privileged
if: steps.pr-info.outputs.has_pr == 'true'
env:
GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
PR_NUMBER: ${{ steps.pr-info.outputs.pr_number }}
RUN_ID: ${{ steps.pr-info.outputs.run_id }}
REPOSITORY: ${{ github.repository }}
BRANCH_STATE: ${{ github.event.workflow_run.head_branch }}
run: |
gh api repos/twentyhq/ci-privileged/dispatches \
-f event_type=breaking-changes-report \
-f "client_payload[pr_number]=$PR_NUMBER" \
-f "client_payload[run_id]=$RUN_ID" \
-f "client_payload[repo]=$REPOSITORY" \
-f "client_payload[branch_state]=$BRANCH_STATE"
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
repository: twentyhq/ci-privileged
event-type: breaking-changes-report
client-payload: '{"pr_number": ${{ toJSON(steps.pr-info.outputs.pr_number) }}, "run_id": ${{ toJSON(steps.pr-info.outputs.run_id) }}, "repo": ${{ toJSON(github.repository) }}, "branch_state": ${{ toJSON(github.event.workflow_run.head_branch) }}}'
+12 -22
View File
@@ -36,27 +36,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Trigger preview environment workflow
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
REPOSITORY: ${{ github.repository }}
run: |
gh api repos/"$REPOSITORY"/dispatches \
-f event_type=preview-environment \
-f "client_payload[pr_number]=$PR_NUMBER" \
-f "client_payload[pr_head_sha]=$PR_HEAD_SHA" \
-f "client_payload[repo_full_name]=$REPOSITORY"
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.repository }}
event-type: preview-environment
client-payload: '{"pr_number": "${{ github.event.pull_request.number }}", "pr_head_sha": "${{ github.event.pull_request.head.sha }}", "repo_full_name": "${{ github.repository }}"}'
- name: Dispatch to ci-privileged for PR comment
env:
GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
KEEPALIVE_DISPATCH_TIME: ${{ github.event.pull_request.updated_at }}
REPOSITORY: ${{ github.repository }}
run: |
gh api repos/twentyhq/ci-privileged/dispatches \
-f event_type=preview-env-url \
-f "client_payload[pr_number]=$PR_NUMBER" \
-f "client_payload[keepalive_dispatch_time]=$KEEPALIVE_DISPATCH_TIME" \
-f "client_payload[repo]=$REPOSITORY"
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
repository: twentyhq/ci-privileged
event-type: preview-env-url
client-payload: '{"pr_number": ${{ toJSON(github.event.pull_request.number) }}, "keepalive_dispatch_time": ${{ toJSON(github.event.pull_request.updated_at) }}, "repo": ${{ toJSON(github.repository) }}}'
+10 -57
View File
@@ -13,12 +13,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout PR
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
ref: ${{ github.event.client_payload.pr_head_sha }}
- name: Login to Docker Hub
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
@@ -56,54 +56,10 @@ jobs:
- name: Create Tunnel
id: expose-tunnel
env:
CLOUDFLARED_VERSION: '2026.3.0'
run: |
set -euo pipefail
# Install cloudflared (pinned for reproducibility)
sudo curl -fsSL -o /usr/local/bin/cloudflared \
"https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64"
sudo chmod +x /usr/local/bin/cloudflared
cloudflared --version
# Start an account-less "quick tunnel" pointing at the server container.
# Cloudflare prints the assigned https://*.trycloudflare.com URL into the log.
log_file="$RUNNER_TEMP/cloudflared.log"
: > "$log_file"
cloudflared tunnel \
--url http://localhost:3000 \
--no-autoupdate \
--logfile "$log_file" \
--loglevel info \
> "$RUNNER_TEMP/cloudflared.stdout" 2>&1 &
pid=$!
echo "$pid" > "$RUNNER_TEMP/cloudflared.pid"
echo "cloudflared PID: $pid"
# Wait up to 2 minutes for the URL to appear; fail fast if cloudflared exits.
url=''
for _ in $(seq 1 60); do
url=$(grep -oE 'https://[a-zA-Z0-9-]+\.trycloudflare\.com' "$log_file" 2>/dev/null | head -n1 || true)
[ -n "$url" ] && break
if ! kill -0 "$pid" 2>/dev/null; then
echo "cloudflared exited before producing a URL"
cat "$log_file" || true
exit 1
fi
sleep 2
done
if [ -z "$url" ]; then
echo "Timed out waiting for tunnel URL"
cat "$log_file" || true
exit 1
fi
echo "Tunnel URL: $url"
echo "tunnel-url=$url" >> "$GITHUB_OUTPUT"
uses: codetalkio/expose-tunnel@v1.5.0
with:
service: bore.pub
port: 3000
- name: Start services with correct SERVER_URL
env:
@@ -143,9 +99,9 @@ jobs:
- name: Seed Dev Workspace
run: |
cd packages/twenty-docker/
echo "Seeding light dev workspace (Apple only)..."
if ! docker compose exec -T server yarn command:prod workspace:seed:dev --light; then
echo "❌ Seeding light dev workspace failed. Dumping server logs..."
echo "Seeding full dev workspace..."
if ! docker compose exec -T server yarn command:prod -- workspace:seed:dev; then
echo "❌ Seeding full dev workspace failed. Dumping server logs..."
docker compose logs server
exit 1
fi
@@ -166,7 +122,7 @@ jobs:
echo "$TUNNEL_URL" > tunnel-url.txt
- name: Upload tunnel URL artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@v4
with:
name: tunnel-url
path: tunnel-url.txt
@@ -178,9 +134,6 @@ jobs:
- name: Cleanup
if: always()
run: |
if [ -f "$RUNNER_TEMP/cloudflared.pid" ]; then
kill "$(cat "$RUNNER_TEMP/cloudflared.pid")" 2>/dev/null || true
fi
cd packages/twenty-docker/
docker compose down -v
working-directory: ./
@@ -23,7 +23,7 @@ jobs:
steps:
- name: Determine project and artifact name
id: project
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
uses: actions/github-script@v7
with:
script: |
const workflowName = context.payload.workflow_run.name;
@@ -43,7 +43,7 @@ jobs:
- name: Check if storybook artifact exists
id: check-artifact
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
uses: actions/github-script@v7
with:
script: |
const artifactName = '${{ steps.project.outputs.artifact_name }}';
@@ -65,7 +65,7 @@ jobs:
- name: Get PR number
if: steps.check-artifact.outputs.exists == 'true'
id: pr-info
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
uses: actions/github-script@v7
with:
script: |
const headBranch = context.payload.workflow_run.head_branch;
@@ -107,7 +107,7 @@ jobs:
- name: Download storybook artifact from triggering run
if: steps.check-artifact.outputs.exists == 'true' && steps.pr-info.outputs.has_pr == 'true'
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
uses: actions/download-artifact@v4
with:
name: ${{ steps.project.outputs.artifact_name }}
path: storybook-static
@@ -120,7 +120,7 @@ jobs:
- name: Upload storybook tarball
if: steps.check-artifact.outputs.exists == 'true' && steps.pr-info.outputs.has_pr == 'true'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@v4
with:
name: ${{ steps.project.outputs.tarball_name }}
path: /tmp/${{ steps.project.outputs.tarball_file }}
@@ -128,20 +128,17 @@ jobs:
- name: Dispatch to ci-privileged
if: steps.check-artifact.outputs.exists == 'true' && steps.pr-info.outputs.has_pr == 'true'
env:
GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
PR_NUMBER: ${{ steps.pr-info.outputs.pr_number }}
RUN_ID: ${{ github.run_id }}
REPOSITORY: ${{ github.repository }}
PROJECT: ${{ steps.project.outputs.project }}
BRANCH: ${{ github.event.workflow_run.head_branch }}
COMMIT: ${{ github.event.workflow_run.head_sha }}
run: |
gh api repos/twentyhq/ci-privileged/dispatches \
-f event_type=visual-regression \
-f "client_payload[pr_number]=$PR_NUMBER" \
-f "client_payload[run_id]=$RUN_ID" \
-f "client_payload[repo]=$REPOSITORY" \
-f "client_payload[project]=$PROJECT" \
-f "client_payload[branch]=$BRANCH" \
-f "client_payload[commit]=$COMMIT"
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
repository: twentyhq/ci-privileged
event-type: visual-regression
client-payload: >-
{
"pr_number": "${{ steps.pr-info.outputs.pr_number }}",
"run_id": "${{ github.run_id }}",
"repo": "${{ github.repository }}",
"project": "${{ steps.project.outputs.project }}",
"branch": "${{ github.event.workflow_run.head_branch }}",
"commit": "${{ github.event.workflow_run.head_sha }}"
}
+7 -6
View File
@@ -35,7 +35,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
token: ${{ github.token }}
ref: ${{ github.head_ref || github.ref_name }}
@@ -66,7 +66,7 @@ jobs:
- name: Pull website translations from Crowdin
if: inputs.force_pull || steps.compile_translations_strict.outcome == 'failure'
uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2
uses: crowdin/github-action@v2
with:
upload_sources: false
upload_translations: false
@@ -128,7 +128,8 @@ jobs:
- name: Trigger i18n automerge
if: steps.compile_translations.outputs.changes_detected == 'true'
env:
GH_TOKEN: ${{ secrets.TWENTY_INFRA_TOKEN }}
run: |
gh api repos/twentyhq/twenty-infra/dispatches -f event_type=i18n-pr-ready
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.TWENTY_INFRA_TOKEN }}
repository: twentyhq/twenty-infra
event-type: i18n-pr-ready
+7 -6
View File
@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
uses: actions/checkout@v4
with:
token: ${{ github.token }}
ref: main
@@ -78,7 +78,7 @@ jobs:
- name: Upload missing website translations
if: steps.check_extract_changes.outputs.changes_detected == 'true'
uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2
uses: crowdin/github-action@v2
with:
upload_sources: true
upload_translations: true
@@ -104,7 +104,8 @@ jobs:
- name: Trigger i18n automerge
if: steps.check_extract_changes.outputs.changes_detected == 'true' || steps.check_compile_changes.outputs.changes_detected == 'true'
env:
GH_TOKEN: ${{ secrets.TWENTY_INFRA_TOKEN }}
run: |
gh api repos/twentyhq/twenty-infra/dispatches -f event_type=i18n-pr-ready
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.TWENTY_INFRA_TOKEN }}
repository: twentyhq/twenty-infra
event-type: i18n-pr-ready
-2
View File
@@ -6,6 +6,4 @@ enableInlineHunks: true
nodeLinker: node-modules
npmMinimalAgeGate: "3d"
yarnPath: .yarn/releases/yarn-4.13.0.cjs
-1
View File
@@ -57,7 +57,6 @@
"lint:diff-with-main": {
"executor": "nx:run-commands",
"cache": false,
"dependsOn": ["twenty-oxlint-rules:build"],
"options": {
"command": "FILES=$(git diff --name-only --diff-filter=d main -- {projectRoot}/ | grep -E '{args.pattern}'); [ -z \"$FILES\" ] && echo 'No changed files.' || (npx oxlint -c {projectRoot}/.oxlintrc.json $FILES && (prettier --check $FILES || (echo 'ERROR: Prettier formatting check failed! Fix with: npx nx lint:diff-with-main --configuration=fix' && false)))",
"pattern": "\\.(ts|tsx|js|jsx)$"
+1 -2
View File
@@ -63,8 +63,7 @@
"packages/twenty-cli",
"packages/create-twenty-app",
"packages/twenty-oxlint-rules",
"packages/twenty-companion",
"packages/twenty-claude-skills"
"packages/twenty-companion"
]
},
"prettier": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "create-twenty-app",
"version": "2.4.0",
"version": "2.3.0",
"description": "Command-line interface to create Twenty application",
"main": "dist/cli.cjs",
"bin": "dist/cli.cjs",
@@ -1,6 +1,6 @@
{
"name": "twenty-linear",
"version": "0.1.6",
"version": "0.1.5",
"description": "Linear integration for Twenty. Connect a user's Linear account and create issues from logic functions.",
"license": "MIT",
"engines": {
@@ -20,7 +20,7 @@
"test:watch": "vitest --config vitest.unit.config.ts"
},
"dependencies": {
"twenty-sdk": "2.3.1"
"twenty-sdk": "2.3.0"
},
"devDependencies": {
"@types/node": "^24.7.2",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 427 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 KiB

@@ -1,6 +1,9 @@
import { defineApplication } from 'twenty-sdk/define';
import { ABOUT_DESCRIPTION } from './constants/ABOUT_DESCRIPTION.md';
import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
import {
APPLICATION_UNIVERSAL_IDENTIFIER,
DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
} from 'src/constants/universal-identifiers';
export default defineApplication({
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
@@ -8,19 +11,10 @@ export default defineApplication({
description:
'Connect Linear to Twenty. Each workspace member connects their own Linear account; logic functions can then create issues and read team data on their behalf.',
logoUrl: 'public/linear-logomark.svg',
aboutDescription: ABOUT_DESCRIPTION,
applicationVariables: undefined,
author: 'Twenty',
category: 'Product management',
emailSupport: 'contact@twenty.com',
screenshots: [
'public/gallery/command-menu-item-1.png',
'public/gallery/command-menu-item-2.png',
'public/gallery/command-menu-item-3.png',
'public/gallery/command-menu-item-4.png',
],
termsUrl: 'https://github.com/twentyhq/twenty?tab=License-1-ov-file#readme',
websiteUrl: 'https://www.twenty.com',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
// OAuth client_id/secret live at the registration level (one OAuth app per
// Twenty server, configured by the server admin) — not per-workspace —
// so they're declared as serverVariables, not applicationVariables.
serverVariables: {
LINEAR_CLIENT_ID: {
description:
@@ -1,17 +0,0 @@
import { defineCommandMenuItem } from 'twenty-sdk/define';
import {
CREATE_ISSUE_COMMAND_UNIVERSAL_IDENTIFIER,
CREATE_ISSUE_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
} from 'src/constants/universal-identifiers';
export default defineCommandMenuItem({
universalIdentifier: CREATE_ISSUE_COMMAND_UNIVERSAL_IDENTIFIER,
label: 'Create Linear issue',
shortLabel: 'Linear issue',
icon: 'IconPlaylistAdd',
isPinned: false,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier:
CREATE_ISSUE_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
});
@@ -15,6 +15,8 @@ export default defineConnectionProvider({
clientIdVariable: 'LINEAR_CLIENT_ID',
clientSecretVariable: 'LINEAR_CLIENT_SECRET',
tokenRequestContentType: 'form-urlencoded',
usePkce: true,
// Linear supports PKCE but doesn't require it for confidential clients.
// Disabled to keep the test surface minimal.
usePkce: false,
},
});
@@ -1,20 +0,0 @@
export const ABOUT_DESCRIPTION = `Connect your Linear account to Twenty to create issues and look up teams straight from your workflows or the AI chat.
## What you can do
Once installed and connected, two tools become available:
- **Create Linear issue**
- From the AI chat, ask something like *"create a Linear issue in the Engineering team titled 'Fix login bug'"* and the AI will file it for you.
- From a workflow, add it as a step with \`teamId\` + \`title\` (and optional \`description\`).
- **List Linear teams** — discovers the teams in your Linear workspace, useful when you need to pick a \`teamId\` for the create-issue step.
## Installing
1. Open **Settings → Applications** in your Twenty workspace.
2. Find **Linear** in the available apps and click **Install**.
3. Open the app, go to the **Connections** tab, and click **Add connection**.
4. Choose **Just for me** (your personal Linear account) or **Workspace shared** (a team-managed Linear account anyone in this workspace can act through), then complete the Linear sign-in.
That's it — you can now use the tools above.`;
@@ -17,24 +17,3 @@ export const CREATE_LINEAR_ISSUE_UNIVERSAL_IDENTIFIER =
export const LIST_LINEAR_TEAMS_UNIVERSAL_IDENTIFIER =
'15824bbc-9c64-4f97-b45f-d0a44b402bb8';
export const LIST_LINEAR_TEAMS_ROUTE_UNIVERSAL_IDENTIFIER =
'a1d4e7b3-5c2f-4a89-9b0e-6f3d8c1a7e5b';
export const CREATE_LINEAR_ISSUE_ROUTE_UNIVERSAL_IDENTIFIER =
'c8f2a6d1-3b7e-4e09-8d5c-2a9f0b4e6c3d';
export const CREATE_ISSUE_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER =
'd4a1c8e5-7f3b-4d62-a90e-1b5c3f8d2a6e';
export const LIST_LINEAR_WORKFLOW_STATES_ROUTE_UNIVERSAL_IDENTIFIER =
'f5a2d8c1-4b7e-4f93-a6d0-9c3e1b8f5a2d';
export const LIST_LINEAR_ISSUE_OPTIONS_ROUTE_UNIVERSAL_IDENTIFIER =
'a3b7e1d9-8c4f-4a26-b5d0-7e9f2c6a8b3d';
export const LIST_LINEAR_ISSUE_OPTIONS_UNIVERSAL_IDENTIFIER =
'c2b99086-6c1b-43e4-9685-de4ade6bda63';
export const CREATE_ISSUE_COMMAND_UNIVERSAL_IDENTIFIER =
'e7b3d9f2-6a1c-4e85-b4d0-8f2c5a7e3b19';
@@ -1,35 +0,0 @@
import { defineLogicFunction } from 'twenty-sdk/define';
import type { RoutePayload } from 'twenty-sdk/define';
import { CREATE_LINEAR_ISSUE_ROUTE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
import { createLinearIssueHandler } from 'src/logic-functions/handlers/create-linear-issue-handler';
const handler = async (event: RoutePayload) => {
const body = event.body as Record<string, unknown> | null;
return createLinearIssueHandler({
teamId: body?.teamId as string | undefined,
title: body?.title as string | undefined,
description: body?.description as string | undefined,
priority: body?.priority as number | undefined,
stateId: body?.stateId as string | undefined,
assigneeId: body?.assigneeId as string | undefined,
projectId: body?.projectId as string | undefined,
estimate: body?.estimate as number | undefined,
labelIds: body?.labelIds as string[] | undefined,
cycleId: body?.cycleId as string | undefined,
dueDate: body?.dueDate as string | undefined,
});
};
export default defineLogicFunction({
universalIdentifier: CREATE_LINEAR_ISSUE_ROUTE_UNIVERSAL_IDENTIFIER,
name: 'create-linear-issue-route',
timeoutSeconds: 30,
handler,
httpRouteTriggerSettings: {
path: '/linear/issues',
httpMethod: 'POST',
isAuthRequired: true,
},
});
@@ -27,45 +27,6 @@ export default defineLogicFunction({
type: 'string',
description: 'Optional issue description (Markdown supported).',
},
priority: {
type: 'integer',
description:
'Issue priority: 0 = No priority, 1 = Urgent, 2 = High, 3 = Medium, 4 = Low.',
minimum: 0,
maximum: 4,
},
stateId: {
type: 'string',
description:
'The workflow state ID for the issue status. Use list-linear-issue-options to discover available states for a team.',
},
assigneeId: {
type: 'string',
description:
'The user ID to assign the issue to. Use list-linear-issue-options to discover team members.',
},
projectId: {
type: 'string',
description: 'The project ID to associate the issue with.',
},
estimate: {
type: 'number',
description:
'The estimate value for the issue. Must match the team estimate scale.',
},
labelIds: {
type: 'array',
items: { type: 'string' },
description: 'Array of label IDs to apply to the issue.',
},
cycleId: {
type: 'string',
description: 'The cycle ID to add the issue to.',
},
dueDate: {
type: 'string',
description: 'Due date in YYYY-MM-DD format.',
},
},
required: ['teamId', 'title'],
},
@@ -79,14 +40,6 @@ export default defineLogicFunction({
teamId: { type: 'string' },
title: { type: 'string' },
description: { type: 'string' },
priority: { type: 'number' },
stateId: { type: 'string' },
assigneeId: { type: 'string' },
projectId: { type: 'string' },
estimate: { type: 'number' },
labelIds: { type: 'array', items: { type: 'string' } },
cycleId: { type: 'string' },
dueDate: { type: 'string' },
},
},
],
@@ -49,17 +49,6 @@ export const createLinearIssueHandler = async (
teamId: input.teamId,
title: input.title,
description: input.description,
...(input.priority !== undefined && { priority: input.priority }),
...(input.stateId !== undefined && { stateId: input.stateId }),
...(input.assigneeId !== undefined && {
assigneeId: input.assigneeId,
}),
...(input.projectId !== undefined && { projectId: input.projectId }),
...(input.estimate !== undefined && { estimate: input.estimate }),
...(input.labelIds !== undefined &&
input.labelIds.length > 0 && { labelIds: input.labelIds }),
...(input.cycleId !== undefined && { cycleId: input.cycleId }),
...(input.dueDate !== undefined && { dueDate: input.dueDate }),
},
},
});
@@ -1,129 +0,0 @@
import { listConnections } from 'twenty-sdk/logic-function';
import { callLinearGraphQL } from 'src/logic-functions/utils/call-linear-graphql';
type LinearMember = {
id: string;
name: string;
displayName: string;
};
type LinearProject = {
id: string;
name: string;
};
type LinearLabel = {
id: string;
name: string;
color: string;
};
type LinearCycle = {
id: string;
name: string | null;
number: number;
startsAt: string;
endsAt: string;
};
type LinearWorkflowState = {
id: string;
name: string;
type: string;
position: number;
};
type IssueOptionsQueryResult = {
team: {
states: { nodes: LinearWorkflowState[] };
members: { nodes: LinearMember[] };
cycles: { nodes: LinearCycle[] };
issueEstimationType: string;
issueEstimationAllowZero: boolean;
};
projects: { nodes: LinearProject[] };
issueLabels: { nodes: LinearLabel[] };
};
type IssueOptions = {
states: LinearWorkflowState[];
members: LinearMember[];
projects: LinearProject[];
labels: LinearLabel[];
cycles: LinearCycle[];
estimationType: string;
estimationAllowZero: boolean;
};
type HandlerResult =
| { success: true; options: IssueOptions }
| { success: false; error: string };
export const listLinearIssueOptionsHandler = async (input: {
teamId?: string;
}): Promise<HandlerResult> => {
if (!input.teamId) {
return { success: false, error: '`teamId` is required.' };
}
const connections = await listConnections({ providerName: 'linear' });
const connection =
connections.find((c) => c.visibility === 'workspace') ?? connections[0];
if (!connection) {
return {
success: false,
error:
'Linear is not connected. Open the app settings and click "Add connection" first.',
};
}
const result = await callLinearGraphQL<IssueOptionsQueryResult>({
accessToken: connection.accessToken,
query: `
query IssueOptions($teamId: String!) {
team(id: $teamId) {
states { nodes { id name type position } }
members { nodes { id name displayName } }
cycles { nodes { id name number startsAt endsAt } }
issueEstimationType
issueEstimationAllowZero
}
projects { nodes { id name } }
issueLabels { nodes { id name color } }
}
`,
variables: { teamId: input.teamId },
});
if (result.errors || !result.data) {
return {
success: false,
error: result.errors?.[0]?.message ?? 'Unknown Linear API error',
};
}
const { team, projects, issueLabels } = result.data;
const now = new Date().toISOString();
const activeCycles = team.cycles.nodes.filter((c) => c.endsAt >= now);
return {
success: true,
options: {
states: team.states.nodes.sort((a, b) => a.position - b.position),
members: team.members.nodes.sort((a, b) =>
a.displayName.localeCompare(b.displayName),
),
projects: projects.nodes.sort((a, b) => a.name.localeCompare(b.name)),
labels: issueLabels.nodes.sort((a, b) => a.name.localeCompare(b.name)),
cycles: activeCycles.sort(
(a, b) =>
new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime(),
),
estimationType: team.issueEstimationType,
estimationAllowZero: team.issueEstimationAllowZero,
},
};
};
@@ -1,63 +0,0 @@
import { listConnections } from 'twenty-sdk/logic-function';
import { callLinearGraphQL } from 'src/logic-functions/utils/call-linear-graphql';
type LinearWorkflowState = {
id: string;
name: string;
type: string;
position: number;
};
type WorkflowStatesQueryResult = {
team: { states: { nodes: LinearWorkflowState[] } };
};
type HandlerResult =
| { success: true; states: LinearWorkflowState[] }
| { success: false; error: string };
export const listLinearWorkflowStatesHandler = async (input: {
teamId?: string;
}): Promise<HandlerResult> => {
if (!input.teamId) {
return { success: false, error: '`teamId` is required.' };
}
const connections = await listConnections({ providerName: 'linear' });
const connection =
connections.find((c) => c.visibility === 'workspace') ?? connections[0];
if (!connection) {
return {
success: false,
error:
'Linear is not connected. Open the app settings and click "Add connection" first.',
};
}
const result = await callLinearGraphQL<WorkflowStatesQueryResult>({
accessToken: connection.accessToken,
query: `
query WorkflowStates($teamId: String!) {
team(id: $teamId) {
states { nodes { id name type position } }
}
}
`,
variables: { teamId: input.teamId },
});
if (result.errors || !result.data) {
return {
success: false,
error: result.errors?.[0]?.message ?? 'Unknown Linear API error',
};
}
const states = result.data.team.states.nodes.sort(
(a, b) => a.position - b.position,
);
return { success: true, states };
};
@@ -1,23 +0,0 @@
import { defineLogicFunction } from 'twenty-sdk/define';
import type { RoutePayload } from 'twenty-sdk/define';
import { LIST_LINEAR_ISSUE_OPTIONS_ROUTE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
import { listLinearIssueOptionsHandler } from 'src/logic-functions/handlers/list-linear-issue-options-handler';
const handler = async (event: RoutePayload) => {
return listLinearIssueOptionsHandler({
teamId: event.queryStringParameters?.teamId,
});
};
export default defineLogicFunction({
universalIdentifier: LIST_LINEAR_ISSUE_OPTIONS_ROUTE_UNIVERSAL_IDENTIFIER,
name: 'list-linear-issue-options-route',
timeoutSeconds: 15,
handler,
httpRouteTriggerSettings: {
path: '/linear/issue-options',
httpMethod: 'GET',
isAuthRequired: true,
},
});
@@ -1,110 +0,0 @@
import { defineLogicFunction } from 'twenty-sdk/define';
import { LIST_LINEAR_ISSUE_OPTIONS_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
import { listLinearIssueOptionsHandler } from 'src/logic-functions/handlers/list-linear-issue-options-handler';
export default defineLogicFunction({
universalIdentifier: LIST_LINEAR_ISSUE_OPTIONS_UNIVERSAL_IDENTIFIER,
name: 'list-linear-issue-options',
description:
'Returns available options for creating a Linear issue in a specific team: workflow states, members, projects, labels, cycles, and estimation settings. Requires a teamId (call list-linear-teams to discover one).',
timeoutSeconds: 15,
handler: listLinearIssueOptionsHandler,
toolTriggerSettings: {
inputSchema: {
type: 'object',
properties: {
teamId: {
type: 'string',
description:
'The Linear team ID to fetch issue options for. Use list-linear-teams to discover available teams.',
},
},
required: ['teamId'],
},
},
workflowActionTriggerSettings: {
label: 'List Linear Issue Options',
inputSchema: [
{
type: 'object',
properties: {
teamId: { type: 'string' },
},
},
],
outputSchema: [
{
type: 'object',
properties: {
success: { type: 'boolean' },
options: {
type: 'object',
properties: {
states: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
type: { type: 'string' },
position: { type: 'number' },
},
},
},
members: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
displayName: { type: 'string' },
},
},
},
projects: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
},
},
},
labels: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
color: { type: 'string' },
},
},
},
cycles: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
number: { type: 'number' },
startsAt: { type: 'string' },
endsAt: { type: 'string' },
},
},
},
estimationType: { type: 'string' },
estimationAllowZero: { type: 'boolean' },
},
},
error: { type: 'string' },
},
},
],
},
});
@@ -1,21 +0,0 @@
import { defineLogicFunction } from 'twenty-sdk/define';
import type { RoutePayload } from 'twenty-sdk/define';
import { LIST_LINEAR_TEAMS_ROUTE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
import { listLinearTeamsHandler } from 'src/logic-functions/handlers/list-linear-teams-handler';
const handler = async (_event: RoutePayload) => {
return listLinearTeamsHandler();
};
export default defineLogicFunction({
universalIdentifier: LIST_LINEAR_TEAMS_ROUTE_UNIVERSAL_IDENTIFIER,
name: 'list-linear-teams-route',
timeoutSeconds: 15,
handler,
httpRouteTriggerSettings: {
path: '/linear/teams',
httpMethod: 'GET',
isAuthRequired: true,
},
});
@@ -16,33 +16,4 @@ export default defineLogicFunction({
properties: {},
},
},
workflowActionTriggerSettings: {
label: 'List Linear Teams',
inputSchema: [
{
type: 'object',
properties: {},
},
],
outputSchema: [
{
type: 'object',
properties: {
success: { type: 'boolean' },
teams: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
key: { type: 'string' },
},
},
},
error: { type: 'string' },
},
},
],
},
});
@@ -1,23 +0,0 @@
import { defineLogicFunction } from 'twenty-sdk/define';
import type { RoutePayload } from 'twenty-sdk/define';
import { LIST_LINEAR_WORKFLOW_STATES_ROUTE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
import { listLinearWorkflowStatesHandler } from 'src/logic-functions/handlers/list-linear-workflow-states-handler';
const handler = async (event: RoutePayload) => {
return listLinearWorkflowStatesHandler({
teamId: event.queryStringParameters?.teamId,
});
};
export default defineLogicFunction({
universalIdentifier: LIST_LINEAR_WORKFLOW_STATES_ROUTE_UNIVERSAL_IDENTIFIER,
name: 'list-linear-workflow-states-route',
timeoutSeconds: 15,
handler,
httpRouteTriggerSettings: {
path: '/linear/workflow-states',
httpMethod: 'GET',
isAuthRequired: true,
},
});
@@ -2,12 +2,4 @@ export type CreateIssueInput = {
teamId?: string;
title?: string;
description?: string;
priority?: number;
stateId?: string;
assigneeId?: string;
projectId?: string;
estimate?: number;
labelIds?: string[];
cycleId?: string;
dueDate?: string;
};
@@ -1,8 +1,10 @@
import { defineApplicationRole } from 'twenty-sdk/define';
import { defineRole } from 'twenty-sdk/define';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
export default defineApplicationRole({
// The Linear logic functions never read workspace data — they only call
// Linear's GraphQL API on behalf of the connected user.
export default defineRole({
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: 'Linear function role',
description: 'No-op role for Linear logic functions',
@@ -5,7 +5,6 @@
"declaration": true,
"outDir": "./dist",
"rootDir": ".",
"jsx": "react-jsx",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"emitDecoratorMetadata": true,
@@ -18,7 +17,7 @@
"strictBindCallApply": false,
"target": "es2020",
"module": "esnext",
"lib": ["es2020", "dom"],
"lib": ["es2020"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"resolveJsonModule": true,
@@ -3736,15 +3736,15 @@ __metadata:
languageName: node
linkType: hard
"twenty-client-sdk@npm:2.3.1":
version: 2.3.1
resolution: "twenty-client-sdk@npm:2.3.1"
"twenty-client-sdk@npm:2.3.0":
version: 2.3.0
resolution: "twenty-client-sdk@npm:2.3.0"
dependencies:
"@genql/cli": "npm:^3.0.3"
"@genql/runtime": "npm:^2.10.0"
esbuild: "npm:^0.25.0"
graphql: "npm:^16.8.1"
checksum: 10c0/b82a40b6411857345e40b81d32f7069364c6795e79306447d1c21b441315016d8c27ee2c128cd97b82379221812cb18bb556eb37dea337e83b05873b9bfc6c6f
checksum: 10c0/ab9a4f4ea9222eebdab6d80f396e5281cc0b78bc571163bc8a4f302afc38989ded21c67e0a8e2aad3cdad20f5ec24f60cd2286d9b38a86f7a77333aff5500b20
languageName: node
linkType: hard
@@ -3754,16 +3754,16 @@ __metadata:
dependencies:
"@types/node": "npm:^24.7.2"
oxlint: "npm:^0.16.0"
twenty-sdk: "npm:2.3.1"
twenty-sdk: "npm:2.3.0"
typescript: "npm:^5.9.3"
vite-tsconfig-paths: "npm:^4.3.2"
vitest: "npm:^3.1.1"
languageName: unknown
linkType: soft
"twenty-sdk@npm:2.3.1":
version: 2.3.1
resolution: "twenty-sdk@npm:2.3.1"
"twenty-sdk@npm:2.3.0":
version: 2.3.0
resolution: "twenty-sdk@npm:2.3.0"
dependencies:
"@genql/cli": "npm:^3.0.3"
"@genql/runtime": "npm:^2.10.0"
@@ -3783,7 +3783,7 @@ __metadata:
react: "npm:^19.0.0"
react-dom: "npm:^19.0.0"
tinyglobby: "npm:^0.2.15"
twenty-client-sdk: "npm:2.3.1"
twenty-client-sdk: "npm:2.3.0"
typescript: "npm:^5.9.2"
uuid: "npm:^13.0.0"
vite: "npm:^7.0.0"
@@ -3791,7 +3791,7 @@ __metadata:
zod: "npm:^4.1.11"
bin:
twenty: dist/cli.cjs
checksum: 10c0/137a7a7e6d46175019406a21694103100e744e49154f2de5529962249ab06b9fc8240bc097fd2f231b315b6b8504ba3b45279e22aa80bdb9e735cd2570497eac
checksum: 10c0/8020481e7500472ec841574458ed18a20588e31f51a8ff9b86e8ceddc933afd9e34840e4130b1aab53b66c35ea7e6b571d2f2dd7b35836bac96f487d84239c1f
languageName: node
linkType: hard
-9
View File
@@ -1,9 +0,0 @@
# twenty-claude-skills
Claude skills for working with Twenty.
Add skills under `skills/<skill-name>/SKILL.md`.
## Skills
- `twenty-record-presentation`: Retrieve and present Twenty CRM records as readable summaries or tables.
@@ -1,10 +0,0 @@
{
"name": "twenty-claude-skills",
"private": true,
"version": "0.1.0",
"description": "Claude skills for working with Twenty.",
"license": "AGPL-3.0",
"files": [
"skills"
]
}
@@ -1,159 +0,0 @@
---
name: twenty-record-presentation
description: "Retrieve and present Twenty CRM records as readable summaries or tables, using the connected Twenty MCP server to discover fields, fetch relevant data, format dates and values, build record links, and avoid raw API output."
---
# Twenty Record Presentation
## Overview
Retrieve the Twenty records needed to answer the user's question, then present them as a useful answer, not as raw API output. Always translate technical fields, timestamps, IDs, and nested structures into readable summaries that help the user scan, compare, and act.
## Retrieval Workflow
Use the selected connected Twenty MCP server when it is available
- `get_tool_catalog``learn_tools``execute_tool`
- Discover the relevant object, fields, filters, and sort options instead of guessing exact API names.
- Retrieve only the fields needed for the answer, plus the fields needed for ordering or disambiguation.
- For "latest", "most recent", or "recent" requests, include the relevant timestamp field used for sorting.
- If the user asks for a broad list, apply a practical limit and state how many records are shown.
- If required context is missing and cannot be discovered from the tools, ask one concise clarifying question.
- If no Twenty MCP tools are available, say that no callable Twenty MCP server is available in the current thread and ask the user to connect or expose the intended workspace.
## Response Shape
Start with the answer or count, then show the records in the clearest compact shape:
- For one record, use a labeled block.
- For 2 to 10 comparable records, use a Markdown table.
- For larger sets, show the most relevant rows first, mention the total, and offer the next useful filter or page only when needed.
- For nested records, summarize the important nested values instead of dumping JSON.
- When comparing records across workspaces, prefer one combined table with a Workspace column if it improves scanning. Use separate sections only when each workspace needs different columns.
Use English labels and prose. Keep user-provided names, record values, emails, URLs, and proper nouns unchanged.
## Record Links
Link records back to their original Twenty context whenever the workspace origin and record identity are known.
- Build record links with the Twenty show-page path: `/object/:objectNameSingular/:objectRecordId`.
- For absolute links, combine the workspace origin with that path, for example `https://example.twenty.com/object/person/record-id`.
- Use `recordReferences` from MCP responses when available to get `objectNameSingular`, `recordId`, and `displayName`.
- If `recordReferences` is missing, use the record's `id` and the object name from the tool that returned it.
- Prefer linking the record display name in tables and summaries instead of adding a raw ID column.
- When showing records from multiple workspaces, generate links with each record's own workspace origin.
- If the workspace origin is unknown, do not invent a hostname. Add a compact Record column with the object name and record ID, or say that direct links need the workspace URL.
## Dates and Times
Never expose ISO/RFC3339 timestamps as the main date display.
- Parse common technical formats such as `2026-05-05T09:43:18.123Z`, `2026-05-05T09:43:18+02:00`, Unix seconds, and Unix milliseconds.
- Convert instants with `Z` or an explicit offset to the user's timezone when known. If timezone is unknown, keep the source timezone or ask only when it changes the meaning.
- Preserve date-only values as dates. Do not shift date-only values across timezones.
- Display absolute dates. Use relative words such as "today", "yesterday", or "last week" only as a supplement when helpful.
- Include the year unless it is truly redundant in a small same-year table.
- Show seconds and milliseconds only when they matter for debugging, audit logs, or ordering events with near-identical times.
Examples, with user timezone Europe/Paris, UTC+2 in May:
- Timestamp: `2026-05-05T09:43:18.123Z` → May 5, 2026, 11:43 AM
- Date-only value: `2026-05-05` → May 5, 2026
If the exact raw timestamp is relevant, put it after the readable value:
- Created: May 5, 2026, 11:43 AM (raw: `2026-05-05T09:43:18.123Z`)
## Field Labels
Convert raw field names into user-facing labels:
- `createdAt` → Created
- `updatedAt` → Last updated
- `deletedAt` → Deleted
- `createdBy` → Created by
- `workspaceMemberId` → Workspace member
- `opportunityStage` → Opportunity stage
Prefer the label users see in Twenty when it is available from metadata. Otherwise, split camelCase, snake_case, and kebab-case into normal words.
## Value Formatting
Format values by meaning:
- **Empty or null**: Not set, or omit if the field is irrelevant.
- **Booleans**: Yes / No.
- **Money**: include currency and grouping, for example EUR 12,450 or USD 12,450 based on the record currency.
- **Percentages**: use `%`, round only enough to stay meaningful.
- **URLs and emails**: make them clickable Markdown links when useful.
- **IDs and UUIDs**: hide by default unless the user asks for identifiers, deduplication, debugging, or exact references.
- **Arrays**: show the count and the most important names, not the full serialized array.
## Record Ordering
When the user asks for "latest", "recent", or "last records":
- State which date field was used when it is not obvious, for example *sorted by Last updated*.
- Prefer `updatedAt` for "recent activity" and `createdAt` for "newest records" unless the user's wording or object semantics points to another date.
- Display the chosen date column in readable form.
- If multiple records share the same date, keep a deterministic secondary order such as name or ID.
## Table Alignment
Make tables easy to scan before making them visually decorative.
- Use Markdown alignment markers intentionally: text columns left-aligned (`:---`), numeric money/count columns right-aligned (`---:`), and short status columns centered only when that actually improves scanning (`:---:`).
- Keep record names on a stable left edge. If rows have favicons, use a dedicated narrow Icon column followed by a linked record-name column.
- If the table is compact and the image is known to be consistently small, it is acceptable to put `![alt](url) [Name](record-url)` in one cell. Do not also add emoji or extra symbols before the name.
- Keep fixed-format fields such as Created, Updated, Amount, and Source to the right of variable-width fields such as Name, Company, Person, and Domain.
- Use a consistent date format within a table so rows line up visually, for example *May 5, 2026, 11:43 AM* or *May 5, 11:43*.
- Prefer natural links over extra link columns: link the record name to Twenty, and link the domain or email only when that external destination is useful.
- Avoid raw ID columns in normal user-facing tables. IDs are long, visually dominant, and destroy alignment unless the user asks for them.
## Markdown Patterns
### Compact table
Use a compact table for comparable records:
```markdown
I found 5 recent opportunities, sorted by last updated date.
| Name | Stage | Amount | Last updated |
| :--- | :--- | ---: | :--- |
| [Acme renewal](https://example.twenty.com/object/opportunity/record-id-1) | Negotiation | EUR 12,450 | May 5, 2026, 11:43 AM |
| [Globex expansion](https://example.twenty.com/object/opportunity/record-id-2) | Discovery | EUR 8,000 | May 4, 2026, 4:10 PM |
```
### Labeled block
Use a labeled block for one important record:
```markdown
**[Acme renewal](https://example.twenty.com/object/opportunity/record-id-1)**
- Stage: Negotiation
- Amount: EUR 12,450
- Next action: Not set
- Last updated: May 5, 2026, 11:43 AM
```
## Raw Data Exceptions
Show raw JSON, raw timestamps, internal IDs, or full nested objects only when the user asks for debugging, export, exact API payloads, schema inspection, or reproducible commands. Even then, put a readable summary before the raw block.
Example:
> Acme renewal — Negotiation stage, EUR 12,450, last updated May 5, 2026, 11:43 AM. Full payload below:
>
> ```json
> {
> "id": "record-id-1",
> "name": "Acme renewal",
> "stage": "NEGOTIATION",
> "amountMicros": "12450000000",
> "currencyCode": "EUR",
> "updatedAt": "2026-05-05T09:43:18.123Z"
> }
> ```
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-client-sdk",
"version": "2.4.0",
"version": "2.3.0",
"sideEffects": false,
"license": "AGPL-3.0",
"scripts": {
@@ -932,7 +932,6 @@ type Workspace {
isMicrosoftAuthEnabled: Boolean!
isMicrosoftAuthBypassEnabled: Boolean!
isCustomDomainEnabled: Boolean!
isInternalMessagesImportEnabled: Boolean!
editableProfileFields: [String!]
defaultRole: Role
fastModel: String!
@@ -1748,13 +1747,17 @@ type FeatureFlag {
enum FeatureFlagKey {
IS_UNIQUE_INDEXES_ENABLED
IS_JSON_FILTER_ENABLED
IS_COMMAND_MENU_ITEM_ENABLED
IS_MARKETPLACE_SETTING_TAB_VISIBLE
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED
IS_PUBLIC_DOMAIN_ENABLED
IS_EMAILING_DOMAIN_ENABLED
IS_EMAIL_GROUP_ENABLED
IS_JUNCTION_RELATIONS_ENABLED
IS_CONNECTED_ACCOUNT_MIGRATED
IS_RICH_TEXT_V1_MIGRATED
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED
IS_DATASOURCE_MIGRATED
IS_BILLING_V2_ENABLED
}
@@ -1967,16 +1970,74 @@ type RotateClientSecret {
clientSecret: String!
}
type ApplicationRegistrationVariableDTO {
type ResendEmailVerificationToken {
success: Boolean!
}
type DeleteSso {
identityProviderId: UUID!
}
type EditSso {
id: UUID!
key: String!
value: String
description: String!
isSecret: Boolean!
isRequired: Boolean!
isFilled: Boolean!
createdAt: DateTime!
updatedAt: DateTime!
type: IdentityProviderType!
issuer: String!
name: String!
status: SSOIdentityProviderStatus!
}
type WorkspaceNameAndId {
displayName: String
id: UUID!
}
type FindAvailableSSOIDP {
type: IdentityProviderType!
id: UUID!
issuer: String!
name: String!
status: SSOIdentityProviderStatus!
workspace: WorkspaceNameAndId!
}
type SetupSso {
id: UUID!
type: IdentityProviderType!
issuer: String!
name: String!
status: SSOIdentityProviderStatus!
}
type SSOConnection {
type: IdentityProviderType!
id: UUID!
issuer: String!
name: String!
status: SSOIdentityProviderStatus!
}
type AvailableWorkspace {
id: UUID!
displayName: String
loginToken: String
personalInviteToken: String
inviteHash: String
workspaceUrls: WorkspaceUrls!
logo: String
sso: [SSOConnection!]!
}
type AvailableWorkspaces {
availableWorkspacesForSignIn: [AvailableWorkspace!]!
availableWorkspacesForSignUp: [AvailableWorkspace!]!
}
type DeletedWorkspaceMember {
id: UUID!
name: FullName!
userEmail: String!
avatarUrl: String
userWorkspaceId: UUID
}
type Relation {
@@ -2100,76 +2161,6 @@ type FieldConnection {
edges: [FieldEdge!]!
}
type ResendEmailVerificationToken {
success: Boolean!
}
type DeleteSso {
identityProviderId: UUID!
}
type EditSso {
id: UUID!
type: IdentityProviderType!
issuer: String!
name: String!
status: SSOIdentityProviderStatus!
}
type WorkspaceNameAndId {
displayName: String
id: UUID!
}
type FindAvailableSSOIDP {
type: IdentityProviderType!
id: UUID!
issuer: String!
name: String!
status: SSOIdentityProviderStatus!
workspace: WorkspaceNameAndId!
}
type SetupSso {
id: UUID!
type: IdentityProviderType!
issuer: String!
name: String!
status: SSOIdentityProviderStatus!
}
type SSOConnection {
type: IdentityProviderType!
id: UUID!
issuer: String!
name: String!
status: SSOIdentityProviderStatus!
}
type AvailableWorkspace {
id: UUID!
displayName: String
loginToken: String
personalInviteToken: String
inviteHash: String
workspaceUrls: WorkspaceUrls!
logo: String
sso: [SSOConnection!]!
}
type AvailableWorkspaces {
availableWorkspacesForSignIn: [AvailableWorkspace!]!
availableWorkspacesForSignUp: [AvailableWorkspace!]!
}
type DeletedWorkspaceMember {
id: UUID!
name: FullName!
userEmail: String!
avatarUrl: String
userWorkspaceId: UUID
}
type BillingEntitlement {
key: BillingEntitlementKey!
value: Boolean!
@@ -3046,7 +3037,7 @@ type Query {
findManyApplicationRegistrations: [ApplicationRegistration!]!
findOneApplicationRegistration(id: String!): ApplicationRegistration!
findApplicationRegistrationStats(id: String!): ApplicationRegistrationStats!
findApplicationRegistrationVariables(applicationRegistrationId: String!): [ApplicationRegistrationVariableDTO!]!
findApplicationRegistrationVariables(applicationRegistrationId: String!): [ApplicationRegistrationVariable!]!
applicationRegistrationTarballUrl(id: String!): String
currentUser: User!
currentWorkspace: Workspace!
@@ -4327,7 +4318,6 @@ input UpdateWorkspaceInput {
editableProfileFields: [String!]
enabledAiModelIds: [String!]
useRecommendedModels: Boolean
isInternalMessagesImportEnabled: Boolean
}
input WorkspaceMigrationInput {
@@ -652,7 +652,6 @@ export interface Workspace {
isMicrosoftAuthEnabled: Scalars['Boolean']
isMicrosoftAuthBypassEnabled: Scalars['Boolean']
isCustomDomainEnabled: Scalars['Boolean']
isInternalMessagesImportEnabled: Scalars['Boolean']
editableProfileFields?: Scalars['String'][]
defaultRole?: Role
fastModel: Scalars['String']
@@ -1389,7 +1388,7 @@ export interface FeatureFlag {
__typename: 'FeatureFlag'
}
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_EMAIL_GROUP_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_BILLING_V2_ENABLED'
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_EMAIL_GROUP_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_DATASOURCE_MIGRATED' | 'IS_BILLING_V2_ENABLED'
export interface WorkspaceUrls {
customUrl?: Scalars['String']
@@ -1605,17 +1604,84 @@ export interface RotateClientSecret {
__typename: 'RotateClientSecret'
}
export interface ApplicationRegistrationVariableDTO {
export interface ResendEmailVerificationToken {
success: Scalars['Boolean']
__typename: 'ResendEmailVerificationToken'
}
export interface DeleteSso {
identityProviderId: Scalars['UUID']
__typename: 'DeleteSso'
}
export interface EditSso {
id: Scalars['UUID']
key: Scalars['String']
value?: Scalars['String']
description: Scalars['String']
isSecret: Scalars['Boolean']
isRequired: Scalars['Boolean']
isFilled: Scalars['Boolean']
createdAt: Scalars['DateTime']
updatedAt: Scalars['DateTime']
__typename: 'ApplicationRegistrationVariableDTO'
type: IdentityProviderType
issuer: Scalars['String']
name: Scalars['String']
status: SSOIdentityProviderStatus
__typename: 'EditSso'
}
export interface WorkspaceNameAndId {
displayName?: Scalars['String']
id: Scalars['UUID']
__typename: 'WorkspaceNameAndId'
}
export interface FindAvailableSSOIDP {
type: IdentityProviderType
id: Scalars['UUID']
issuer: Scalars['String']
name: Scalars['String']
status: SSOIdentityProviderStatus
workspace: WorkspaceNameAndId
__typename: 'FindAvailableSSOIDP'
}
export interface SetupSso {
id: Scalars['UUID']
type: IdentityProviderType
issuer: Scalars['String']
name: Scalars['String']
status: SSOIdentityProviderStatus
__typename: 'SetupSso'
}
export interface SSOConnection {
type: IdentityProviderType
id: Scalars['UUID']
issuer: Scalars['String']
name: Scalars['String']
status: SSOIdentityProviderStatus
__typename: 'SSOConnection'
}
export interface AvailableWorkspace {
id: Scalars['UUID']
displayName?: Scalars['String']
loginToken?: Scalars['String']
personalInviteToken?: Scalars['String']
inviteHash?: Scalars['String']
workspaceUrls: WorkspaceUrls
logo?: Scalars['String']
sso: SSOConnection[]
__typename: 'AvailableWorkspace'
}
export interface AvailableWorkspaces {
availableWorkspacesForSignIn: AvailableWorkspace[]
availableWorkspacesForSignUp: AvailableWorkspace[]
__typename: 'AvailableWorkspaces'
}
export interface DeletedWorkspaceMember {
id: Scalars['UUID']
name: FullName
userEmail: Scalars['String']
avatarUrl?: Scalars['String']
userWorkspaceId?: Scalars['UUID']
__typename: 'DeletedWorkspaceMember'
}
export interface Relation {
@@ -1737,86 +1803,6 @@ export interface FieldConnection {
__typename: 'FieldConnection'
}
export interface ResendEmailVerificationToken {
success: Scalars['Boolean']
__typename: 'ResendEmailVerificationToken'
}
export interface DeleteSso {
identityProviderId: Scalars['UUID']
__typename: 'DeleteSso'
}
export interface EditSso {
id: Scalars['UUID']
type: IdentityProviderType
issuer: Scalars['String']
name: Scalars['String']
status: SSOIdentityProviderStatus
__typename: 'EditSso'
}
export interface WorkspaceNameAndId {
displayName?: Scalars['String']
id: Scalars['UUID']
__typename: 'WorkspaceNameAndId'
}
export interface FindAvailableSSOIDP {
type: IdentityProviderType
id: Scalars['UUID']
issuer: Scalars['String']
name: Scalars['String']
status: SSOIdentityProviderStatus
workspace: WorkspaceNameAndId
__typename: 'FindAvailableSSOIDP'
}
export interface SetupSso {
id: Scalars['UUID']
type: IdentityProviderType
issuer: Scalars['String']
name: Scalars['String']
status: SSOIdentityProviderStatus
__typename: 'SetupSso'
}
export interface SSOConnection {
type: IdentityProviderType
id: Scalars['UUID']
issuer: Scalars['String']
name: Scalars['String']
status: SSOIdentityProviderStatus
__typename: 'SSOConnection'
}
export interface AvailableWorkspace {
id: Scalars['UUID']
displayName?: Scalars['String']
loginToken?: Scalars['String']
personalInviteToken?: Scalars['String']
inviteHash?: Scalars['String']
workspaceUrls: WorkspaceUrls
logo?: Scalars['String']
sso: SSOConnection[]
__typename: 'AvailableWorkspace'
}
export interface AvailableWorkspaces {
availableWorkspacesForSignIn: AvailableWorkspace[]
availableWorkspacesForSignUp: AvailableWorkspace[]
__typename: 'AvailableWorkspaces'
}
export interface DeletedWorkspaceMember {
id: Scalars['UUID']
name: FullName
userEmail: Scalars['String']
avatarUrl?: Scalars['String']
userWorkspaceId?: Scalars['UUID']
__typename: 'DeletedWorkspaceMember'
}
export interface BillingEntitlement {
key: BillingEntitlementKey
value: Scalars['Boolean']
@@ -2646,7 +2632,7 @@ export interface Query {
findManyApplicationRegistrations: ApplicationRegistration[]
findOneApplicationRegistration: ApplicationRegistration
findApplicationRegistrationStats: ApplicationRegistrationStats
findApplicationRegistrationVariables: ApplicationRegistrationVariableDTO[]
findApplicationRegistrationVariables: ApplicationRegistrationVariable[]
applicationRegistrationTarballUrl?: Scalars['String']
currentUser: User
currentWorkspace: Workspace
@@ -3580,7 +3566,6 @@ export interface WorkspaceGenqlSelection{
isMicrosoftAuthEnabled?: boolean | number
isMicrosoftAuthBypassEnabled?: boolean | number
isCustomDomainEnabled?: boolean | number
isInternalMessagesImportEnabled?: boolean | number
editableProfileFields?: boolean | number
defaultRole?: RoleGenqlSelection
fastModel?: boolean | number
@@ -4579,16 +4564,92 @@ export interface RotateClientSecretGenqlSelection{
__scalar?: boolean | number
}
export interface ApplicationRegistrationVariableDTOGenqlSelection{
export interface ResendEmailVerificationTokenGenqlSelection{
success?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface DeleteSsoGenqlSelection{
identityProviderId?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface EditSsoGenqlSelection{
id?: boolean | number
key?: boolean | number
value?: boolean | number
description?: boolean | number
isSecret?: boolean | number
isRequired?: boolean | number
isFilled?: boolean | number
createdAt?: boolean | number
updatedAt?: boolean | number
type?: boolean | number
issuer?: boolean | number
name?: boolean | number
status?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface WorkspaceNameAndIdGenqlSelection{
displayName?: boolean | number
id?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface FindAvailableSSOIDPGenqlSelection{
type?: boolean | number
id?: boolean | number
issuer?: boolean | number
name?: boolean | number
status?: boolean | number
workspace?: WorkspaceNameAndIdGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
export interface SetupSsoGenqlSelection{
id?: boolean | number
type?: boolean | number
issuer?: boolean | number
name?: boolean | number
status?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface SSOConnectionGenqlSelection{
type?: boolean | number
id?: boolean | number
issuer?: boolean | number
name?: boolean | number
status?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface AvailableWorkspaceGenqlSelection{
id?: boolean | number
displayName?: boolean | number
loginToken?: boolean | number
personalInviteToken?: boolean | number
inviteHash?: boolean | number
workspaceUrls?: WorkspaceUrlsGenqlSelection
logo?: boolean | number
sso?: SSOConnectionGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
export interface AvailableWorkspacesGenqlSelection{
availableWorkspacesForSignIn?: AvailableWorkspaceGenqlSelection
availableWorkspacesForSignUp?: AvailableWorkspaceGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
export interface DeletedWorkspaceMemberGenqlSelection{
id?: boolean | number
name?: FullNameGenqlSelection
userEmail?: boolean | number
avatarUrl?: boolean | number
userWorkspaceId?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
@@ -4722,96 +4783,6 @@ export interface FieldConnectionGenqlSelection{
__scalar?: boolean | number
}
export interface ResendEmailVerificationTokenGenqlSelection{
success?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface DeleteSsoGenqlSelection{
identityProviderId?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface EditSsoGenqlSelection{
id?: boolean | number
type?: boolean | number
issuer?: boolean | number
name?: boolean | number
status?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface WorkspaceNameAndIdGenqlSelection{
displayName?: boolean | number
id?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface FindAvailableSSOIDPGenqlSelection{
type?: boolean | number
id?: boolean | number
issuer?: boolean | number
name?: boolean | number
status?: boolean | number
workspace?: WorkspaceNameAndIdGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
export interface SetupSsoGenqlSelection{
id?: boolean | number
type?: boolean | number
issuer?: boolean | number
name?: boolean | number
status?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface SSOConnectionGenqlSelection{
type?: boolean | number
id?: boolean | number
issuer?: boolean | number
name?: boolean | number
status?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface AvailableWorkspaceGenqlSelection{
id?: boolean | number
displayName?: boolean | number
loginToken?: boolean | number
personalInviteToken?: boolean | number
inviteHash?: boolean | number
workspaceUrls?: WorkspaceUrlsGenqlSelection
logo?: boolean | number
sso?: SSOConnectionGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
export interface AvailableWorkspacesGenqlSelection{
availableWorkspacesForSignIn?: AvailableWorkspaceGenqlSelection
availableWorkspacesForSignUp?: AvailableWorkspaceGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
export interface DeletedWorkspaceMemberGenqlSelection{
id?: boolean | number
name?: FullNameGenqlSelection
userEmail?: boolean | number
avatarUrl?: boolean | number
userWorkspaceId?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface BillingEntitlementGenqlSelection{
key?: boolean | number
value?: boolean | number
@@ -5709,7 +5680,7 @@ export interface QueryGenqlSelection{
findManyApplicationRegistrations?: ApplicationRegistrationGenqlSelection
findOneApplicationRegistration?: (ApplicationRegistrationGenqlSelection & { __args: {id: Scalars['String']} })
findApplicationRegistrationStats?: (ApplicationRegistrationStatsGenqlSelection & { __args: {id: Scalars['String']} })
findApplicationRegistrationVariables?: (ApplicationRegistrationVariableDTOGenqlSelection & { __args: {applicationRegistrationId: Scalars['String']} })
findApplicationRegistrationVariables?: (ApplicationRegistrationVariableGenqlSelection & { __args: {applicationRegistrationId: Scalars['String']} })
applicationRegistrationTarballUrl?: { __args: {id: Scalars['String']} }
currentUser?: UserGenqlSelection
currentWorkspace?: WorkspaceGenqlSelection
@@ -6298,7 +6269,7 @@ export interface UpdateWorkspaceMemberSettingsInput {workspaceMemberId: Scalars[
export interface ActivateWorkspaceInput {displayName?: (Scalars['String'] | null)}
export interface UpdateWorkspaceInput {subdomain?: (Scalars['String'] | null),customDomain?: (Scalars['String'] | null),displayName?: (Scalars['String'] | null),logo?: (Scalars['String'] | null),inviteHash?: (Scalars['String'] | null),isPublicInviteLinkEnabled?: (Scalars['Boolean'] | null),allowImpersonation?: (Scalars['Boolean'] | null),isGoogleAuthEnabled?: (Scalars['Boolean'] | null),isMicrosoftAuthEnabled?: (Scalars['Boolean'] | null),isPasswordAuthEnabled?: (Scalars['Boolean'] | null),isGoogleAuthBypassEnabled?: (Scalars['Boolean'] | null),isMicrosoftAuthBypassEnabled?: (Scalars['Boolean'] | null),isPasswordAuthBypassEnabled?: (Scalars['Boolean'] | null),defaultRoleId?: (Scalars['UUID'] | null),isTwoFactorAuthenticationEnforced?: (Scalars['Boolean'] | null),trashRetentionDays?: (Scalars['Float'] | null),eventLogRetentionDays?: (Scalars['Float'] | null),fastModel?: (Scalars['String'] | null),smartModel?: (Scalars['String'] | null),aiAdditionalInstructions?: (Scalars['String'] | null),editableProfileFields?: (Scalars['String'][] | null),enabledAiModelIds?: (Scalars['String'][] | null),useRecommendedModels?: (Scalars['Boolean'] | null),isInternalMessagesImportEnabled?: (Scalars['Boolean'] | null)}
export interface UpdateWorkspaceInput {subdomain?: (Scalars['String'] | null),customDomain?: (Scalars['String'] | null),displayName?: (Scalars['String'] | null),logo?: (Scalars['String'] | null),inviteHash?: (Scalars['String'] | null),isPublicInviteLinkEnabled?: (Scalars['Boolean'] | null),allowImpersonation?: (Scalars['Boolean'] | null),isGoogleAuthEnabled?: (Scalars['Boolean'] | null),isMicrosoftAuthEnabled?: (Scalars['Boolean'] | null),isPasswordAuthEnabled?: (Scalars['Boolean'] | null),isGoogleAuthBypassEnabled?: (Scalars['Boolean'] | null),isMicrosoftAuthBypassEnabled?: (Scalars['Boolean'] | null),isPasswordAuthBypassEnabled?: (Scalars['Boolean'] | null),defaultRoleId?: (Scalars['UUID'] | null),isTwoFactorAuthenticationEnforced?: (Scalars['Boolean'] | null),trashRetentionDays?: (Scalars['Float'] | null),eventLogRetentionDays?: (Scalars['Float'] | null),fastModel?: (Scalars['String'] | null),smartModel?: (Scalars['String'] | null),aiAdditionalInstructions?: (Scalars['String'] | null),editableProfileFields?: (Scalars['String'][] | null),enabledAiModelIds?: (Scalars['String'][] | null),useRecommendedModels?: (Scalars['Boolean'] | null)}
export interface WorkspaceMigrationInput {actions: WorkspaceMigrationDeleteActionInput[]}
@@ -7427,10 +7398,82 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const ApplicationRegistrationVariableDTO_possibleTypes: string[] = ['ApplicationRegistrationVariableDTO']
export const isApplicationRegistrationVariableDTO = (obj?: { __typename?: any } | null): obj is ApplicationRegistrationVariableDTO => {
if (!obj?.__typename) throw new Error('__typename is missing in "isApplicationRegistrationVariableDTO"')
return ApplicationRegistrationVariableDTO_possibleTypes.includes(obj.__typename)
const ResendEmailVerificationToken_possibleTypes: string[] = ['ResendEmailVerificationToken']
export const isResendEmailVerificationToken = (obj?: { __typename?: any } | null): obj is ResendEmailVerificationToken => {
if (!obj?.__typename) throw new Error('__typename is missing in "isResendEmailVerificationToken"')
return ResendEmailVerificationToken_possibleTypes.includes(obj.__typename)
}
const DeleteSso_possibleTypes: string[] = ['DeleteSso']
export const isDeleteSso = (obj?: { __typename?: any } | null): obj is DeleteSso => {
if (!obj?.__typename) throw new Error('__typename is missing in "isDeleteSso"')
return DeleteSso_possibleTypes.includes(obj.__typename)
}
const EditSso_possibleTypes: string[] = ['EditSso']
export const isEditSso = (obj?: { __typename?: any } | null): obj is EditSso => {
if (!obj?.__typename) throw new Error('__typename is missing in "isEditSso"')
return EditSso_possibleTypes.includes(obj.__typename)
}
const WorkspaceNameAndId_possibleTypes: string[] = ['WorkspaceNameAndId']
export const isWorkspaceNameAndId = (obj?: { __typename?: any } | null): obj is WorkspaceNameAndId => {
if (!obj?.__typename) throw new Error('__typename is missing in "isWorkspaceNameAndId"')
return WorkspaceNameAndId_possibleTypes.includes(obj.__typename)
}
const FindAvailableSSOIDP_possibleTypes: string[] = ['FindAvailableSSOIDP']
export const isFindAvailableSSOIDP = (obj?: { __typename?: any } | null): obj is FindAvailableSSOIDP => {
if (!obj?.__typename) throw new Error('__typename is missing in "isFindAvailableSSOIDP"')
return FindAvailableSSOIDP_possibleTypes.includes(obj.__typename)
}
const SetupSso_possibleTypes: string[] = ['SetupSso']
export const isSetupSso = (obj?: { __typename?: any } | null): obj is SetupSso => {
if (!obj?.__typename) throw new Error('__typename is missing in "isSetupSso"')
return SetupSso_possibleTypes.includes(obj.__typename)
}
const SSOConnection_possibleTypes: string[] = ['SSOConnection']
export const isSSOConnection = (obj?: { __typename?: any } | null): obj is SSOConnection => {
if (!obj?.__typename) throw new Error('__typename is missing in "isSSOConnection"')
return SSOConnection_possibleTypes.includes(obj.__typename)
}
const AvailableWorkspace_possibleTypes: string[] = ['AvailableWorkspace']
export const isAvailableWorkspace = (obj?: { __typename?: any } | null): obj is AvailableWorkspace => {
if (!obj?.__typename) throw new Error('__typename is missing in "isAvailableWorkspace"')
return AvailableWorkspace_possibleTypes.includes(obj.__typename)
}
const AvailableWorkspaces_possibleTypes: string[] = ['AvailableWorkspaces']
export const isAvailableWorkspaces = (obj?: { __typename?: any } | null): obj is AvailableWorkspaces => {
if (!obj?.__typename) throw new Error('__typename is missing in "isAvailableWorkspaces"')
return AvailableWorkspaces_possibleTypes.includes(obj.__typename)
}
const DeletedWorkspaceMember_possibleTypes: string[] = ['DeletedWorkspaceMember']
export const isDeletedWorkspaceMember = (obj?: { __typename?: any } | null): obj is DeletedWorkspaceMember => {
if (!obj?.__typename) throw new Error('__typename is missing in "isDeletedWorkspaceMember"')
return DeletedWorkspaceMember_possibleTypes.includes(obj.__typename)
}
@@ -7547,86 +7590,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const ResendEmailVerificationToken_possibleTypes: string[] = ['ResendEmailVerificationToken']
export const isResendEmailVerificationToken = (obj?: { __typename?: any } | null): obj is ResendEmailVerificationToken => {
if (!obj?.__typename) throw new Error('__typename is missing in "isResendEmailVerificationToken"')
return ResendEmailVerificationToken_possibleTypes.includes(obj.__typename)
}
const DeleteSso_possibleTypes: string[] = ['DeleteSso']
export const isDeleteSso = (obj?: { __typename?: any } | null): obj is DeleteSso => {
if (!obj?.__typename) throw new Error('__typename is missing in "isDeleteSso"')
return DeleteSso_possibleTypes.includes(obj.__typename)
}
const EditSso_possibleTypes: string[] = ['EditSso']
export const isEditSso = (obj?: { __typename?: any } | null): obj is EditSso => {
if (!obj?.__typename) throw new Error('__typename is missing in "isEditSso"')
return EditSso_possibleTypes.includes(obj.__typename)
}
const WorkspaceNameAndId_possibleTypes: string[] = ['WorkspaceNameAndId']
export const isWorkspaceNameAndId = (obj?: { __typename?: any } | null): obj is WorkspaceNameAndId => {
if (!obj?.__typename) throw new Error('__typename is missing in "isWorkspaceNameAndId"')
return WorkspaceNameAndId_possibleTypes.includes(obj.__typename)
}
const FindAvailableSSOIDP_possibleTypes: string[] = ['FindAvailableSSOIDP']
export const isFindAvailableSSOIDP = (obj?: { __typename?: any } | null): obj is FindAvailableSSOIDP => {
if (!obj?.__typename) throw new Error('__typename is missing in "isFindAvailableSSOIDP"')
return FindAvailableSSOIDP_possibleTypes.includes(obj.__typename)
}
const SetupSso_possibleTypes: string[] = ['SetupSso']
export const isSetupSso = (obj?: { __typename?: any } | null): obj is SetupSso => {
if (!obj?.__typename) throw new Error('__typename is missing in "isSetupSso"')
return SetupSso_possibleTypes.includes(obj.__typename)
}
const SSOConnection_possibleTypes: string[] = ['SSOConnection']
export const isSSOConnection = (obj?: { __typename?: any } | null): obj is SSOConnection => {
if (!obj?.__typename) throw new Error('__typename is missing in "isSSOConnection"')
return SSOConnection_possibleTypes.includes(obj.__typename)
}
const AvailableWorkspace_possibleTypes: string[] = ['AvailableWorkspace']
export const isAvailableWorkspace = (obj?: { __typename?: any } | null): obj is AvailableWorkspace => {
if (!obj?.__typename) throw new Error('__typename is missing in "isAvailableWorkspace"')
return AvailableWorkspace_possibleTypes.includes(obj.__typename)
}
const AvailableWorkspaces_possibleTypes: string[] = ['AvailableWorkspaces']
export const isAvailableWorkspaces = (obj?: { __typename?: any } | null): obj is AvailableWorkspaces => {
if (!obj?.__typename) throw new Error('__typename is missing in "isAvailableWorkspaces"')
return AvailableWorkspaces_possibleTypes.includes(obj.__typename)
}
const DeletedWorkspaceMember_possibleTypes: string[] = ['DeletedWorkspaceMember']
export const isDeletedWorkspaceMember = (obj?: { __typename?: any } | null): obj is DeletedWorkspaceMember => {
if (!obj?.__typename) throw new Error('__typename is missing in "isDeletedWorkspaceMember"')
return DeletedWorkspaceMember_possibleTypes.includes(obj.__typename)
}
const BillingEntitlement_possibleTypes: string[] = ['BillingEntitlement']
export const isBillingEntitlement = (obj?: { __typename?: any } | null): obj is BillingEntitlement => {
if (!obj?.__typename) throw new Error('__typename is missing in "isBillingEntitlement"')
@@ -8758,13 +8721,17 @@ export const enumLogicFunctionExecutionStatus = {
export const enumFeatureFlagKey = {
IS_UNIQUE_INDEXES_ENABLED: 'IS_UNIQUE_INDEXES_ENABLED' as const,
IS_JSON_FILTER_ENABLED: 'IS_JSON_FILTER_ENABLED' as const,
IS_COMMAND_MENU_ITEM_ENABLED: 'IS_COMMAND_MENU_ITEM_ENABLED' as const,
IS_MARKETPLACE_SETTING_TAB_VISIBLE: 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' as const,
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED: 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' as const,
IS_PUBLIC_DOMAIN_ENABLED: 'IS_PUBLIC_DOMAIN_ENABLED' as const,
IS_EMAILING_DOMAIN_ENABLED: 'IS_EMAILING_DOMAIN_ENABLED' as const,
IS_EMAIL_GROUP_ENABLED: 'IS_EMAIL_GROUP_ENABLED' as const,
IS_JUNCTION_RELATIONS_ENABLED: 'IS_JUNCTION_RELATIONS_ENABLED' as const,
IS_CONNECTED_ACCOUNT_MIGRATED: 'IS_CONNECTED_ACCOUNT_MIGRATED' as const,
IS_RICH_TEXT_V1_MIGRATED: 'IS_RICH_TEXT_V1_MIGRATED' as const,
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED: 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' as const,
IS_DATASOURCE_MIGRATED: 'IS_DATASOURCE_MIGRATED' as const,
IS_BILLING_V2_ENABLED: 'IS_BILLING_V2_ENABLED' as const
}
File diff suppressed because it is too large Load Diff
@@ -21,7 +21,7 @@ Für benutzerdefinierte Domänen müssen Sie die DNS-Einstellungen bei Ihrem Dom
## Genehmigte Domänen
Konfigurieren Sie dies unter **Einstellungen → Mitglieder → Einladen**.
Konfigurieren Sie dies unter **Einstellungen → Mitglieder → Zugriff**.
Jede Person mit einer E-Mail-Adresse in diesen Domänen darf sich automatisch für diesen Arbeitsbereich registrieren.
@@ -77,7 +77,7 @@ Verwalten Sie Einladungen, die noch nicht angenommen wurden:
Erlauben Sie Teammitgliedern, basierend auf ihrer E-Mail-Domäne automatisch beizutreten:
1. Gehen Sie zu **Einstellungen → Mitglieder → Einladen**
1. Gehen Sie zu **Einstellungen → Mitglieder → Zugriff**
2. Fügen Sie die Domäne Ihres Unternehmens hinzu (z. B. `yourcompany.com`)
3. Jede Person mit dieser E-Mail-Domäne kann ohne Einladung beitreten
@@ -146,7 +146,7 @@ Eine Subdomain ist schnell eingerichtet, während eine benutzerdefinierte Domän
</Accordion>
<Accordion title="Wie funktionieren genehmigte Zugriffsdomänen?">
Sie können genehmigte Zugriffsdomänen konfigurieren, damit Teammitglieder mit Firmen-E-Mail-Adressen Ihrem Arbeitsbereich automatisch beitreten können. Gehen Sie zu **Einstellungen → Mitglieder → Einladen** und fügen Sie Ihre Firmendomäne hinzu (z. B. `yourcompany.com`).
Sie können genehmigte Zugriffsdomänen konfigurieren, damit Teammitglieder mit Firmen-E-Mail-Adressen Ihrem Arbeitsbereich automatisch beitreten können. Gehen Sie zu **Einstellungen → Mitglieder → Zugriff** und fügen Sie Ihre Firmendomäne hinzu (z. B. `yourcompany.com`).
</Accordion>
</AccordionGroup>
@@ -21,7 +21,7 @@ Para domínios personalizados, você precisará configurar as configurações de
## Domínios Aprovados
Configure em **Configurações → Membros → Convite**.
Configure em **Configurações → Membros → Acesso**.
Qualquer pessoa com um endereço de e-mail nesses domínios pode inscrever-se neste espaço de trabalho automaticamente.
@@ -77,7 +77,7 @@ Gerencie convites que ainda não foram aceitos:
Permita que membros da equipe ingressem automaticamente com base no domínio de e-mail:
1. Vá para **Configurações → Membros → Convites**
1. Vá para **Configurações → Membros → Acesso**
2. Adicione o domínio da sua empresa (por exemplo, `yourcompany.com`)
3. Qualquer pessoa com esse domínio de e-mail pode ingressar sem convite
@@ -146,7 +146,7 @@ Um subdomínio é rápido de configurar, enquanto um domínio personalizado ofer
</Accordion>
<Accordion title="Como funcionam os domínios de acesso aprovados?">
Você pode configurar domínios de acesso aprovados para que membros da equipe com endereços de e-mail da empresa possam ingressar automaticamente no seu espaço de trabalho. Vá para **Configurações → Membros → Convidar** e adicione o domínio da sua empresa (por exemplo, `yourcompany.com`).
Você pode configurar domínios de acesso aprovados para que membros da equipe com endereços de e-mail da empresa possam ingressar automaticamente no seu espaço de trabalho. Vá para **Configurações → Membros → Acesso** e adicione o domínio da sua empresa (por exemplo, `yourcompany.com`).
</Accordion>
</AccordionGroup>
@@ -21,7 +21,7 @@ Pentru domeniile personalizate, va trebui să configurați setările DNS la furn
## Domenii Aprobate
Configurați în **Setări → Membri → Invită**.
Configurați în **Setări → Membri → Acces**.
Oricine are o adresă de email pe aceste domenii se poate înscrie automat în acest spațiu de lucru.
@@ -77,7 +77,7 @@ Gestionați invitațiile care nu au fost acceptate:
Permiteți membrilor echipei să se alăture automat pe baza domeniului lor de e-mail:
1. Accesați **Setări → Membri → Invită**
1. Accesați **Setări → Membri → Acces**
2. Adăugați domeniul companiei dvs. (de ex., `yourcompany.com`)
3. Oricine are acel domeniu de e-mail se poate alătura fără invitație
@@ -146,7 +146,7 @@ Un subdomeniu se configurează rapid, în timp ce un domeniu personalizat oferă
</Accordion>
<Accordion title="Cum funcționează domeniile de acces aprobate?">
Puteți configura domenii de acces aprobate astfel încât membrii echipei cu adrese de e-mail ale companiei să se poată alătura automat spațiului dvs. de lucru. Accesați **Setări → Membri → Invită** și adăugați domeniul companiei (de ex., `yourcompany.com`).
Puteți configura domenii de acces aprobate astfel încât membrii echipei cu adrese de e-mail ale companiei să se poată alătura automat spațiului dvs. de lucru. Accesați **Setări → Membri → Acces** și adăugați domeniul companiei (de ex., `yourcompany.com`).
</Accordion>
</AccordionGroup>
@@ -21,7 +21,7 @@ description: Настройте домен рабочего пространст
## Утвержденные домены
Настройте в **Настройки → Участники → Пригласить**.
Настройте в **Настройки → Участники → Доступ**.
Любой пользователь с адресом электронной почты в этих доменах может автоматически зарегистрироваться в этом рабочем пространстве.
@@ -77,7 +77,7 @@ description: Пригласите членов команды и управля
Разрешите участникам команды присоединяться автоматически на основе домена их электронной почты:
1. Перейдите в **Настройки → Участники → Пригласить**
1. Перейдите в **Настройки → Участники → Доступ**
2. Добавьте домен вашей компании (например, `yourcompany.com`)
3. Любой с таким доменом электронной почты сможет присоединиться без приглашения
@@ -146,7 +146,7 @@ description: Часто задаваемые вопросы о настройк
</Accordion>
<Accordion title="Как работают утвержденные домены доступа?">
Вы можете настроить утвержденные домены доступа, чтобы участники команды с корпоративными адресами электронной почты могли автоматически присоединяться к вашему рабочему пространству. Перейдите в **Настройки → Участники → Пригласить** и добавьте домен вашей компании (например, `yourcompany.com`).
Вы можете настроить утвержденные домены доступа, чтобы участники команды с корпоративными адресами электронной почты могли автоматически присоединяться к вашему рабочему пространству. Перейдите в **Настройки → Участники → Доступ** и добавьте домен вашей компании (например, `yourcompany.com`).
</Accordion>
</AccordionGroup>
@@ -21,7 +21,7 @@ Alt alan adınızı düzenleyin veya çalışma alanınız için özel bir alan
## Onaylanmış Alan Adları
**Ayarlar → Üyeler → Davet** altında yapılandırın.
**Ayarlar → Üyeler → Erişim** altında yapılandırın.
Bu alan adlarındaki bir e-posta adresine sahip olan herkes bu çalışma alanına otomatik olarak kaydolabilir.
@@ -77,7 +77,7 @@ Kabul edilmemiş davetleri yönetin:
Ekip üyelerinin e-posta alan adına göre otomatik olarak katılmasına izin verin:
1. **Ayarlar → Üyeler → Davet** bölümüne gidin
1. **Ayarlar → Üyeler → Erişim** bölümüne gidin
2. Şirketinizin alan adını ekleyin (örneğin, `yourcompany.com`)
3. Bu e-posta alan adına sahip olan herkes davet olmadan katılabilir
@@ -146,7 +146,7 @@ Alt alan adı kurmak hızlıdır; özel alan adı ise ekibiniz için tam markal
</Accordion>
<Accordion title="Onaylı erişim alan adları nasıl çalışır?">
Şirket e-posta adreslerine sahip ekip üyelerinin çalışma alanınıza otomatik olarak katılabilmeleri için onaylı erişim alan adlarını yapılandırabilirsiniz. **Ayarlar → Üyeler → Davet**'e gidin ve şirket alan adınızı ekleyin (ör. `yourcompany.com`).
Şirket e-posta adreslerine sahip ekip üyelerinin çalışma alanınıza otomatik olarak katılabilmeleri için onaylı erişim alan adlarını yapılandırabilirsiniz. **Ayarlar → Üyeler → Erişim**'e gidin ve şirket alan adınızı ekleyin (ör. `yourcompany.com`).
</Accordion>
</AccordionGroup>
@@ -21,7 +21,7 @@ For custom domains, you'll need to configure DNS settings with your domain provi
## 批准的域名
在 **设置 → 成员 → 邀请** 下进行配置。
在 **设置 → 成员 → 访问权限** 下进行配置。
Anyone with an email address at these domains is allowed to sign up for this workspace automatically.
@@ -77,7 +77,7 @@ description: 邀请团队成员并管理工作区访问权限。
根据其电子邮件域允许团队成员自动加入:
1. 转到 **设置 → 成员 → 邀请**
1. 转到 **设置 → 成员 → 访问权限**
2. 添加您公司的域名(例如,`yourcompany.com`
3. 使用该电子邮件域的任何人都可无需邀请加入。

Some files were not shown because too many files have changed in this diff Show More