Compare commits

..
Author SHA1 Message Date
Sonarly Claude Code 1183be8316 fix(sso): accept Response-signed SAML assertions from Auth0
https://sonarly.com/issue/36537?type=bug

SAML SSO authentication fails with "Invalid signature" for Auth0 identity providers that sign the Response envelope but not individual Assertions.

Fix: ## Fix Summary

Changed SAML signature validation to accept **Response-level signatures** instead of **Assertion-level signatures**, restoring compatibility with Auth0 and other standard SAML identity providers.

## Changes Made

**1. `packages/twenty-server/src/engine/core-modules/auth/strategies/saml.auth.strategy.ts`**
- Changed `wantAssertionsSigned: true` → `wantAssertionsSigned: false`
- Changed `wantAuthnResponseSigned: false` → `wantAuthnResponseSigned: true`

**2. `packages/twenty-server/src/engine/core-modules/auth/controllers/sso-auth.controller.ts`**
- Changed `wantAssertionsSigned: true` → `wantAssertionsSigned: false` in metadata generation

Both files must match to ensure the Service Provider metadata advertises the same requirements that the runtime validation enforces.

## Why This Fix Works

**SAML Response Structure:**
```
<samlp:Response>           ← Response envelope (most IdPs sign HERE)
  <Signature>...</Signature>
  <saml:Assertion>         ← Assertion (some IdPs sign here instead)
    ... user attributes ...
  </saml:Assertion>
</samlp:Response>
```

**IdP Default Behavior:**
- **Auth0, Okta, Azure AD, Google**: Sign the Response envelope (standard practice)
- **Some enterprise IdPs**: Can be configured to sign Assertions

**The Bug:**
Commit 17786a3298 (Feb 2026) changed `wantAssertionsSigned` from `false` to `true` to improve security, but Auth0 sends Response-signed (not Assertion-signed) SAML responses. The @node-saml library's validation logic is:

```javascript
if (this.options.wantAssertionsSigned || !validSignature) {
    // Validate Assertion signature
}
```

With `wantAssertionsSigned: true`, the library ALWAYS validates the Assertion signature even when a valid Response signature exists. Since Auth0's Assertions aren't signed, validation fails with "Invalid signature".

**The Fix:**
- `wantAuthnResponseSigned: true` — Require Response signature (what Auth0 provides)
- `wantAssertionsSigned: false` — Don't require Assertion signature

This accepts the standard SAML practice (Response signing) while maintaining security through signature validation. If an IdP signs both Response and Assertion, both will be validated. If an IdP signs only the Response (standard), that signature will be validated and accepted.

## Compatibility Impact

This fix restores the original behavior from commit 0f0a7966b1 (the initial SAML implementation) which had both set to `false`, with a TODO comment "Improve the feature by sign the response" — indicating Response signing was always the intended direction.

**Impact on existing SSO configurations:**
-  **Auth0**: Works (was broken, now fixed)
-  **Okta**: Works (may have been broken, now fixed)
-  **Azure AD**: Works (may have been broken, now fixed)
-  **Google Workspace**: Works (may have been broken, now fixed)
- ⚠️ **Custom IdPs configured for Assertion-only signing**: Will break, but this is a non-standard configuration

The fix prioritizes compatibility with standard IdP behavior over theoretical support for non-standard Assertion-only signing.
2026-05-10 15:03:07 +00:00
739 changed files with 6212 additions and 16303 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
+20 -52
View File
@@ -1,7 +1,7 @@
name: GraphQL and OpenAPI Breaking Changes Detection
on:
pull_request:
pull_request:
types: [opened, synchronize, edited]
branches:
- main
@@ -54,7 +54,7 @@ jobs:
image: clickhouse/clickhouse-server:25.8.8
env:
CLICKHOUSE_PASSWORD: clickhousePassword
CLICKHOUSE_URL: 'http://default:clickhousePassword@localhost:8123/twenty'
CLICKHOUSE_URL: "http://default:clickhousePassword@localhost:8123/twenty"
ports:
- 8123:8123
- 9000:9000
@@ -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
@@ -235,6 +228,7 @@ jobs:
echo "Current branch files downloaded:"
ls -la current-*
- name: Preserve current branch files
run: |
# Create a temp directory to store current branch files
@@ -304,7 +298,7 @@ jobs:
set_env_var "PG_DATABASE_URL" "postgres://postgres:postgres@localhost:5432/main_branch"
set_env_var "NODE_PORT" "${{ env.MAIN_SERVER_PORT }}"
set_env_var "REDIS_URL" "redis://localhost:6379/1"
set_env_var "REDIS_URL" "redis://localhost:6379"
set_env_var "CLICKHOUSE_URL" "http://default:clickhousePassword@localhost:8123/twenty"
set_env_var "CLICKHOUSE_PASSWORD" "clickhousePassword"
@@ -330,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
@@ -351,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
@@ -401,6 +388,7 @@ jobs:
echo "Main branch files downloaded:"
ls -la main-*
- name: Restore current branch files
run: |
# Move current branch files back to working directory
@@ -419,33 +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
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" ] || ! jq empty "$file" 2>/dev/null; then
echo "::warning::Invalid or missing schema file: $file"
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-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")"
valid=false
elif ! jq -e '.data.__schema' "$file" > /dev/null 2>&1; then
echo "::warning::Schema file is not a valid GraphQL introspection response: $file"
valid=false
fi
done
for file in main-rest-api.json current-rest-api.json \
main-rest-metadata-api.json current-rest-metadata-api.json; do
if [ ! -f "$file" ] || ! jq empty "$file" 2>/dev/null; then
echo "::warning::Invalid or missing REST API file: $file"
valid=false
fi
done
@@ -642,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: |
@@ -660,3 +626,5 @@ jobs:
if [ -f /tmp/main-server.pid ]; then
kill $(cat /tmp/main-server.pid) || true
fi
@@ -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!
@@ -2365,7 +2356,6 @@ type PublicDomain {
id: UUID!
domain: String!
isValidated: Boolean!
applicationId: UUID
createdAt: DateTime!
}
@@ -3046,7 +3036,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!
@@ -3337,8 +3327,7 @@ type Mutation {
updateLabPublicFeatureFlag(input: UpdateLabPublicFeatureFlagInput!): FeatureFlag!
enablePostgresProxy: PostgresCredentials!
disablePostgresProxy: PostgresCredentials!
createPublicDomain(domain: String!, applicationId: String): PublicDomain!
updatePublicDomain(domain: String!, applicationId: String): PublicDomain!
createPublicDomain(domain: String!): PublicDomain!
deletePublicDomain(domain: String!): Boolean!
checkPublicDomainValidRecords(domain: String!): DomainValidRecords
createEmailingDomain(domain: String!, driver: EmailingDomainDriver!): EmailingDomain!
@@ -4327,7 +4316,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']
@@ -2040,7 +2026,6 @@ export interface PublicDomain {
id: Scalars['UUID']
domain: Scalars['String']
isValidated: Scalars['Boolean']
applicationId?: Scalars['UUID']
createdAt: Scalars['DateTime']
__typename: 'PublicDomain'
}
@@ -2646,7 +2631,7 @@ export interface Query {
findManyApplicationRegistrations: ApplicationRegistration[]
findOneApplicationRegistration: ApplicationRegistration
findApplicationRegistrationStats: ApplicationRegistrationStats
findApplicationRegistrationVariables: ApplicationRegistrationVariableDTO[]
findApplicationRegistrationVariables: ApplicationRegistrationVariable[]
applicationRegistrationTarballUrl?: Scalars['String']
currentUser: User
currentWorkspace: Workspace
@@ -2871,7 +2856,6 @@ export interface Mutation {
enablePostgresProxy: PostgresCredentials
disablePostgresProxy: PostgresCredentials
createPublicDomain: PublicDomain
updatePublicDomain: PublicDomain
deletePublicDomain: Scalars['Boolean']
checkPublicDomainValidRecords?: DomainValidRecords
createEmailingDomain: EmailingDomain
@@ -3580,7 +3564,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 +4562,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 +4781,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
@@ -5066,7 +5035,6 @@ export interface PublicDomainGenqlSelection{
id?: boolean | number
domain?: boolean | number
isValidated?: boolean | number
applicationId?: boolean | number
createdAt?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
@@ -5709,7 +5677,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
@@ -5954,8 +5922,7 @@ export interface MutationGenqlSelection{
updateLabPublicFeatureFlag?: (FeatureFlagGenqlSelection & { __args: {input: UpdateLabPublicFeatureFlagInput} })
enablePostgresProxy?: PostgresCredentialsGenqlSelection
disablePostgresProxy?: PostgresCredentialsGenqlSelection
createPublicDomain?: (PublicDomainGenqlSelection & { __args: {domain: Scalars['String'], applicationId?: (Scalars['String'] | null)} })
updatePublicDomain?: (PublicDomainGenqlSelection & { __args: {domain: Scalars['String'], applicationId?: (Scalars['String'] | null)} })
createPublicDomain?: (PublicDomainGenqlSelection & { __args: {domain: Scalars['String']} })
deletePublicDomain?: { __args: {domain: Scalars['String']} }
checkPublicDomainValidRecords?: (DomainValidRecordsGenqlSelection & { __args: {domain: Scalars['String']} })
createEmailingDomain?: (EmailingDomainGenqlSelection & { __args: {domain: Scalars['String'], driver: EmailingDomainDriver} })
@@ -6298,7 +6265,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 +7394,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 +7586,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 +8717,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
@@ -37,7 +37,7 @@ Both are available as REST and GraphQL. GraphQL adds batch upserts and the abili
Authorization: Bearer YOUR_API_KEY
```
Create an API key in **Settings → API & Webhooks → + Create key**. Copy it immediately — it's shown once. Keys can be scoped to a specific role under **Settings → Members → Roles → Assignment tab** to limit what they can access.
Create an API key in **Settings → API & Webhooks → + Create key**. Copy it immediately — it's shown once. Keys can be scoped to a specific role under **Settings → Roles → Assignment tab** to limit what they can access.
<VimeoEmbed videoId="928786722" title="Creating API key" />
@@ -83,7 +83,7 @@ Your API key grants access to sensitive data. Don't share it with untrusted serv
For better security, assign a specific role to limit access:
1. Go to **Settings → Members → Roles**
1. Go to **Settings → Roles**
2. Click on the role to assign
3. Open the **Assignment** tab
4. Under **API Keys**, click **+ Assign to API key**
@@ -37,7 +37,7 @@ Beide sind als REST und GraphQL verfügbar. GraphQL bietet Batch-Upserts und die
Authorization: Bearer YOUR_API_KEY
```
Erstellen Sie einen API-Schlüssel unter **Settings > APIs & Webhooks > + Create key**. Kopieren Sie ihn sofort — er wird nur einmal angezeigt. Schlüssel können unter **Settings → Members → Roles Assignment tab** auf eine bestimmte Rolle beschränkt werden, um ihren Zugriff einzuschränken.
Erstellen Sie einen API-Schlüssel unter **Settings > APIs & Webhooks > + Create key**. Kopieren Sie ihn sofort — er wird nur einmal angezeigt. Schlüssel können unter **Settings > Roles > Assignment tab** auf eine bestimmte Rolle beschränkt werden, um ihren Zugriff einzuschränken.
<VimeoEmbed videoId="928786722" title="API-Schlüssel erstellen" />
@@ -88,7 +88,7 @@ Ihr API-Schlüssel gewährt Zugriff auf sensible Daten. Teilen Sie ihn nicht mit
Für mehr Sicherheit weisen Sie eine spezifische Rolle zu, um den Zugriff zu beschränken:
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
1. Gehen Sie zu **Einstellungen → Rollen**
2. Klicken Sie auf die Rolle, die Sie zuweisen möchten
3. Öffnen Sie den Tab **Zuweisungen**
4. Unter **API-Schlüssel** auf **+ API-Schlüssel zuweisen** klicken
@@ -9,7 +9,7 @@ KI-Agenten respektieren Ihre bestehende Berechtigungsstruktur. Dies ist besonder
## Weisen Sie einem KI-Agenten eine Rolle zu
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
1. Gehen Sie zu **Einstellungen → Rollen**
2. Klicken Sie auf die Rolle, die Sie zuweisen möchten
3. Öffnen Sie den Tab **Zuweisungen**
4. Unter **KI-Agenten** klicken Sie auf **+ KI-Agent zuweisen**
@@ -17,7 +17,7 @@ description: Häufig gestellte Fragen zu KI-Funktionen in Twenty.
</Accordion>
<Accordion title="Haben KI-Agenten Zugriff auf alle meine Daten?">
KI-Agenten werden über das Berechtigungssystem verwaltet. Sie können KI-Agenten unter **Einstellungen → Mitglieder → Rollen** bestimmte Rollen zuweisen und haben damit volle Kontrolle darüber, auf welche Daten sie zugreifen können und welche Aktionen sie ausführen dürfen.
KI-Agenten werden über das Berechtigungssystem verwaltet. Sie können KI-Agenten unter **Einstellungen → Rollen** bestimmte Rollen zuweisen und haben damit volle Kontrolle darüber, auf welche Daten sie zugreifen können und welche Aktionen sie ausführen dürfen.
</Accordion>
<Accordion title="Wie funktionieren KI-Credits?">
@@ -48,7 +48,7 @@ Erweitern Sie Ihre Workflows mit KI-gestützten Aktionen und autonomen Agenten.
KI-Agenten werden über das bestehende Berechtigungssystem verwaltet:
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
1. Gehen Sie zu **Einstellungen → Rollen**
2. Konfigurieren Sie, auf welche Daten jeder KI-Agent zugreifen kann
3. Legen Sie Lese-/Schreibberechtigungen pro Objekt fest
@@ -214,7 +214,7 @@ Schließen Sie nach dem Import der Daten die Konfiguration Ihres Arbeitsbereichs
### Rollen und Berechtigungen konfigurieren
* Richten Sie Rollen unter **Einstellungen → Mitglieder → Rollen** ein
* Richten Sie Rollen unter **Einstellungen → Rollen** ein
* Weisen Sie Benutzer den entsprechenden Rollen zu
### E-Mail und Kalender verbinden
@@ -127,7 +127,7 @@ Erstellen Sie nach dem Import der Daten manuell neu:
### Rollen und Berechtigungen
* Richten Sie Rollen unter **Einstellungen → Mitglieder → Rollen** ein
* Konfigurieren Sie Rollen in **Einstellungen → Rollen**
* Weisen Sie Benutzer den entsprechenden Rollen zu
### Integrationen
@@ -13,7 +13,7 @@ Das Berechtigungssystem von Twenty ermöglicht es Ihnen, den Zugriff auf drei Ha
Um eine neue Rolle zu erstellen:
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
1. Gehen Sie zu **Einstellungen → Rollen**
2. Unter **Alle Rollen** klicken Sie auf **+ Rolle erstellen**
3. Geben Sie einen Rollennamen ein
4. Im Standard-Tab **Berechtigungen** [Berechtigungen konfigurieren](#customize-permissions)
@@ -23,7 +23,7 @@ Um eine neue Rolle zu erstellen:
Um eine Rolle zu löschen:
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
1. Gehen Sie zu **Einstellungen → Rollen**
2. Klicken Sie auf die Rolle, die Sie entfernen möchten
3. Öffnen Sie den Tab **Einstellungen** und klicken Sie auf **Rolle löschen**
4. Klicken Sie im Modal auf **Bestätigen**
@@ -36,13 +36,13 @@ Wenn eine Rolle gelöscht wird, wird jedes ihr zugewiesene Mitglied des Arbeitsb
### Aktuelle Zuweisungen anzeigen
* Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
* Gehen Sie zu **Einstellungen → Rollen**
* Sehen Sie alle Rollen und wie viele Mitglieder jeweils zugewiesen sind
* Anzeigen, welche Mitglieder welche Rollen haben
### Weisen Sie einem Mitglied eine Rolle zu
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
1. Gehen Sie zu **Einstellungen → Rollen**
2. Klicken Sie auf die Rolle, die Sie zuweisen möchten
3. Öffnen Sie den Tab **Zuweisungen**
4. Klicken Sie auf **+ Mitglied zuweisen**
@@ -51,7 +51,7 @@ Wenn eine Rolle gelöscht wird, wird jedes ihr zugewiesene Mitglied des Arbeitsb
### Standardrolle festlegen
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
1. Gehen Sie zu **Einstellungen → Rollen**
2. Suchen Sie im Abschnitt **Optionen** nach **Standardrolle**
3. Wählen Sie aus, welche Rolle neue Mitglieder automatisch erhalten sollen
4. Neue Arbeitsbereichsmitglieder werden beim Beitritt dieser Rolle zugewiesen
@@ -168,7 +168,7 @@ Neben Mitgliedern des Arbeitsbereichs können Rollen auch **API-Schlüsseln** un
### Einem API-Schlüssel eine Rolle zuweisen
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
1. Gehen Sie zu **Einstellungen → Rollen**
2. Klicken Sie auf die Rolle, die Sie zuweisen möchten
3. Öffnen Sie den Tab **Zuweisungen**
4. Unter **API-Schlüssel** auf **+ API-Schlüssel zuweisen** klicken
@@ -183,7 +183,7 @@ API-Schlüssel ohne zugewiesene Rolle verwenden Standardberechtigungen. Weisen S
### Einem KI-Agenten eine Rolle zuweisen
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
1. Gehen Sie zu **Einstellungen → Rollen**
2. Klicken Sie auf die Rolle, die Sie zuweisen möchten
3. Öffnen Sie den Tab **Zuweisungen**
4. Unter **KI-Agenten** auf **+ KI-Agenten zuweisen** klicken
@@ -19,7 +19,7 @@ Jedes Mitglied des Arbeitsbereichs, dem diese Rolle zugewiesen ist, wird automat
</Accordion>
<Accordion title="Wie lege ich eine Standardrolle für neue Mitglieder fest?">
Gehen Sie zu **Einstellungen → Mitglieder → Rollen**, suchen Sie die Option **Standardrolle** und wählen Sie aus, welche Rolle neue Mitglieder beim Beitritt automatisch erhalten sollen.
Gehen Sie zu **Einstellungen → Rollen**, suchen Sie die Option **Standardrolle** und wählen Sie aus, welche Rolle neue Mitglieder beim Beitritt automatisch erhalten sollen.
</Accordion>
<Accordion title="Kann ich einem Benutzer mehrere Rollen zuweisen?">
@@ -64,7 +64,7 @@ Berechtigungen auf Zeilenebene werden bis Q1 2026 im Tarif **Organization** verf
</Accordion>
<Accordion title="Wie mache ich ein Feld für bestimmte Benutzer schreibgeschützt?">
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
1. Gehen Sie zu **Einstellungen → Rollen**
2. Wählen Sie die Rolle aus
3. Navigieren Sie zu dem Objekt, das das Feld enthält
4. Setzen Sie die Feldberechtigung auf **Feld anzeigen** (ohne Feld bearbeiten)
@@ -3,12 +3,10 @@ title: Domäneneinstellungen
description: Konfigurieren Sie die Arbeitsbereichsdomäne, genehmigte Zugriffsdomänen und öffentliche Domänen.
---
Domain-Einstellungen befinden sich an drei Stellen, abhängig davon, was Sie konfigurieren.
Konfigurieren Sie die Domäneneinstellungen unter **Einstellungen → Domänen**.
## Arbeitsbereichsdomäne
Konfigurieren Sie dies unter **Einstellungen → Allgemein → Arbeitsbereichs-Domain**.
Bearbeiten Sie den Namen Ihrer Subdomäne oder legen Sie eine benutzerdefinierte Domäne für Ihren Arbeitsbereich fest.
### Domäne anpassen
@@ -21,8 +19,6 @@ Für benutzerdefinierte Domänen müssen Sie die DNS-Einstellungen bei Ihrem Dom
## Genehmigte Domänen
Konfigurieren Sie dies unter **Einstellungen → Mitglieder → Einladen**.
Jede Person mit einer E-Mail-Adresse in diesen Domänen darf sich automatisch für diesen Arbeitsbereich registrieren.
### Genehmigte Zugriffsdomäne hinzufügen
@@ -39,16 +35,13 @@ Dies ist nützlich, um Ihrem gesamten Team die Selbstregistrierung zu ermöglich
## Öffentliche Domains
Konfigurieren Sie dies unter **Einstellungen → Apps → Entwickler**.
Stellen Sie eine vollständige und sichere Hosting-Umgebung auf diesen Domains bereit. Eine öffentliche Domain kann an eine bestimmte App gebunden werden wenn sie gebunden ist, sind unter dieser Domain nur die HTTP-gerouteten Logikfunktionen dieser App erreichbar. Lassen Sie die Bindung leer, um alle HTTP-Routen des Arbeitsbereichs bereitzustellen.
Stellen Sie eine vollständige und sichere Hosting-Umgebung auf diesen Domains bereit.
### Öffentliche Domäne hinzufügen
1. Klicken Sie auf **Öffentliche Domäne hinzufügen**
2. Geben Sie die Domäne ein, die Sie verwenden möchten
3. Optional an eine App binden
4. Konfigurieren Sie die DNS-Einstellungen gemäß Anleitung
5. Überprüfen Sie die Domäne
3. Konfigurieren Sie die DNS-Einstellungen gemäß Anleitung
4. Überprüfen Sie die Domäne
SSL-Zertifikate werden für öffentliche Domänen automatisch bereitgestellt.
@@ -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 → Domänen**
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
@@ -137,7 +137,7 @@ Ja, Sie können mehrere E-Mail-Konten verbinden. Gehen Sie zu **Einstellungen
<AccordionGroup>
<Accordion title="Kann ich meine Arbeitsbereichsdomäne anpassen?">
Ja! Gehen Sie zu **Einstellungen → Allgemein → Workspace-Domäne** und klicken Sie auf **Domäne anpassen**. Sie haben zwei Optionen:
Ja! Gehen Sie zu **Einstellungen → Domänen** und klicken Sie auf **Domäne anpassen**. Sie haben zwei Optionen:
* **Subdomain**: Verwenden Sie eine Twenty-Subdomain wie `yourcompany.twenty.com`
* **Benutzerdefinierte Domäne**: Verwenden Sie Ihre eigene Domäne wie `crm.yourcompany.com` (erfordert DNS-Konfiguration)
@@ -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 → Domänen** und fügen Sie Ihre Firmendomäne hinzu (z. B. `yourcompany.com`).
</Accordion>
</AccordionGroup>
@@ -44,7 +44,7 @@ Fügen Sie Ihrem Arbeitsbereich Teammitglieder hinzu:
4. Weisen Sie geeignete Rollen zu
<Note>
Bevor Sie Ihr Team einladen, überprüfen Sie die Standardrolle unter **Einstellungen → Mitglieder → Rollen**. Neuen Mitgliedern wird diese Rolle beim Beitritt automatisch zugewiesen.
Bevor Sie Ihr Team einladen, überprüfen Sie die Standardrolle unter **Einstellungen → Rollen**. Neuen Mitgliedern wird diese Rolle beim Beitritt automatisch zugewiesen.
</Note>
## Checkliste für Arbeitsbereichseinstellungen
@@ -38,7 +38,7 @@ Sie benötigen zwei benutzerdefinierte Felder am Objekt „Opportunities“.
Wenn Benutzer diese berechneten Felder nicht manuell bearbeiten sollen:
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
1. Gehen Sie zu **Einstellungen → Rollen**
2. Wählen Sie die zu konfigurierende Rolle aus
3. Suchen Sie das Objekt „Opportunities“
4. Setzen Sie die Felder **Probability** und **Expected Amount** auf schreibgeschützt
@@ -61,7 +61,7 @@ Sie benötigen keine "Days in"-Felder für Closed Won und Closed Lost, da dies f
Wenn Benutzer diese berechneten Felder nicht manuell bearbeiten sollen:
1. Gehen Sie zu **Einstellungen → Mitglieder → Rollen**
1. Gehen Sie zu **Einstellungen → Rollen**
2. Rolle zum Konfigurieren auswählen
3. Suchen Sie das Objekt Opportunities
4. Setzen Sie die Felder "Last Entered" und "Days in" auf schreibgeschützt

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