mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-07 16:19:09 +00:00
Compare commits
5 commits
2ea1097e48
...
3ebfbe4780
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ebfbe4780 | ||
|
|
4f5a5040e3 | ||
|
|
faae9a685b | ||
|
|
52cc696431 | ||
|
|
f79f49c755 |
7 changed files with 114 additions and 50 deletions
|
|
@ -110,7 +110,7 @@ Each invocation creates a deterministic thread ID, so follow-up messages on the
|
|||
|
||||
**Trigger tags (Sea Haven fork):** a mention is a case-insensitive substring match on the comment body — `@openswe`, `@open-swe`, `@openswe-dev`, or `@seahaven-openswe` (the deployed App slug). GitHub won't linkify `@seahaven-openswe` (App `[bot]` accounts aren't user-mentionable), but the text still fires a run.
|
||||
|
||||
**Engineering conventions & attribution (Sea Haven fork):** the main agent's system prompt is tuned to the Sea Haven engineering handbook — branch names are `feature|bug|hotfix/<kebab-desc>` (optional resolvable `<KEY>-` prefix), PR titles are imperative with no conventional-commit `type:` prefix, PR bodies use `## Summary / Validation / Tests / Notes`, and commit messages follow the handbook format (≤50-char imperative subject, *why* over *what*). **No agent/AI attribution is added to any artifact** — no `Co-authored-by` bot trailer, no `Made by [Open SWE]` footer, no "generated by an agent" notes. Commits are currently authored as the **triggering user** (the upstream behavior, which keeps Vercel preview deploys resolvable); flipping authorship to the bot account is tracked separately in issue #11 pending the Vercel-resolvability decision.
|
||||
**Engineering conventions & attribution (Sea Haven fork):** the main agent's system prompt is tuned to the Sea Haven engineering handbook — branch names are `feature|bug|hotfix/<kebab-desc>` (optional resolvable `<KEY>-` prefix), PR bodies use `## Summary / Validation / Tests / Notes`, and commit messages follow the handbook format (≤50-char imperative subject, *why* over *what*). The **PR title rule is repo-aware**: when the target repo enforces a conventional-commit title (an `amannn/action-semantic-pull-request` workflow, a `commitlint` config, or a documented requirement in `AGENTS.md` / `CONTRIBUTING.md`), the agent emits a conforming `type(scope): …` title that reads the action's allowed types/scopes — this lets it pass gates like this repo's own `PR Title Lint` and upstream `langchain-ai/open-swe` without manual retitling; otherwise it falls back to the Sea Haven imperative style with no `type:` prefix. PRs that resolve a GitHub issue **auto-link it** in the body (`Closes #<n>` for full fixes, `Refs #<n>`/`Part of #<n>` for partial work, `Closes owner/repo#<n>` cross-repo); because the Sea Haven flow targets `dev` rather than the default branch, the issue closes when `dev` is promoted, not at dev-merge. **No agent/AI attribution is added to any artifact** — no `Co-authored-by` bot trailer, no `Made by [Open SWE]` footer, no "generated by an agent" notes. Commits are currently authored as the **triggering user** (the upstream behavior, which keeps Vercel preview deploys resolvable); flipping authorship to the bot account is tracked separately in issue #11 pending the Vercel-resolvability decision.
|
||||
|
||||
### 7. Validation — Prompt-Driven
|
||||
|
||||
|
|
|
|||
|
|
@ -394,7 +394,16 @@ When you have completed your implementation, follow these steps in order:
|
|||
- **Open a new PR** with the `open_pull_request` tool (pass `owner`, `repo`, `head` = your branch, `base`, `title`, `body`). This attributes the PR to the triggering user. Push the branch BEFORE calling it.
|
||||
- **Update an existing PR** (edit the body, mark ready for review, etc.) with `GH_TOKEN=dummy gh pr edit`. If a PR already exists for the branch (including one the user pasted in), do NOT open a duplicate — `open_pull_request` returns the existing PR's URL, so switch to `gh pr edit`. For follow-up changes, add a new commit on top of the existing branch history.
|
||||
|
||||
**PR Title** (under 70 characters): imperative mood, capitalized, describing the change — not the ticket. Do NOT use a conventional-commit `type:` prefix (no `feat:`/`fix:`/`chore:`). When a ticket key is resolvable from the run context, prefix it in square brackets; otherwise omit it entirely:
|
||||
**PR Title** (under 70 characters): the title rule is **repo-aware** — first detect whether the target repo enforces a conventional-commit PR title, then pick the matching style. The repo is already cloned, so this check is cheap.
|
||||
|
||||
*Detect a conventional-commit title gate* — the repo enforces one if ANY of these hold:
|
||||
- a workflow under `.github/workflows/` references `amannn/action-semantic-pull-request` (or any `semantic-pull-request` action);
|
||||
- a `commitlint` config wired to PR titles (`commitlint.config.*`, `.commitlintrc*`, or a `commitlint` key in `package.json`);
|
||||
- `AGENTS.md` / `CONTRIBUTING.md` states a conventional-commit title requirement.
|
||||
|
||||
*If a gate is enforced* → emit a conventional-commit title `type(scope): description` and conform to the action's configuration. This **overrides** the Sea Haven no-`type:`-prefix default. Open the workflow (e.g. `.github/workflows/pr_lint.yml`) and read the allowed `types`/`scopes` so you stay inside them; if `requireScope` is false, a scope is optional. Map the work to a type: new functionality → `feat`, defect fix → `fix`, infra/CI → `ci`/`build`/`chore`, docs → `docs`, tests → `test`, refactor → `refactor`, perf → `perf`. Examples: `feat: add retry logic for transient upstream failures` or `fix(deps): pin langgraph-cli`. Do NOT rely on an escape-hatch label (e.g. `ignore-lint-pr-title`) to dodge the check — conform to the title instead. (Note: this repo's own `PR Title Lint` and upstream `langchain-ai/open-swe` both enforce this — emit a conforming `type:` title for them.)
|
||||
|
||||
*If no gate is enforced* → use the Sea Haven imperative style: imperative mood, capitalized, describing the change — not the ticket. Do NOT use a conventional-commit `type:` prefix (no `feat:`/`fix:`/`chore:`). When a ticket key is resolvable from the run context, prefix it in square brackets; otherwise omit it entirely:
|
||||
```
|
||||
[<KEY>] Add retry logic for transient upstream failures
|
||||
```
|
||||
|
|
@ -415,6 +424,13 @@ When you have completed your implementation, follow these steps in order:
|
|||
<Anything reviewers should know — migration steps, deploy order, follow-ups, breaking changes. Omit this section if empty.>
|
||||
```
|
||||
|
||||
**Link the GitHub issue the PR resolves** — when the run originates from (or fully fixes) a GitHub issue, add a closing keyword to the PR body so merging auto-closes the issue. The issue number is usually in-context: issue-triggered runs receive a `## GitHub Issue: #<n>` line; for Slack/Linear-triggered runs that fix a GitHub issue, pick `#<n>` up from the task text.
|
||||
- When the PR **fully resolves** a GitHub issue in the **same repo**, add a dedicated trailing line in `## Summary` (or its own line at the end of the body): `Closes #<n>`. GitHub recognizes `Closes`/`Fixes`/`Resolves #<n>` anywhere in the body.
|
||||
- When the PR only **partially** addresses an issue (more work remains), use a **non-closing** reference so the issue stays open: `Refs #<n>` or `Part of #<n>`.
|
||||
- **Cross-repo**: if the issue lives in a different repo, use the fully-qualified form: `Closes owner/repo#<n>` (or `Refs owner/repo#<n>` for partial).
|
||||
- This is the GitHub-issue analog of the Linear `Refs: <KEY>` commit trailer — placed in the PR body where GitHub's auto-close looks.
|
||||
- **Default-branch caveat (don't mistake this for a bug):** GitHub only auto-closes the linked issue when the PR merges into the repo's **default branch**. In the Sea Haven flow the agent targets `dev`, not the default branch, so `Closes #<n>` will **not** close the issue at dev-merge time — it closes when `dev` is promoted to the default branch. The link still renders, and the issue closes on promotion; this is the correct, expected outcome. On repos where the agent targets the default branch directly, it closes on merge as usual.
|
||||
|
||||
You don't need to add links back to the originating Slack thread or Linear ticket — for private repos, `open_pull_request` appends a `## References` section automatically.
|
||||
|
||||
When the target repo is public, don't reference private repos or private PR/issue numbers in the description.
|
||||
|
|
@ -426,6 +442,8 @@ When you have completed your implementation, follow these steps in order:
|
|||
- No generic subjects ("Fix stuff", "Update code", "WIP", "Address review comments") and no self-referential phrasing ("This commit…", "This PR…", "I refactored…").
|
||||
- When a ticket key is resolvable, add a `Refs: <KEY>` trailer (combine with `#<issue>` when both apply); otherwise omit the trailer.
|
||||
|
||||
This per-commit convention is independent of the repo-aware **PR title** rule above. On a repo that requires conventional PR titles **and** squash-merges, the squash commit subject becomes the PR title (e.g. `feat: …`) and so diverges from this imperative-no-prefix commit style — that's an acceptable tradeoff (the target repo's title lint wins), not a contradiction. Your own per-commit subjects still follow the Sea Haven format here.
|
||||
|
||||
**IMPORTANT: For code-change tasks, never ask the user for permission or confirmation before pushing commits or opening/updating a draft PR. Do not say "if you want, I can proceed" or "shall I open the PR?". When implementation is done and checks pass, push autonomously, and open/update a draft PR autonomously when requested, necessary, or required by the Always Create PRs dashboard setting.**
|
||||
|
||||
**IMPORTANT: If you made commits directly via `git commit` or `git revert` in the sandbox, you MUST push those commits to GitHub. Never report the work as done without pushing.**
|
||||
|
|
|
|||
|
|
@ -158,34 +158,64 @@ while IFS=$'\t' read -r nb vb; do
|
|||
done < <(jq -r '.Parameters[] | (.Name|@base64) + "\t" + (.Value|@base64)' <<<"$ssm_json")
|
||||
log "loaded ${ssm_count} config param(s) from SSM"
|
||||
|
||||
# 2) Secrets Manager (sensitive values). batch-get-secret-value filters by name
|
||||
# prefix; one secret per env var, SecretString = the value. The AWS CLI does NOT
|
||||
# auto-paginate this operation (unlike list/get-parameters-by-path), and a single
|
||||
# response caps well under the full set (~10 items/page) — so we MUST follow
|
||||
# NextToken ourselves or secrets on later pages are silently dropped (they then
|
||||
# surface as FAIL-FAST "missing required var"). `--no-cli-pager` only disables the
|
||||
# OUTPUT pager, not API pagination. Each page's records are base64(name<TAB>value).
|
||||
# 2) Secrets Manager (sensitive values). We request secrets by EXPLICIT id
|
||||
# (`batch-get-secret-value --secret-id-list ...`) rather than a name-prefix
|
||||
# `--filters` collection scan (OSWE-IAC-SECRETS-LIST-01). Two wins:
|
||||
# (a) Least privilege — an explicit id list lets the instance role scope
|
||||
# BatchGetSecretValue to the per-secret ARN prefix and DROP the account-wide
|
||||
# `secretsmanager:ListSecrets` grant that a filtered scan unavoidably forces
|
||||
# (ListSecrets has no resource-level scoping). A filtered batch call also only
|
||||
# authorizes against `*`; an id-list call authorizes per-secret ARN.
|
||||
# (b) Deterministic set — the value set is the fixed SECRET_VARS shells created by
|
||||
# the CDK ConfigStore, so we no longer depend on a list scan returning every
|
||||
# page. Value-less shells and ids with no current value come back in the
|
||||
# response `.Errors[]` (ResourceNotFound), never in `.SecretValues[]`, so a
|
||||
# genuinely-missing REQUIRED secret is still caught by the FAIL-FAST check
|
||||
# below — an absent optional secret is simply skipped.
|
||||
# `--secret-id-list` is capped at 20 ids per call, so we chunk it. `--no-cli-pager`
|
||||
# disables only the OUTPUT pager. Each record is base64(name<TAB>value).
|
||||
#
|
||||
# SOURCE OF TRUTH for this list: infra/lib/constructs/config-store.ts `SECRET_VARS`.
|
||||
# Keep the two in lockstep — a new secret shell created there must be added here or it
|
||||
# will never be fetched into the .env.
|
||||
SECRET_VARS=(
|
||||
ANTHROPIC_API_KEY CORRIDOR_API_TOKEN CORRIDOR_MCP_TOKEN CORRIDOR_TOKEN
|
||||
DASHBOARD_JWT_SECRET DAYTONA_API_KEY EXA_API_KEY FIREWORKS_API_KEY
|
||||
GITHUB_APP_CLIENT_SECRET GITHUB_APP_PRIVATE_KEY GITHUB_PAT GITHUB_WEBHOOK_SECRET
|
||||
GOOGLE_API_KEY GROQ_API_KEY JUDGE_ANTHROPIC_API_KEY LANGSMITH_API_KEY
|
||||
LANGSMITH_API_KEY_PROD LANGCHAIN_API_KEY LINEAR_API_KEY LINEAR_WEBHOOK_SECRET
|
||||
OPENAI_API_KEY RUNLOOP_API_KEY SLACK_BOT_TOKEN SLACK_CLIENT_SECRET
|
||||
SLACK_SIGNING_SECRET TOKEN_ENCRYPTION_KEY USER_ID_API_KEY_MAP X_SERVICE_AUTH_JWT_SECRET
|
||||
)
|
||||
|
||||
batch_get_secrets_tsv() {
|
||||
local token="" page
|
||||
while :; do
|
||||
if [ -n "$token" ]; then
|
||||
page="$(aws secretsmanager batch-get-secret-value \
|
||||
--filters "Key=name,Values=${SECRET_PREFIX}" \
|
||||
--region "$REGION" --no-cli-pager --output json --next-token "$token")"
|
||||
else
|
||||
page="$(aws secretsmanager batch-get-secret-value \
|
||||
--filters "Key=name,Values=${SECRET_PREFIX}" \
|
||||
--region "$REGION" --no-cli-pager --output json)"
|
||||
fi
|
||||
local -a ids=()
|
||||
local v
|
||||
for v in "${SECRET_VARS[@]}"; do ids+=("${SECRET_PREFIX}${v}"); done
|
||||
|
||||
local i page
|
||||
local -a chunk
|
||||
for ((i = 0; i < ${#ids[@]}; i += 20)); do
|
||||
chunk=("${ids[@]:i:20}")
|
||||
# Capture the response into a variable FIRST so a non-zero `aws` exit (throttle,
|
||||
# AccessDenied, KMS DecryptionFailure) aborts under set -e instead of being
|
||||
# silently swallowed — then we'd FAIL-FAST below as "missing secret" with a wrong
|
||||
# root cause. (Value-less / absent shells come back in .Errors[], not .SecretValues[].)
|
||||
page="$(aws secretsmanager batch-get-secret-value \
|
||||
--secret-id-list "${chunk[@]}" \
|
||||
--region "$REGION" --no-cli-pager --output json)"
|
||||
printf '%s' "$page" \
|
||||
| jq -r '.SecretValues[] | select(.SecretString != null) | (.Name|@base64) + "\t" + (.SecretString|@base64)'
|
||||
token="$(printf '%s' "$page" | jq -r '.NextToken // empty')"
|
||||
[ -n "$token" ] || break
|
||||
done
|
||||
}
|
||||
|
||||
log "reading secrets under ${SECRET_PREFIX} ..."
|
||||
secret_count=0
|
||||
# Capture into a variable (NOT `done < <(...)` process substitution) so a non-zero
|
||||
# exit from batch_get_secrets_tsv propagates under set -e — process substitution hides
|
||||
# the producer's exit status from the parent shell, which would let a failed AWS call
|
||||
# fall through to a misleading "missing required var" FAIL-FAST. Mirrors the SSM read.
|
||||
secrets_tsv="$(batch_get_secrets_tsv)"
|
||||
while IFS=$'\t' read -r nb vb; do
|
||||
[ -n "$nb" ] || continue
|
||||
name="$(printf '%s' "$nb" | b64d)"
|
||||
|
|
@ -198,7 +228,7 @@ while IFS=$'\t' read -r nb vb; do
|
|||
[ -n "$key" ] || continue
|
||||
accept_var "$key" "$value" "Secrets"
|
||||
secret_count=$((secret_count + 1))
|
||||
done < <(batch_get_secrets_tsv)
|
||||
done <<<"$secrets_tsv"
|
||||
log "loaded ${secret_count} secret(s) from Secrets Manager"
|
||||
|
||||
# --- Sea Haven DEFAULT_REPO_OWNER guard --------------------------------------
|
||||
|
|
|
|||
|
|
@ -64,35 +64,28 @@ export class InstanceRole extends Construct {
|
|||
// random 6-char suffix, hence the trailing `*`. This is the statement that
|
||||
// actually gates which secret VALUES the box can read: prefix-scoped, so the
|
||||
// dev box can never read prod secret values (and vice versa). GetSecretValue is
|
||||
// checked per-secret even when the value is returned via the batch call below.
|
||||
// checked per-secret even when the value is returned via BatchGetSecretValue.
|
||||
//
|
||||
// BatchGetSecretValue is included here (prefix-scoped, NOT `*`) because
|
||||
// fetch-config.sh now fetches by EXPLICIT `--secret-id-list` rather than a name
|
||||
// `--filters` scan (OSWE-IAC-SECRETS-LIST-01). An id-list batch call authorizes
|
||||
// against each referenced secret's ARN, so the prefix ARN satisfies it — and we
|
||||
// no longer need the account-wide `secretsmanager:ListSecrets` grant that a
|
||||
// filtered collection scan unavoidably forced (ListSecrets has no resource-level
|
||||
// scoping). Net effect: the box can read open-swe-<env>/* secret values and
|
||||
// nothing else — it can no longer enumerate secret names account-wide.
|
||||
this.role.addToPolicy(
|
||||
new iam.PolicyStatement({
|
||||
sid: "ReadSecretValues",
|
||||
actions: ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"],
|
||||
actions: [
|
||||
"secretsmanager:GetSecretValue",
|
||||
"secretsmanager:BatchGetSecretValue",
|
||||
"secretsmanager:DescribeSecret",
|
||||
],
|
||||
resources: [`arn:aws:secretsmanager:${REGION}:${ACCOUNT}:secret:${p}/*`],
|
||||
}),
|
||||
);
|
||||
|
||||
// OPERATION-level grants for fetch-config.sh's prefix-FILTERED batch read
|
||||
// (`batch-get-secret-value --filters Key=name,Values=open-swe-<env>/`). Both of
|
||||
// these are collection operations that AWS authorizes against `*`, NOT a
|
||||
// per-secret ARN: BatchGetSecretValue with a filter is a collection call (a
|
||||
// prefix-scoped ARN does NOT satisfy it — it AccessDenies), and ListSecrets is a
|
||||
// list action with no resource-level scoping at all. Neither returns or widens
|
||||
// VALUE access: a secret's value is still only returned when the prefix-scoped
|
||||
// GetSecretValue above allows it, so cross-env VALUE isolation is preserved. The
|
||||
// residual is metadata-only (the box can ENUMERATE secret names account-wide).
|
||||
// Future hardening to drop both `*` grants: switch fetch-config to an explicit
|
||||
// `--secret-id-list` (no filter), which lets BatchGetSecretValue be prefix-scoped
|
||||
// and needs no ListSecrets. Tracked as OSWE-IAC-SECRETS-LIST-01.
|
||||
this.role.addToPolicy(
|
||||
new iam.PolicyStatement({
|
||||
sid: "SecretsBatchListOps",
|
||||
actions: ["secretsmanager:BatchGetSecretValue", "secretsmanager:ListSecrets"],
|
||||
resources: ["*"],
|
||||
}),
|
||||
);
|
||||
|
||||
// NOTE (T11): SSM SecureString + Secrets Manager here are assumed to use the
|
||||
// AWS-managed keys (alias/aws/ssm, alias/aws/secretsmanager) for which the
|
||||
// service grants Decrypt implicitly — so NO kms:Decrypt is granted. If T11
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ dependencies = [
|
|||
"fastapi>=0.138.1",
|
||||
"uvicorn>=0.49.0",
|
||||
"httpx>=0.28.1",
|
||||
"PyJWT>=2.12.1",
|
||||
"PyJWT>=2.13.0",
|
||||
"cryptography>=48.0.1",
|
||||
"langgraph-sdk>=0.4.2",
|
||||
"langchain>=1.3.9",
|
||||
|
|
|
|||
|
|
@ -195,6 +195,29 @@ def test_construct_system_prompt_uses_sea_haven_conventions() -> None:
|
|||
assert "## Release Note" not in prompt
|
||||
|
||||
|
||||
def test_construct_system_prompt_links_resolved_github_issue() -> None:
|
||||
prompt = construct_system_prompt(working_dir="/workspace")
|
||||
|
||||
# Part A: PRs that resolve a GitHub issue auto-link it for auto-close.
|
||||
assert "Closes #<n>" in prompt
|
||||
assert "Refs #<n>" in prompt or "Part of #<n>" in prompt
|
||||
assert "Closes owner/repo#<n>" in prompt
|
||||
# The default-branch auto-close caveat must be stated so it isn't read as a bug.
|
||||
assert "default branch" in prompt
|
||||
assert "promoted" in prompt or "promotion" in prompt
|
||||
|
||||
|
||||
def test_construct_system_prompt_pr_title_rule_is_repo_aware() -> None:
|
||||
prompt = construct_system_prompt(working_dir="/workspace")
|
||||
|
||||
# Part B: detect a conventional-commit title gate and conform to it...
|
||||
assert "amannn/action-semantic-pull-request" in prompt
|
||||
assert "repo-aware" in prompt
|
||||
assert "type(scope): description" in prompt or "type(scope): …" in prompt
|
||||
# ...without dropping the no-prefix default for repos that don't enforce one.
|
||||
assert "Do NOT use a conventional-commit `type:` prefix" in prompt
|
||||
|
||||
|
||||
def test_construct_system_prompt_shell_escapes_user_name() -> None:
|
||||
import shlex
|
||||
|
||||
|
|
|
|||
8
uv.lock
generated
8
uv.lock
generated
|
|
@ -2049,7 +2049,7 @@ requires-dist = [
|
|||
{ name = "langsmith", specifier = "==0.9.3" },
|
||||
{ name = "markdownify", specifier = ">=1.2.2" },
|
||||
{ name = "pygments", marker = "extra == 'dev'", specifier = ">=2.20.0" },
|
||||
{ name = "pyjwt", specifier = ">=2.12.1" },
|
||||
{ name = "pyjwt", specifier = ">=2.13.0" },
|
||||
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=9.1.1" },
|
||||
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=1.4.0" },
|
||||
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15.20" },
|
||||
|
|
@ -2622,11 +2622,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "pyjwt"
|
||||
version = "2.12.1"
|
||||
version = "2.13.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue