Compare commits

..

24 Commits

Author SHA1 Message Date
opencode-agent[bot]
eb176161be chore: update nix node_modules hashes 2026-02-19 06:40:48 +00:00
opencode-agent[bot]
8f0f4f42e0 Apply PR #14215: Remove use of Bun.file 2026-02-19 06:32:04 +00:00
opencode-agent[bot]
496712f28b Apply PR #12633: feat(tui): add auto-accept mode for permission requests 2026-02-19 06:32:03 +00:00
opencode-agent[bot]
4715b09851 Apply PR #12022: feat: update tui model dialog to utilize model family to reduce noise in list 2026-02-19 06:32:03 +00:00
opencode-agent[bot]
e59e472f0f Apply PR #11811: feat: make plan mode the default 2026-02-19 06:32:03 +00:00
Aiden Cline
308e500832 tweak: bake in the aws and google auth pkgs (#14241) 2026-02-19 00:31:33 -06:00
opencode-agent[bot]
4b878f6aeb chore: generate 2026-02-19 05:19:18 +00:00
Ariane Emory
1893473148 fix: token substitution in OPENCODE_CONFIG_CONTENT (alternate take) (#14047) 2026-02-18 23:18:24 -06:00
Dax Raad
cab1f7de3c core: introduce Filesystem abstraction layer for consistent file operations across the codebase
Replaces direct Bun.file() and Bun.write() calls with centralized Filesystem utility

Benefits: consistent error handling, improved testability, and platform independence

Adds readArrayBuffer(), mimeType(), readJson(), writeJson(), stat(), exists() methods

Updates all tests to use Filesystem patterns for reliable mocking
2026-02-18 20:44:17 -05:00
Dax Raad
d50d46de4b Unrevert: re-apply all Bun.file() to Filesystem module refactors 2026-02-18 20:44:17 -05:00
Dax
e31f00ad22 Merge branch 'dev' into feat/auto-accept-permissions 2026-02-16 21:50:34 -05:00
LukeParkerDev
a90e8de050 add missing return 2026-02-11 13:24:17 +10:00
Aiden Cline
eabf770053 Merge branch 'dev' into utilize-family-in-dialog 2026-02-10 14:43:15 -06:00
Dax
86d7bdc542 Merge branch 'dev' into feat/auto-accept-permissions 2026-02-09 10:55:01 -05:00
Dax
d3ab78bba0 Merge branch 'dev' into feat/auto-accept-permissions 2026-02-09 10:04:40 -05:00
Dax Raad
a531f3f36d core: run command build agent now auto-accepts file edits to reduce workflow interruptions while still requiring confirmation for bash commands 2026-02-07 20:00:09 -05:00
Dax Raad
bb3382311d tui: standardize autoedit indicator text styling to match other status labels 2026-02-07 19:57:45 -05:00
Dax Raad
ad545d0cc9 tui: allow auto-accepting only edit permissions instead of all permissions 2026-02-07 19:52:53 -05:00
Dax Raad
ac244b1458 tui: add searchable 'toggle' keywords to command palette and show current state in toggle titles 2026-02-07 17:03:34 -05:00
Dax Raad
f202536b65 tui: show enable/disable state in permission toggle and make it searchable by 'toggle permissions' 2026-02-07 16:57:48 -05:00
Dax Raad
405cc3f610 tui: streamline permission toggle command naming and add keyboard shortcut support
Rename 'Toggle autoaccept permissions' to 'Toggle permissions' for clarity
and move the command to the Agent category for better discoverability.
Add permission_auto_accept_toggle keybind to enable keyboard shortcut
toggling of auto-accept mode for permission requests.
2026-02-07 16:51:55 -05:00
Dax Raad
878c1b8c2d feat(tui): add auto-accept mode for permission requests
Add a toggleable auto-accept mode that automatically accepts all incoming
permission requests with a 'once' reply. This is useful for users who want
to streamline their workflow when they trust the agent's actions.

Changes:
- Add permission_auto_accept keybind (default: shift+tab) to config
- Remove default for agent_cycle_reverse (was shift+tab)
- Add auto-accept logic in sync.tsx to auto-reply when enabled
- Add command bar action to toggle auto-accept mode (copy: "Toggle autoaccept permissions")
- Add visual indicator showing 'auto-accept' when active
- Store auto-accept state in KV for persistence across sessions
2026-02-07 16:44:39 -05:00
Aiden Cline
bb4d978684 feat: update tui model dialog to utilize model family to reduce noise in list 2026-02-03 15:48:40 -06:00
Dax Raad
afec40e8da feat: make plan mode the default, remove experimental flag
- Remove OPENCODE_EXPERIMENTAL_PLAN_MODE flag from flag.ts
- Update prompt.ts to always use plan mode logic
- Update registry.ts to always include plan tools in CLI
- Remove flag documentation from cli.mdx
2026-02-02 10:40:40 -05:00
67 changed files with 1234 additions and 974 deletions

View File

@@ -19,68 +19,103 @@ jobs:
- uses: ./.github/actions/setup-bun
- name: Install dependencies
run: bun install
- name: Install opencode
run: curl -fsSL https://opencode.ai/install | bash
- name: Build prompt
uses: actions/github-script@v8
with:
script: |
const fs = require("fs")
const issue = context.payload.issue
const body = issue.body ?? ""
const text = [
"Check this new issue for compliance and duplicates:",
"",
`CURRENT_ISSUE_NUMBER: ${issue.number}`,
"",
`Title: ${issue.title}`,
"",
"Description:",
body,
].join("\n")
fs.writeFileSync("issue_info.txt", text)
- name: Check duplicates and compliance
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
bun script/duplicate-issue.ts -f issue_info.txt "Check the attached file for issue details and return either a comment body or No action required" > issue_comment.txt
- name: Post comment and label issue
env:
COMMENT: ${{ github.workspace }}/issue_comment.txt
uses: actions/github-script@v8
with:
script: |
const fs = require("fs")
const comment = fs.readFileSync(process.env.COMMENT, "utf8").trim()
if (comment === "No action required") {
core.info("No comment needed")
return
OPENCODE_PERMISSION: |
{
"bash": {
"*": "deny",
"gh issue*": "allow"
},
"webfetch": "deny"
}
run: |
opencode run -m opencode/claude-sonnet-4-6 "A new issue has been created:
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: `_The following comment was made by an LLM, it may be inaccurate:_\n\n${comment}`,
})
Issue number: ${{ github.event.issue.number }}
if (!comment.includes("<!-- issue-compliance -->")) return
Lookup this issue with gh issue view ${{ github.event.issue.number }}.
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
labels: ["needs:compliance"],
})
You have TWO tasks. Perform both, then post a SINGLE comment (if needed).
---
TASK 1: CONTRIBUTING GUIDELINES COMPLIANCE CHECK
Check whether the issue follows our contributing guidelines and issue templates.
This project has three issue templates that every issue MUST use one of:
1. Bug Report - requires a Description field with real content
2. Feature Request - requires a verification checkbox and description, title should start with [FEATURE]:
3. Question - requires the Question field with real content
Additionally check:
- No AI-generated walls of text (long, AI-generated descriptions are not acceptable)
- The issue has real content, not just template placeholder text left unchanged
- Bug reports should include some context about how to reproduce
- Feature requests should explain the problem or need
- We want to push for having the user provide system description & information
Do NOT be nitpicky about optional fields. Only flag real problems like: no template used, required fields empty or placeholder text only, obviously AI-generated walls of text, or completely empty/nonsensical content.
---
TASK 2: DUPLICATE CHECK
Search through existing issues (excluding #${{ github.event.issue.number }}) to find potential duplicates.
Consider:
1. Similar titles or descriptions
2. Same error messages or symptoms
3. Related functionality or components
4. Similar feature requests
Additionally, if the issue mentions keybinds, keyboard shortcuts, or key bindings, note the pinned keybinds issue #4997.
---
POSTING YOUR COMMENT:
Based on your findings, post a SINGLE comment on issue #${{ github.event.issue.number }}. Build the comment as follows:
If the issue is NOT compliant, start the comment with:
<!-- issue-compliance -->
Then explain what needs to be fixed and that they have 2 hours to edit the issue before it is automatically closed. Also add the label needs:compliance to the issue using: gh issue edit ${{ github.event.issue.number }} --add-label needs:compliance
If duplicates were found, include a section about potential duplicates with links.
If the issue mentions keybinds/keyboard shortcuts, include a note about #4997.
If the issue IS compliant AND no duplicates were found AND no keybind reference, do NOT comment at all.
Use this format for the comment:
[If not compliant:]
<!-- issue-compliance -->
This issue doesn't fully meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md).
**What needs to be fixed:**
- [specific reasons]
Please edit this issue to address the above within **2 hours**, or it will be automatically closed.
[If duplicates found, add:]
---
This issue might be a duplicate of existing issues. Please check:
- #[issue_number]: [brief description of similarity]
[If keybind-related, add:]
For keybind-related issues, please also check our pinned keybinds documentation: #4997
[End with if not compliant:]
If you believe this was flagged incorrectly, please let a maintainer know.
Remember: post at most ONE comment combining all findings. If everything is fine, post nothing."
recheck-compliance:
if: github.event.action == 'edited' && contains(github.event.issue.labels.*.name, 'needs:compliance')
@@ -96,109 +131,47 @@ jobs:
- uses: ./.github/actions/setup-bun
- name: Install dependencies
run: bun install
- name: Install opencode
run: curl -fsSL https://opencode.ai/install | bash
- name: Build recheck prompt
uses: actions/github-script@v8
with:
script: |
const fs = require("fs")
const issue = context.payload.issue
const body = issue.body ?? ""
const text = [
"Recheck this edited issue for compliance:",
"",
"MODE: recheck-compliance",
`CURRENT_ISSUE_NUMBER: ${issue.number}`,
"",
`Title: ${issue.title}`,
"",
"Description:",
body,
].join("\n")
fs.writeFileSync("issue_info.txt", text)
- name: Recheck compliance
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENCODE_PERMISSION: |
{
"bash": {
"*": "deny",
"gh issue*": "allow"
},
"webfetch": "deny"
}
run: |
bun script/duplicate-issue.ts -f issue_info.txt "Recheck compliance for this edited issue and return either No action required or a compliance comment body" > issue_comment.txt
opencode run -m opencode/claude-sonnet-4-6 "Issue #${{ github.event.issue.number }} was previously flagged as non-compliant and has been edited.
- name: Update compliance state
env:
COMMENT: ${{ github.workspace }}/issue_comment.txt
uses: actions/github-script@v8
with:
script: |
const fs = require("fs")
const marker = "<!-- issue-compliance -->"
const comment = fs.readFileSync(process.env.COMMENT, "utf8").trim()
const issue_number = context.payload.issue.number
Lookup this issue with gh issue view ${{ github.event.issue.number }}.
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number,
per_page: 100,
})
Re-check whether the issue now follows our contributing guidelines and issue templates.
const compliance = comments.filter((x) => (x.body ?? "").includes(marker))
This project has three issue templates that every issue MUST use one of:
if (comment === "No action required") {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number,
name: "needs:compliance",
})
} catch {}
1. Bug Report - requires a Description field with real content
2. Feature Request - requires a verification checkbox and description, title should start with [FEATURE]:
3. Question - requires the Question field with real content
for (const entry of compliance) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: entry.id,
})
}
Additionally check:
- No AI-generated walls of text (long, AI-generated descriptions are not acceptable)
- The issue has real content, not just template placeholder text left unchanged
- Bug reports should include some context about how to reproduce
- Feature requests should explain the problem or need
- We want to push for having the user provide system description & information
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number,
body: "Thanks for updating your issue. It now meets our contributing guidelines. :+1:",
})
return
}
Do NOT be nitpicky about optional fields. Only flag real problems like: no template used, required fields empty or placeholder text only, obviously AI-generated walls of text, or completely empty/nonsensical content.
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number,
labels: ["needs:compliance"],
})
If the issue is NOW compliant:
1. Remove the needs:compliance label: gh issue edit ${{ github.event.issue.number }} --remove-label needs:compliance
2. Find and delete the previous compliance comment (the one containing <!-- issue-compliance -->) using: gh api repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments --jq '.[] | select(.body | contains(\"<!-- issue-compliance -->\")) | .id' then delete it with: gh api -X DELETE repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments/{id}
3. Post a short comment thanking them for updating the issue.
const body = `_The following comment was made by an LLM, it may be inaccurate:_\n\n${comment}`
const existing = compliance.at(-1)
if (!existing) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number,
body,
})
return
}
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
})
If the issue is STILL not compliant:
Post a comment explaining what still needs to be fixed. Keep the needs:compliance label."

View File

@@ -6,18 +6,6 @@ on:
jobs:
check-standards:
if: |
github.event.pull_request.user.login != 'actions-user' &&
github.event.pull_request.user.login != 'opencode' &&
github.event.pull_request.user.login != 'rekram1-node' &&
github.event.pull_request.user.login != 'thdxr' &&
github.event.pull_request.user.login != 'kommander' &&
github.event.pull_request.user.login != 'jayair' &&
github.event.pull_request.user.login != 'fwang' &&
github.event.pull_request.user.login != 'adamdotdevin' &&
github.event.pull_request.user.login != 'iamdavidhill' &&
github.event.pull_request.user.login != 'R44VC0RP' &&
github.event.pull_request.user.login != 'opencode-agent[bot]'
runs-on: ubuntu-latest
permissions:
contents: read

View File

@@ -1,98 +0,0 @@
---
mode: primary
hidden: true
model: opencode/claude-haiku-4-5
color: "#E67E22"
tools:
"*": false
"github-issue-search": true
---
You are a duplicate issue detection agent. When an issue is opened, your job is to search for potentially duplicate or related open issues.
You have two jobs:
1. Check if the issue follows our issue templates/contributing requirements.
2. Check for potential duplicate issues.
Use the github-issue-search tool to find potentially related issues.
IMPORTANT: The input will contain a line `CURRENT_ISSUE_NUMBER: NNNN`. Never mark that issue as a duplicate of itself.
The input may also contain `MODE: recheck-compliance`.
- When MODE is `recheck-compliance`, ONLY run compliance checks. Do not run duplicate checks. Do not include duplicate or keybind sections.
- When MODE is missing, do the full opened-issue behavior (compliance + duplicates + keybind note).
## Compliance checks
This project has three issue templates:
1. Bug Report - needs a Description field with real content.
2. Feature Request - title should start with `[FEATURE]:` and include verification checkbox + meaningful description.
3. Question - needs a Question field with real content.
Also check:
- no AI-generated walls of text
- required sections are not placeholder-only / unchanged template text
- bug reports include some repro context
- feature requests explain the problem/need
- encourage system information where relevant
Do not be nitpicky about optional fields. Only flag real issues (missing template/required content, placeholder-only content, obviously AI-generated wall of text, empty/nonsensical issue).
## Duplicate checks
Search for duplicates by trying multiple keyword combinations from the issue title/body. Prioritize:
- similar title/description
- same error/symptoms
- same component/feature area
If the issue mentions keybinds, keyboard shortcuts, or key bindings, include a note to check pinned issue #4997.
## Output rules
If MODE is `recheck-compliance` and the issue is compliant, output exactly:
No action required
If MODE is missing and the issue is compliant AND no duplicates are found AND no keybind note is needed, output exactly:
No action required
Otherwise output exactly one markdown comment body with this structure:
- In `recheck-compliance` mode: include only the non-compliant section and ending note.
- In default mode: include sections as needed (non-compliant, duplicates, keybind).
- If non-compliant, start with:
<!-- issue-compliance -->
This issue doesn't fully meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md).
**What needs to be fixed:**
- [specific reason]
Please edit this issue to address the above within **2 hours**, or it will be automatically closed.
- If duplicates were found, add:
---
This issue might be a duplicate of existing issues. Please check:
- #1234: [brief reason]
- If keybind-related, add:
For keybind-related issues, please also check our pinned keybinds documentation: #4997
- If non-compliant, end with:
If you believe this was flagged incorrectly, please let a maintainer know.
Keep output concise. Do not wrap output in code fences.

View File

@@ -1,42 +0,0 @@
---
name: bun-file-io
description: Use this when you are working on file operations like reading, writing, scanning, or deleting files. It summarizes the preferred file APIs and patterns used in this repo. It also notes when to use filesystem helpers for directories.
---
## Use this when
- Editing file I/O or scans in `packages/opencode`
- Handling directory operations or external tools
## Bun file APIs (from Bun docs)
- `Bun.file(path)` is lazy; call `text`, `json`, `stream`, `arrayBuffer`, `bytes`, `exists` to read.
- Metadata: `file.size`, `file.type`, `file.name`.
- `Bun.write(dest, input)` writes strings, buffers, Blobs, Responses, or files.
- `Bun.file(...).delete()` deletes a file.
- `file.writer()` returns a FileSink for incremental writes.
- `Bun.Glob` + `Array.fromAsync(glob.scan({ cwd, absolute, onlyFiles, dot }))` for scans.
- Use `Bun.which` to find a binary, then `Bun.spawn` to run it.
- `Bun.readableStreamToText/Bytes/JSON` for stream output.
## When to use node:fs
- Use `node:fs/promises` for directories (`mkdir`, `readdir`, recursive operations).
## Repo patterns
- Prefer Bun APIs over Node `fs` for file access.
- Check `Bun.file(...).exists()` before reading.
- For binary/large files use `arrayBuffer()` and MIME checks via `file.type`.
- Use `Bun.Glob` + `Array.fromAsync` for scans.
- Decode tool stderr with `Bun.readableStreamToText`.
- For large writes, use `Bun.write(Bun.file(path), text)`.
NOTE: Bun.file(...).exists() will return `false` if the value is a directory.
Use Filesystem.exists(...) instead if path can be file or directory
## Quick checklist
- Use Bun APIs first.
- Use `path.join`/`path.resolve` for paths.
- Prefer promise `.catch(...)` over `try/catch` when possible.

View File

@@ -1,57 +0,0 @@
/// <reference path="../env.d.ts" />
import { tool } from "@opencode-ai/plugin"
import DESCRIPTION from "./github-issue-search.txt"
async function githubFetch(endpoint: string, options: RequestInit = {}) {
const response = await fetch(`https://api.github.com${endpoint}`, {
...options,
headers: {
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
Accept: "application/vnd.github+json",
"Content-Type": "application/json",
...options.headers,
},
})
if (!response.ok) {
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`)
}
return response.json()
}
interface Issue {
title: string
html_url: string
}
export default tool({
description: DESCRIPTION,
args: {
query: tool.schema.string().describe("Search query for issue titles and descriptions"),
limit: tool.schema.number().describe("Maximum number of results to return").default(10),
offset: tool.schema.number().describe("Number of results to skip for pagination").default(0),
},
async execute(args) {
const owner = "anomalyco"
const repo = "opencode"
const page = Math.floor(args.offset / args.limit) + 1
const searchQuery = encodeURIComponent(`${args.query} repo:${owner}/${repo} type:issue state:open`)
const result = await githubFetch(
`/search/issues?q=${searchQuery}&per_page=${args.limit}&page=${page}&sort=updated&order=desc`,
)
if (result.total_count === 0) {
return `No issues found matching "${args.query}"`
}
const issues = result.items as Issue[]
if (issues.length === 0) {
return `No other issues found matching "${args.query}"`
}
const formatted = issues.map((issue) => `${issue.title}\n${issue.html_url}`).join("\n\n")
return `Found ${result.total_count} issues (showing ${issues.length}):\n\n${formatted}`
},
})

View File

@@ -1,10 +0,0 @@
Use this tool to search GitHub issues by title and description.
This tool searches issues in the sst/opencode repository and returns LLM-friendly results including:
- Issue number and title
- Author
- State (open/closed/merged)
- Labels
- Description snippet
Use the query parameter to search for keywords that might appear in issue titles or descriptions.

254
bun.lock
View File

@@ -288,6 +288,7 @@
"@ai-sdk/togetherai": "1.0.34",
"@ai-sdk/vercel": "1.0.33",
"@ai-sdk/xai": "2.0.51",
"@aws-sdk/credential-providers": "3.993.0",
"@clack/prompts": "1.0.0-alpha.1",
"@gitlab/gitlab-ai-provider": "3.6.0",
"@gitlab/opencode-gitlab-auth": "1.3.3",
@@ -320,6 +321,7 @@
"diff": "catalog:",
"drizzle-orm": "1.0.0-beta.12-a5629fb",
"fuzzysort": "3.1.0",
"google-auth-library": "10.5.0",
"gray-matter": "4.0.3",
"hono": "catalog:",
"hono-openapi": "catalog:",
@@ -670,27 +672,35 @@
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
"@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.993.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/credential-provider-node": "^3.972.10", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.9", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-retry": "^4.4.33", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.32", "@smithy/util-defaults-mode-node": "^4.2.35", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-7Ne3Yk/bgQPVebAkv7W+RfhiwTRSbfER9BtbhOa2w/+dIr902LrJf6vrZlxiqaJbGj2ALx8M+ZK1YIHVxSwu9A=="],
"@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.933.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/credential-provider-node": "3.933.0", "@aws-sdk/middleware-bucket-endpoint": "3.930.0", "@aws-sdk/middleware-expect-continue": "3.930.0", "@aws-sdk/middleware-flexible-checksums": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-location-constraint": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-sdk-s3": "3.932.0", "@aws-sdk/middleware-ssec": "3.930.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/signature-v4-multi-region": "3.932.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/eventstream-serde-browser": "^4.2.5", "@smithy/eventstream-serde-config-resolver": "^4.3.5", "@smithy/eventstream-serde-node": "^4.2.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-blob-browser": "^4.2.6", "@smithy/hash-node": "^4.2.5", "@smithy/hash-stream-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/md5-js": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-KxwZvdxdCeWK6o8mpnb+kk7Kgb8V+8AjTwSXUWH1UAD85B0tjdo1cSfE5zoR5fWGol4Ml5RLez12a6LPhsoTqA=="],
"@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zwGLSiK48z3PzKpQiDMKP85+fpIrPMF1qQOQW9OW7BGj5AuBZIisT2O4VzIgYJeh+t47MLU7VgBQL7muc+MJDg=="],
"@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.993.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.9", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-retry": "^4.4.33", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.32", "@smithy/util-defaults-mode-node": "^4.2.35", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-VLUN+wIeNX24fg12SCbzTUBnBENlL014yMKZvRhPkcn4wHR6LKgNrjsG3fZ03Xs0XoKaGtNFi1VVrq666sGBoQ=="],
"@aws-sdk/client-sts": ["@aws-sdk/client-sts@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/credential-provider-node": "3.782.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-Q1QLY3xE2z1trgriusP/6w40mI/yJjM524bN4gs+g6YX4sZGufpa7+Dj+JjL4fz8f9BCJ3ZlI+p4WxFxH7qvdQ=="],
"@aws-sdk/core": ["@aws-sdk/core@3.932.0", "", { "dependencies": { "@aws-sdk/types": "3.930.0", "@aws-sdk/xml-builder": "3.930.0", "@smithy/core": "^3.18.2", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-AS8gypYQCbNojwgjvZGkJocC2CoEICDx9ZJ15ILsv+MlcCVLtUJSRSx3VzJOUY2EEIaGLRrPNlIqyn/9/fySvA=="],
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.932.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-ozge/c7NdHUDyHqro6+P5oHt8wfKSUBN+olttiVfBe9Mw3wBMpPa3gQ0pZnG+gwBkKskBuip2bMR16tqYvUSEA=="],
"@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.3", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.980.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-dW/DqTk90XW7hIngqntAVtJJyrkS51wcLhGz39lOMe0TlSmZl+5R/UGnAZqNbXmWuJHLzxe+MLgagxH41aTsAQ=="],
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.932.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/types": "3.930.0", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-b6N9Nnlg8JInQwzBkUq5spNaXssM3h3zLxGzpPrnw0nHSIWPJPTbZzA5Ca285fcDUFuKP+qf3qkuqlAjGOdWhg=="],
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.9", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-ZptrOwQynfupubvcngLkbdIq/aXvl/czdpEG8XJ8mN8Nb19BR0jaK0bR+tfuMU36Ez9q4xv7GGkHFqEEP2hUUQ=="],
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.933.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/credential-provider-env": "3.932.0", "@aws-sdk/credential-provider-http": "3.932.0", "@aws-sdk/credential-provider-process": "3.932.0", "@aws-sdk/credential-provider-sso": "3.933.0", "@aws-sdk/credential-provider-web-identity": "3.933.0", "@aws-sdk/nested-clients": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-HygGyKuMG5AaGXsmM0d81miWDon55xwalRHB3UmDg3QBhtunbNIoIaWUbNTKuBZXcIN6emeeEZw/YgSMqLc0YA=="],
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.11", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/types": "^3.973.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.10", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" } }, "sha512-hECWoOoH386bGr89NQc9vA/abkGf5TJrMREt+lhNcnSNmoBS04fK7vc3LrJBSQAUGGVj0Tz3f4dHB3w5veovig=="],
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.9", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/credential-provider-env": "^3.972.9", "@aws-sdk/credential-provider-http": "^3.972.11", "@aws-sdk/credential-provider-login": "^3.972.9", "@aws-sdk/credential-provider-process": "^3.972.9", "@aws-sdk/credential-provider-sso": "^3.972.9", "@aws-sdk/credential-provider-web-identity": "^3.972.9", "@aws-sdk/nested-clients": "3.993.0", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-zr1csEu9n4eDiHMTYJabX1mDGuGLgjgUnNckIivvk43DocJC9/f6DefFrnUPZXE+GHtbW50YuXb+JIxKykU74A=="],
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.9", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/nested-clients": "3.993.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-m4RIpVgZChv0vWS/HKChg1xLgZPpx8Z+ly9Fv7FwA8SOfuC6I3htcSaBz2Ch4bneRIiBUhwP4ziUo0UZgtJStQ=="],
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.933.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.932.0", "@aws-sdk/credential-provider-http": "3.932.0", "@aws-sdk/credential-provider-ini": "3.933.0", "@aws-sdk/credential-provider-process": "3.932.0", "@aws-sdk/credential-provider-sso": "3.933.0", "@aws-sdk/credential-provider-web-identity": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-L2dE0Y7iMLammQewPKNeEh1z/fdJyYEU+/QsLBD9VEh+SXcN/FIyTi21Isw8wPZN6lMB9PDVtISzBnF8HuSFrw=="],
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.932.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-BodZYKvT4p/Dkm28Ql/FhDdS1+p51bcZeMMu2TRtU8PoMDHnVDhHz27zASEKSZwmhvquxHrZHB0IGuVqjZUtSQ=="],
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.9", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-gOWl0Fe2gETj5Bk151+LYKpeGi2lBDLNu+NMNpHRlIrKHdBmVun8/AalwMK8ci4uRfG5a3/+zvZBMpuen1SZ0A=="],
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.933.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.933.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/token-providers": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-/R1DBR7xNcuZIhS2RirU+P2o8E8/fOk+iLAhbqeSTq+g09fP/F6W7ouFpS5eVE2NIfWG7YBFoVddOhvuqpn51g=="],
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.9", "", { "dependencies": { "@aws-sdk/client-sso": "3.993.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/token-providers": "3.993.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-ey7S686foGTArvFhi3ifQXmgptKYvLSGE2250BAQceMSXZddz7sUSNERGJT2S7u5KIe/kgugxrt01hntXVln6w=="],
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.933.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/nested-clients": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-c7Eccw2lhFx2/+qJn3g+uIDWRuWi2A6Sz3PVvckFUEzPsP0dPUo19hlvtarwP5GzrsXn0yEPRVhpewsIaSCGaQ=="],
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.9", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/nested-clients": "3.993.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-8LnfS76nHXoEc9aRRiMMpxZxJeDG0yusdyo3NvPhCgESmBUgpMa4luhGbClW5NoX/qRcGxxM6Z/esqANSNMTow=="],
"@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.993.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.993.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/credential-provider-cognito-identity": "^3.972.3", "@aws-sdk/credential-provider-env": "^3.972.9", "@aws-sdk/credential-provider-http": "^3.972.11", "@aws-sdk/credential-provider-ini": "^3.972.9", "@aws-sdk/credential-provider-login": "^3.972.9", "@aws-sdk/credential-provider-node": "^3.972.10", "@aws-sdk/credential-provider-process": "^3.972.9", "@aws-sdk/credential-provider-sso": "^3.972.9", "@aws-sdk/credential-provider-web-identity": "^3.972.9", "@aws-sdk/nested-clients": "3.993.0", "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-1M/nukgPSLqe9krzOKHnE8OylUaKAiokAV3xRLdeExVHcRE7WG5uzCTKWTj1imKvPjDqXq/FWhlbbdWIn7xIwA=="],
"@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.930.0", "", { "dependencies": { "@aws-sdk/types": "3.930.0", "@aws-sdk/util-arn-parser": "3.893.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-cnCLWeKPYgvV4yRYPFH6pWMdUByvu2cy2BAlfsPpvnm4RaVioztyvxmQj5PmVN5fvWs5w/2d6U7le8X9iye2sA=="],
@@ -712,13 +722,13 @@
"@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.932.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@smithy/core": "^3.18.2", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-9BGTbJyA/4PTdwQWE9hAFIJGpsYkyEW20WON3i15aDqo5oRZwZmqaVageOD57YYqG8JDJjvcwKyDdR4cc38dvg=="],
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-o1GX0+IPlFi/D8ei9y/jj3yucJWNfPnbB5appVBWevAyUdZA5KzQ2nK/hDxiu9olTZlFEFpf1m1Rn3FaGxHqsw=="],
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.993.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.9", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-retry": "^4.4.33", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.32", "@smithy/util-defaults-mode-node": "^4.2.35", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-iOq86f2H67924kQUIPOAvlmMaOAvOLoDOIb66I2YqSUpMYB6ufiuJW3RlREgskxv86S5qKzMnfy/X6CqMjK6XQ=="],
"@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.930.0", "", { "dependencies": { "@aws-sdk/types": "3.930.0", "@smithy/config-resolver": "^4.4.3", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-KL2JZqH6aYeQssu1g1KuWsReupdfOoxD6f1as2VC+rdwYFUu4LfzMsFfXnBvvQWWqQ7rZHWOw1T+o5gJmg7Dzw=="],
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.932.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.932.0", "@aws-sdk/types": "3.930.0", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-NCIRJvoRc9246RZHIusY1+n/neeG2yGhBGdKhghmrNdM+mLLN6Ii7CKFZjx3DhxtpHMpl1HWLTMhdVrGwP2upw=="],
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.933.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/nested-clients": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Qzq7zj9yXUgAAJEbbmqRhm0jmUndl8nHG0AbxFEfCfQRVZWL96Qzx0mf8lYwT9hIMrXncLwy31HOthmbXwFRwQ=="],
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.993.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/nested-clients": "3.993.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-+35g4c+8r7sB9Sjp1KPdM8qxGn6B/shBjJtEUN4e+Edw9UEQlZKIzioOGu3UAbyE0a/s450LdLZr4wbJChtmww=="],
"@aws-sdk/types": ["@aws-sdk/types@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-we/vaAgwlEFW7IeftmCLlLMw+6hFs3DzZPJw7lVHbj/5HJ0bz9gndxEsS2lQoeJ1zhiiLqAqvXxmM43s0MBg0A=="],
@@ -4252,6 +4262,48 @@
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.10", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.9", "@aws-sdk/credential-provider-http": "^3.972.11", "@aws-sdk/credential-provider-ini": "^3.972.9", "@aws-sdk/credential-provider-process": "^3.972.9", "@aws-sdk/credential-provider-sso": "^3.972.9", "@aws-sdk/credential-provider-web-identity": "^3.972.9", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-70nCESlvnzjo4LjJ8By8MYIiBogkYPSXl3WmMZfH9RZcB/Nt9qVWbFpYj6Fk1vLa4Vk8qagFVeXgxdieMxG1QA=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.11", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@smithy/core": "^3.23.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-R8CvPsPHXwzIHCAza+bllY6PrctEk4lYq/SkHJz9NLoBHCcKQrbOcsfXxO6xmipSbUNIbNIUhH0lBsJGgsRdiw=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.993.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.972.9", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-JNswdsLdQemxqaSIBL2HRhsHPUBBziAgoi5RQv6/9avmE5g5RSdt1hWr3mHJ7OxqRYf+KeB11ExWbiqfrnoeaA=="],
"@aws-sdk/client-sso/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="],
"@aws-sdk/client-sso/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA=="],
"@aws-sdk/client-sso/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA=="],
"@aws-sdk/client-sso/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q=="],
"@aws-sdk/client-sso/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.11", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@smithy/core": "^3.23.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-R8CvPsPHXwzIHCAza+bllY6PrctEk4lYq/SkHJz9NLoBHCcKQrbOcsfXxO6xmipSbUNIbNIUhH0lBsJGgsRdiw=="],
"@aws-sdk/client-sso/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow=="],
"@aws-sdk/client-sso/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="],
"@aws-sdk/client-sso/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.993.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw=="],
"@aws-sdk/client-sso/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw=="],
"@aws-sdk/client-sso/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.972.9", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-JNswdsLdQemxqaSIBL2HRhsHPUBBziAgoi5RQv6/9avmE5g5RSdt1hWr3mHJ7OxqRYf+KeB11ExWbiqfrnoeaA=="],
"@aws-sdk/client-sts/@aws-sdk/core": ["@aws-sdk/core@3.775.0", "", { "dependencies": { "@aws-sdk/types": "3.775.0", "@smithy/core": "^3.2.0", "@smithy/node-config-provider": "^4.0.2", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/signature-v4": "^5.0.2", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/util-middleware": "^4.0.2", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-8vpW4WihVfz0DX+7WnnLGm3GuQER++b0IwQG35JlQMlgqnc44M//KbJPsIHA0aJUJVwJAEShgfr5dUbY8WUzaA=="],
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.782.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.775.0", "@aws-sdk/credential-provider-http": "3.775.0", "@aws-sdk/credential-provider-ini": "3.782.0", "@aws-sdk/credential-provider-process": "3.775.0", "@aws-sdk/credential-provider-sso": "3.782.0", "@aws-sdk/credential-provider-web-identity": "3.782.0", "@aws-sdk/types": "3.775.0", "@smithy/credential-provider-imds": "^4.0.2", "@smithy/property-provider": "^4.0.2", "@smithy/shared-ini-file-loader": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-HZiAF+TCEyKjju9dgysjiPIWgt/+VerGaeEp18mvKLNfgKz1d+/82A2USEpNKTze7v3cMFASx3CvL8yYyF7mJw=="],
@@ -4274,6 +4326,80 @@
"@aws-sdk/client-sts/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.782.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/types": "3.775.0", "@smithy/node-config-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-dMFkUBgh2Bxuw8fYZQoH/u3H4afQ12VSkzEi//qFiDTwbKYq+u+RYjc8GLDM6JSK1BShMu5AVR7HD4ap1TYUnA=="],
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.980.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.5", "@aws-sdk/credential-provider-node": "^3.972.4", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.5", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.980.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.3", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.28", "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-nLgMW2drTzv+dTo3ORCcotQPcrUaTQ+xoaDTdSaUXdZO7zbbVyk7ysE5GDTnJdZWcUjHOSB8xfNQhOTTNVPhFw=="],
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="],
"@aws-sdk/credential-provider-env/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="],
"@aws-sdk/credential-provider-env/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="],
"@aws-sdk/credential-provider-http/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="],
"@aws-sdk/credential-provider-http/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="],
"@aws-sdk/credential-provider-ini/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="],
"@aws-sdk/credential-provider-ini/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="],
"@aws-sdk/credential-provider-login/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="],
"@aws-sdk/credential-provider-login/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.932.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-ozge/c7NdHUDyHqro6+P5oHt8wfKSUBN+olttiVfBe9Mw3wBMpPa3gQ0pZnG+gwBkKskBuip2bMR16tqYvUSEA=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.932.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/types": "3.930.0", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-b6N9Nnlg8JInQwzBkUq5spNaXssM3h3zLxGzpPrnw0nHSIWPJPTbZzA5Ca285fcDUFuKP+qf3qkuqlAjGOdWhg=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.933.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/credential-provider-env": "3.932.0", "@aws-sdk/credential-provider-http": "3.932.0", "@aws-sdk/credential-provider-process": "3.932.0", "@aws-sdk/credential-provider-sso": "3.933.0", "@aws-sdk/credential-provider-web-identity": "3.933.0", "@aws-sdk/nested-clients": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-HygGyKuMG5AaGXsmM0d81miWDon55xwalRHB3UmDg3QBhtunbNIoIaWUbNTKuBZXcIN6emeeEZw/YgSMqLc0YA=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.932.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-BodZYKvT4p/Dkm28Ql/FhDdS1+p51bcZeMMu2TRtU8PoMDHnVDhHz27zASEKSZwmhvquxHrZHB0IGuVqjZUtSQ=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.933.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.933.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/token-providers": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-/R1DBR7xNcuZIhS2RirU+P2o8E8/fOk+iLAhbqeSTq+g09fP/F6W7ouFpS5eVE2NIfWG7YBFoVddOhvuqpn51g=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.933.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/nested-clients": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-c7Eccw2lhFx2/+qJn3g+uIDWRuWi2A6Sz3PVvckFUEzPsP0dPUo19hlvtarwP5GzrsXn0yEPRVhpewsIaSCGaQ=="],
"@aws-sdk/credential-provider-process/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="],
"@aws-sdk/credential-provider-process/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="],
"@aws-sdk/credential-provider-sso/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="],
"@aws-sdk/credential-provider-sso/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="],
"@aws-sdk/credential-provider-web-identity/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="],
"@aws-sdk/credential-provider-web-identity/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="],
"@aws-sdk/credential-providers/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="],
"@aws-sdk/credential-providers/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.10", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.9", "@aws-sdk/credential-provider-http": "^3.972.11", "@aws-sdk/credential-provider-ini": "^3.972.9", "@aws-sdk/credential-provider-process": "^3.972.9", "@aws-sdk/credential-provider-sso": "^3.972.9", "@aws-sdk/credential-provider-web-identity": "^3.972.9", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-70nCESlvnzjo4LjJ8By8MYIiBogkYPSXl3WmMZfH9RZcB/Nt9qVWbFpYj6Fk1vLa4Vk8qagFVeXgxdieMxG1QA=="],
"@aws-sdk/credential-providers/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="],
"@aws-sdk/nested-clients/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="],
"@aws-sdk/nested-clients/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA=="],
"@aws-sdk/nested-clients/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA=="],
"@aws-sdk/nested-clients/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q=="],
"@aws-sdk/nested-clients/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.11", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@smithy/core": "^3.23.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-R8CvPsPHXwzIHCAza+bllY6PrctEk4lYq/SkHJz9NLoBHCcKQrbOcsfXxO6xmipSbUNIbNIUhH0lBsJGgsRdiw=="],
"@aws-sdk/nested-clients/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow=="],
"@aws-sdk/nested-clients/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="],
"@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.993.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw=="],
"@aws-sdk/nested-clients/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw=="],
"@aws-sdk/nested-clients/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.972.9", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-JNswdsLdQemxqaSIBL2HRhsHPUBBziAgoi5RQv6/9avmE5g5RSdt1hWr3mHJ7OxqRYf+KeB11ExWbiqfrnoeaA=="],
"@aws-sdk/token-providers/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="],
"@aws-sdk/token-providers/@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="],
"@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="],
"@azure/core-http/@azure/abort-controller": ["@azure/abort-controller@1.1.0", "", { "dependencies": { "tslib": "^2.2.0" } }, "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw=="],
@@ -4804,6 +4930,10 @@
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="],
"@aws-sdk/client-sso/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="],
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.775.0", "", { "dependencies": { "@aws-sdk/core": "3.775.0", "@aws-sdk/types": "3.775.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6ESVxwCbGm7WZ17kY1fjmxQud43vzJFoLd4bmlR+idQSWdqlzGDYdcfzpjDKTcivdtNrVYmFvcH1JBUwCRAZhw=="],
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.775.0", "", { "dependencies": { "@aws-sdk/core": "3.775.0", "@aws-sdk/types": "3.775.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/util-stream": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-PjDQeDH/J1S0yWV32wCj2k5liRo0ssXMseCBEkCsD3SqsU8o5cU82b0hMX4sAib/RkglCSZqGO0xMiN0/7ndww=="],
@@ -4816,6 +4946,54 @@
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.782.0", "", { "dependencies": { "@aws-sdk/core": "3.775.0", "@aws-sdk/nested-clients": "3.782.0", "@aws-sdk/types": "3.775.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-xCna0opVPaueEbJoclj5C6OpDNi0Gynj+4d7tnuXGgQhTHPyAz8ZyClkVqpi5qvHTgxROdUEDxWqEO5jqRHZHQ=="],
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/core": ["@aws-sdk/core@3.973.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.5", "@smithy/core": "^3.23.2", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wdQ8vrvHkKIV7yNUKXyjPWKCdYEUrZTHJ8Ojd5uJxXp9vqPCkUR1dpi1NtOLcrDgueJH7MUH5lQZxshjFPSbDA=="],
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.10", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.9", "@aws-sdk/credential-provider-http": "^3.972.11", "@aws-sdk/credential-provider-ini": "^3.972.9", "@aws-sdk/credential-provider-process": "^3.972.9", "@aws-sdk/credential-provider-sso": "^3.972.9", "@aws-sdk/credential-provider-web-identity": "^3.972.9", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-70nCESlvnzjo4LjJ8By8MYIiBogkYPSXl3WmMZfH9RZcB/Nt9qVWbFpYj6Fk1vLa4Vk8qagFVeXgxdieMxG1QA=="],
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA=="],
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA=="],
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q=="],
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.11", "", { "dependencies": { "@aws-sdk/core": "^3.973.11", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@smithy/core": "^3.23.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-R8CvPsPHXwzIHCAza+bllY6PrctEk4lYq/SkHJz9NLoBHCcKQrbOcsfXxO6xmipSbUNIbNIUhH0lBsJGgsRdiw=="],
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow=="],
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.980.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw=="],
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw=="],
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.972.9", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-JNswdsLdQemxqaSIBL2HRhsHPUBBziAgoi5RQv6/9avmE5g5RSdt1hWr3mHJ7OxqRYf+KeB11ExWbiqfrnoeaA=="],
"@aws-sdk/credential-provider-env/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="],
"@aws-sdk/credential-provider-http/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="],
"@aws-sdk/credential-provider-ini/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="],
"@aws-sdk/credential-provider-login/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-o1GX0+IPlFi/D8ei9y/jj3yucJWNfPnbB5appVBWevAyUdZA5KzQ2nK/hDxiu9olTZlFEFpf1m1Rn3FaGxHqsw=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zwGLSiK48z3PzKpQiDMKP85+fpIrPMF1qQOQW9OW7BGj5AuBZIisT2O4VzIgYJeh+t47MLU7VgBQL7muc+MJDg=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.933.0", "", { "dependencies": { "@aws-sdk/core": "3.932.0", "@aws-sdk/nested-clients": "3.933.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Qzq7zj9yXUgAAJEbbmqRhm0jmUndl8nHG0AbxFEfCfQRVZWL96Qzx0mf8lYwT9hIMrXncLwy31HOthmbXwFRwQ=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-o1GX0+IPlFi/D8ei9y/jj3yucJWNfPnbB5appVBWevAyUdZA5KzQ2nK/hDxiu9olTZlFEFpf1m1Rn3FaGxHqsw=="],
"@aws-sdk/credential-provider-process/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="],
"@aws-sdk/credential-provider-sso/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="],
"@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="],
"@aws-sdk/credential-providers/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="],
"@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="],
"@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="],
"@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
"@azure/core-xml/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
@@ -5254,6 +5432,10 @@
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="],
"@aws-sdk/client-sso/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="],
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA=="],
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-5GlJBejo8wqMpSSEKb45WE82YxI2k73YuebjLH/eWDNQeE6VI5Bh9lA1YQ7xNkLLH8hIsb0pSfKVuwh0VEzVrg=="],
@@ -5262,6 +5444,32 @@
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA=="],
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.5", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA=="],
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/middleware-user-agent/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.993.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw=="],
"@aws-sdk/credential-provider-env/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="],
"@aws-sdk/credential-provider-http/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="],
"@aws-sdk/credential-provider-ini/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="],
"@aws-sdk/credential-provider-login/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="],
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-o1GX0+IPlFi/D8ei9y/jj3yucJWNfPnbB5appVBWevAyUdZA5KzQ2nK/hDxiu9olTZlFEFpf1m1Rn3FaGxHqsw=="],
"@aws-sdk/credential-provider-process/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="],
"@aws-sdk/credential-provider-sso/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="],
"@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="],
"@aws-sdk/credential-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="],
"@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="],
"@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="],
"@jsx-email/cli/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"@jsx-email/cli/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
@@ -5412,8 +5620,34 @@
"@astrojs/check/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
"@aws-sdk/client-sso/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA=="],
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="],
"@aws-sdk/credential-provider-env/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
"@aws-sdk/credential-provider-http/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
"@aws-sdk/credential-provider-ini/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
"@aws-sdk/credential-provider-login/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
"@aws-sdk/credential-provider-process/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
"@aws-sdk/credential-provider-sso/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
"@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
"@aws-sdk/credential-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
"@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
"@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
"@jsx-email/cli/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="],
@@ -5448,6 +5682,8 @@
"tw-to-css/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
"archiver-utils/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"archiver-utils/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],

View File

@@ -1,8 +1,8 @@
{
"nodeModules": {
"x86_64-linux": "sha256-7y6gQyIxyrdp2DaG/0oOEpuL+1n9oa8arUn1CuDiDhA=",
"aarch64-linux": "sha256-7dnHO2WqQZ9A8cG3EC8p7408YR9n2F5C6DG5rNWHqNY=",
"aarch64-darwin": "sha256-jxjhnVfE61RVOHaWvDO4mGLk6guQ8jHeXv/pbu5nbaE=",
"x86_64-darwin": "sha256-22yM4FEtVxGWRug6H0rKog86Q/cYE3QsADrRbLeJKVQ="
"x86_64-linux": "sha256-zs3o4OrLGqECnOxzbawP1UC+a7U3pZKr9QE+36qW+iA=",
"aarch64-linux": "sha256-bg0xtNJBbaZpDleCw+S6aay9Ntcil/h4HW7a1jGfc8Q=",
"aarch64-darwin": "sha256-alEZaFnNgd/7evGv+HLUieeRr8+YVN/FxhH2sNQBMcQ=",
"x86_64-darwin": "sha256-NMBZX6Y7JCUqK6ntCoaf7/a6tFArzDSV/TnBCTtwGMw="
}
}

View File

@@ -55,6 +55,7 @@
"@actions/core": "1.11.1",
"@actions/github": "6.0.1",
"@agentclientprotocol/sdk": "0.14.1",
"@aws-sdk/credential-providers": "3.993.0",
"@ai-sdk/amazon-bedrock": "3.0.79",
"@ai-sdk/anthropic": "2.0.62",
"@ai-sdk/azure": "2.0.91",
@@ -107,6 +108,7 @@
"drizzle-orm": "1.0.0-beta.12-a5629fb",
"fuzzysort": "3.1.0",
"gray-matter": "4.0.3",
"google-auth-library": "10.5.0",
"hono": "catalog:",
"hono-openapi": "catalog:",
"ignore": "7.0.5",

View File

@@ -63,6 +63,7 @@ export namespace Agent {
question: "deny",
plan_enter: "deny",
plan_exit: "deny",
edit: "ask",
// mirrors github.com/github/gitignore Node.gitignore pattern for .env files
read: {
"*": "allow",

View File

@@ -365,6 +365,11 @@ export const RunCommand = cmd({
action: "deny",
pattern: "*",
},
{
permission: "edit",
action: "allow",
pattern: "*",
},
]
function title() {

View File

@@ -457,6 +457,7 @@ function App() {
{
title: "Toggle MCPs",
value: "mcp.list",
search: "toggle mcps",
category: "Agent",
slash: {
name: "mcps",
@@ -532,8 +533,9 @@ function App() {
category: "System",
},
{
title: "Toggle appearance",
title: mode() === "dark" ? "Light mode" : "Dark mode",
value: "theme.switch_mode",
search: "toggle appearance",
onSelect: (dialog) => {
setMode(mode() === "dark" ? "light" : "dark")
dialog.clear()
@@ -572,6 +574,7 @@ function App() {
},
{
title: "Toggle debug panel",
search: "toggle debug",
category: "System",
value: "app.debug",
onSelect: (dialog) => {
@@ -581,6 +584,7 @@ function App() {
},
{
title: "Toggle console",
search: "toggle console",
category: "System",
value: "app.console",
onSelect: (dialog) => {
@@ -621,6 +625,7 @@ function App() {
{
title: terminalTitleEnabled() ? "Disable terminal title" : "Enable terminal title",
value: "terminal.title.toggle",
search: "toggle terminal title",
keybind: "terminal_title_toggle",
category: "System",
onSelect: (dialog) => {
@@ -636,6 +641,7 @@ function App() {
{
title: kv.get("animations_enabled", true) ? "Disable animations" : "Enable animations",
value: "app.toggle.animations",
search: "toggle animations",
category: "System",
onSelect: (dialog) => {
kv.set("animations_enabled", !kv.get("animations_enabled", true))
@@ -645,6 +651,7 @@ function App() {
{
title: kv.get("diff_wrap_mode", "word") === "word" ? "Disable diff wrapping" : "Enable diff wrapping",
value: "app.toggle.diffwrap",
search: "toggle diff wrapping",
category: "System",
onSelect: (dialog) => {
const current = kv.get("diff_wrap_mode", "word")

View File

@@ -7,6 +7,27 @@ import { useDialog } from "@tui/ui/dialog"
import { createDialogProviderOptions, DialogProvider } from "./dialog-provider"
import { useKeybind } from "../context/keybind"
import * as fuzzysort from "fuzzysort"
import type { Provider } from "@opencode-ai/sdk/v2"
function pickLatest(models: [string, Provider["models"][string]][]) {
const picks: Record<string, [string, Provider["models"][string]]> = {}
for (const item of models) {
const model = item[0]
const info = item[1]
const key = info.family ?? model
const prev = picks[key]
if (!prev) {
picks[key] = item
continue
}
if (info.release_date !== prev[1].release_date) {
if (info.release_date > prev[1].release_date) picks[key] = item
continue
}
if (model > prev[0]) picks[key] = item
}
return Object.values(picks)
}
export function useConnected() {
const sync = useSync()
@@ -21,6 +42,7 @@ export function DialogModel(props: { providerID?: string }) {
const dialog = useDialog()
const keybind = useKeybind()
const [query, setQuery] = createSignal("")
const [all, setAll] = createSignal(false)
const connected = useConnected()
const providers = createDialogProviderOptions()
@@ -72,8 +94,8 @@ export function DialogModel(props: { providerID?: string }) {
(provider) => provider.id !== "opencode",
(provider) => provider.name,
),
flatMap((provider) =>
pipe(
flatMap((provider) => {
const items = pipe(
provider.models,
entries(),
filter(([_, info]) => info.status !== "deprecated"),
@@ -104,8 +126,9 @@ export function DialogModel(props: { providerID?: string }) {
(x) => x.footer !== "Free",
(x) => x.title,
),
),
),
)
return items
}),
)
const popularProviders = !connected()
@@ -154,6 +177,13 @@ export function DialogModel(props: { providerID?: string }) {
local.model.toggleFavorite(option.value as { providerID: string; modelID: string })
},
},
{
keybind: keybind.all.model_show_all_toggle?.[0],
title: all() ? "Show latest only" : "Show all models",
onTrigger: () => {
setAll((value) => !value)
},
},
]}
onFilter={setQuery}
flat={true}

View File

@@ -1,9 +1,10 @@
import path from "path"
import { Global } from "@/global"
import { Filesystem } from "@/util/filesystem"
import { onMount } from "solid-js"
import { createStore } from "solid-js/store"
import { createSimpleContext } from "../../context/helper"
import { appendFile } from "fs/promises"
import { appendFile, writeFile } from "fs/promises"
function calculateFrecency(entry?: { frequency: number; lastOpen: number }): number {
if (!entry) return 0
@@ -17,9 +18,9 @@ const MAX_FRECENCY_ENTRIES = 1000
export const { use: useFrecency, provider: FrecencyProvider } = createSimpleContext({
name: "Frecency",
init: () => {
const frecencyFile = Bun.file(path.join(Global.Path.state, "frecency.jsonl"))
const frecencyPath = path.join(Global.Path.state, "frecency.jsonl")
onMount(async () => {
const text = await frecencyFile.text().catch(() => "")
const text = await Filesystem.readText(frecencyPath).catch(() => "")
const lines = text
.split("\n")
.filter(Boolean)
@@ -53,7 +54,7 @@ export const { use: useFrecency, provider: FrecencyProvider } = createSimpleCont
if (sorted.length > 0) {
const content = sorted.map((entry) => JSON.stringify(entry)).join("\n") + "\n"
Bun.write(frecencyFile, content).catch(() => {})
writeFile(frecencyPath, content).catch(() => {})
}
})
@@ -68,7 +69,7 @@ export const { use: useFrecency, provider: FrecencyProvider } = createSimpleCont
lastOpen: Date.now(),
}
setStore("data", absolutePath, newEntry)
appendFile(frecencyFile.name!, JSON.stringify({ path: absolutePath, ...newEntry }) + "\n").catch(() => {})
appendFile(frecencyPath, JSON.stringify({ path: absolutePath, ...newEntry }) + "\n").catch(() => {})
if (Object.keys(store.data).length > MAX_FRECENCY_ENTRIES) {
const sorted = Object.entries(store.data)
@@ -76,7 +77,7 @@ export const { use: useFrecency, provider: FrecencyProvider } = createSimpleCont
.slice(0, MAX_FRECENCY_ENTRIES)
setStore("data", Object.fromEntries(sorted))
const content = sorted.map(([path, entry]) => JSON.stringify({ path, ...entry })).join("\n") + "\n"
Bun.write(frecencyFile, content).catch(() => {})
writeFile(frecencyPath, content).catch(() => {})
}
}

View File

@@ -1,5 +1,6 @@
import path from "path"
import { Global } from "@/global"
import { Filesystem } from "@/util/filesystem"
import { onMount } from "solid-js"
import { createStore, produce } from "solid-js/store"
import { clone } from "remeda"
@@ -30,9 +31,9 @@ const MAX_HISTORY_ENTRIES = 50
export const { use: usePromptHistory, provider: PromptHistoryProvider } = createSimpleContext({
name: "PromptHistory",
init: () => {
const historyFile = Bun.file(path.join(Global.Path.state, "prompt-history.jsonl"))
const historyPath = path.join(Global.Path.state, "prompt-history.jsonl")
onMount(async () => {
const text = await historyFile.text().catch(() => "")
const text = await Filesystem.readText(historyPath).catch(() => "")
const lines = text
.split("\n")
.filter(Boolean)
@@ -51,7 +52,7 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create
// Rewrite file with only valid entries to self-heal corruption
if (lines.length > 0) {
const content = lines.map((line) => JSON.stringify(line)).join("\n") + "\n"
writeFile(historyFile.name!, content).catch(() => {})
writeFile(historyPath, content).catch(() => {})
}
})
@@ -97,11 +98,11 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create
if (trimmed) {
const content = store.history.map((line) => JSON.stringify(line)).join("\n") + "\n"
writeFile(historyFile.name!, content).catch(() => {})
writeFile(historyPath, content).catch(() => {})
return
}
appendFile(historyFile.name!, JSON.stringify(entry) + "\n").catch(() => {})
appendFile(historyPath, JSON.stringify(entry) + "\n").catch(() => {})
},
}
},

View File

@@ -1,6 +1,8 @@
import { BoxRenderable, TextareaRenderable, MouseEvent, PasteEvent, t, dim, fg } from "@opentui/core"
import { createEffect, createMemo, type JSX, onMount, createSignal, onCleanup, on, Show, Switch, Match } from "solid-js"
import "opentui-spinner/solid"
import path from "path"
import { Filesystem } from "@/util/filesystem"
import { useLocal } from "@tui/context/local"
import { useTheme } from "@tui/context/theme"
import { EmptyBorder } from "@tui/component/border"
@@ -75,6 +77,7 @@ export function Prompt(props: PromptProps) {
const renderer = useRenderer()
const { theme, syntax } = useTheme()
const kv = useKV()
const [autoaccept, setAutoaccept] = kv.signal<"none" | "edit">("permission_auto_accept", "edit")
function promptModelWarning() {
toast.show({
@@ -168,6 +171,17 @@ export function Prompt(props: PromptProps) {
command.register(() => {
return [
{
title: autoaccept() === "none" ? "Enable autoedit" : "Disable autoedit",
value: "permission.auto_accept.toggle",
search: "toggle permissions",
keybind: "permission_auto_accept_toggle",
category: "Agent",
onSelect: (dialog) => {
setAutoaccept(() => (autoaccept() === "none" ? "edit" : "none"))
dialog.clear()
},
},
{
title: "Clear prompt",
value: "prompt.clear",
@@ -931,26 +945,26 @@ export function Prompt(props: PromptProps) {
const isUrl = /^(https?):\/\//.test(filepath)
if (!isUrl) {
try {
const file = Bun.file(filepath)
const mime = Filesystem.mimeType(filepath)
const filename = path.basename(filepath)
// Handle SVG as raw text content, not as base64 image
if (file.type === "image/svg+xml") {
if (mime === "image/svg+xml") {
event.preventDefault()
const content = await file.text().catch(() => {})
const content = await Filesystem.readText(filepath).catch(() => {})
if (content) {
pasteText(content, `[SVG: ${file.name ?? "image"}]`)
pasteText(content, `[SVG: ${filename ?? "image"}]`)
return
}
}
if (file.type.startsWith("image/")) {
if (mime.startsWith("image/")) {
event.preventDefault()
const content = await file
.arrayBuffer()
const content = await Filesystem.readArrayBuffer(filepath)
.then((buffer) => Buffer.from(buffer).toString("base64"))
.catch(() => {})
if (content) {
await pasteImage({
filename: file.name,
mime: file.type,
filename,
mime,
content,
})
return
@@ -994,23 +1008,30 @@ export function Prompt(props: PromptProps) {
cursorColor={theme.text}
syntaxStyle={syntax()}
/>
<box flexDirection="row" flexShrink={0} paddingTop={1} gap={1}>
<text fg={highlight()}>
{store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "}
</text>
<Show when={store.mode === "normal"}>
<box flexDirection="row" gap={1}>
<text flexShrink={0} fg={keybind.leader ? theme.textMuted : theme.text}>
{local.model.parsed().model}
</text>
<text fg={theme.textMuted}>{local.model.parsed().provider}</text>
<Show when={showVariant()}>
<text fg={theme.textMuted}>·</text>
<text>
<span style={{ fg: theme.warning, bold: true }}>{local.model.variant.current()}</span>
<box flexDirection="row" flexShrink={0} paddingTop={1} gap={1} justifyContent="space-between">
<box flexDirection="row" gap={1}>
<text fg={highlight()}>
{store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "}
</text>
<Show when={store.mode === "normal"}>
<box flexDirection="row" gap={1}>
<text flexShrink={0} fg={keybind.leader ? theme.textMuted : theme.text}>
{local.model.parsed().model}
</text>
</Show>
</box>
<text fg={theme.textMuted}>{local.model.parsed().provider}</text>
<Show when={showVariant()}>
<text fg={theme.textMuted}>·</text>
<text>
<span style={{ fg: theme.warning, bold: true }}>{local.model.variant.current()}</span>
</text>
</Show>
</box>
</Show>
</box>
<Show when={autoaccept() === "edit"}>
<text>
<span style={{ fg: theme.warning }}>autoedit</span>
</text>
</Show>
</box>
</box>

View File

@@ -1,5 +1,6 @@
import path from "path"
import { Global } from "@/global"
import { Filesystem } from "@/util/filesystem"
import { onMount } from "solid-js"
import { createStore, produce } from "solid-js/store"
import { clone } from "remeda"
@@ -18,9 +19,9 @@ const MAX_STASH_ENTRIES = 50
export const { use: usePromptStash, provider: PromptStashProvider } = createSimpleContext({
name: "PromptStash",
init: () => {
const stashFile = Bun.file(path.join(Global.Path.state, "prompt-stash.jsonl"))
const stashPath = path.join(Global.Path.state, "prompt-stash.jsonl")
onMount(async () => {
const text = await stashFile.text().catch(() => "")
const text = await Filesystem.readText(stashPath).catch(() => "")
const lines = text
.split("\n")
.filter(Boolean)
@@ -39,7 +40,7 @@ export const { use: usePromptStash, provider: PromptStashProvider } = createSimp
// Rewrite file with only valid entries to self-heal corruption
if (lines.length > 0) {
const content = lines.map((line) => JSON.stringify(line)).join("\n") + "\n"
writeFile(stashFile.name!, content).catch(() => {})
writeFile(stashPath, content).catch(() => {})
}
})
@@ -66,11 +67,11 @@ export const { use: usePromptStash, provider: PromptStashProvider } = createSimp
if (trimmed) {
const content = store.entries.map((line) => JSON.stringify(line)).join("\n") + "\n"
writeFile(stashFile.name!, content).catch(() => {})
writeFile(stashPath, content).catch(() => {})
return
}
appendFile(stashFile.name!, JSON.stringify(stash) + "\n").catch(() => {})
appendFile(stashPath, JSON.stringify(stash) + "\n").catch(() => {})
},
pop() {
if (store.entries.length === 0) return undefined
@@ -82,7 +83,7 @@ export const { use: usePromptStash, provider: PromptStashProvider } = createSimp
)
const content =
store.entries.length > 0 ? store.entries.map((line) => JSON.stringify(line)).join("\n") + "\n" : ""
writeFile(stashFile.name!, content).catch(() => {})
writeFile(stashPath, content).catch(() => {})
return entry
},
remove(index: number) {
@@ -94,7 +95,7 @@ export const { use: usePromptStash, provider: PromptStashProvider } = createSimp
)
const content =
store.entries.length > 0 ? store.entries.map((line) => JSON.stringify(line)).join("\n") + "\n" : ""
writeFile(stashFile.name!, content).catch(() => {})
writeFile(stashPath, content).catch(() => {})
},
}
},

View File

@@ -1,4 +1,5 @@
import { Global } from "@/global"
import { Filesystem } from "@/util/filesystem"
import { createSignal, type Setter } from "solid-js"
import { createStore } from "solid-js/store"
import { createSimpleContext } from "./helper"
@@ -9,10 +10,9 @@ export const { use: useKV, provider: KVProvider } = createSimpleContext({
init: () => {
const [ready, setReady] = createSignal(false)
const [store, setStore] = createStore<Record<string, any>>()
const file = Bun.file(path.join(Global.Path.state, "kv.json"))
const filePath = path.join(Global.Path.state, "kv.json")
file
.json()
Filesystem.readJson(filePath)
.then((x) => {
setStore(x)
})
@@ -44,7 +44,7 @@ export const { use: useKV, provider: KVProvider } = createSimpleContext({
},
set(key: string, value: any) {
setStore(key, value)
Bun.write(file, JSON.stringify(store, null, 2))
Filesystem.writeJson(filePath, store)
},
}
return result

View File

@@ -12,6 +12,7 @@ import { Provider } from "@/provider/provider"
import { useArgs } from "./args"
import { useSDK } from "./sdk"
import { RGBA } from "@opentui/core"
import { Filesystem } from "@/util/filesystem"
export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
name: "Local",
@@ -119,7 +120,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
variant: {},
})
const file = Bun.file(path.join(Global.Path.state, "model.json"))
const filePath = path.join(Global.Path.state, "model.json")
const state = {
pending: false,
}
@@ -130,19 +131,15 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
return
}
state.pending = false
Bun.write(
file,
JSON.stringify({
recent: modelStore.recent,
favorite: modelStore.favorite,
variant: modelStore.variant,
}),
)
Filesystem.writeJson(filePath, {
recent: modelStore.recent,
favorite: modelStore.favorite,
variant: modelStore.variant,
})
}
file
.json()
.then((x) => {
Filesystem.readJson(filePath)
.then((x: any) => {
if (Array.isArray(x.recent)) setModelStore("recent", x.recent)
if (Array.isArray(x.favorite)) setModelStore("favorite", x.favorite)
if (typeof x.variant === "object" && x.variant !== null) setModelStore("variant", x.variant)

View File

@@ -25,6 +25,7 @@ import { createSimpleContext } from "./helper"
import type { Snapshot } from "@/snapshot"
import { useExit } from "./exit"
import { useArgs } from "./args"
import { useKV } from "./kv"
import { batch, onMount } from "solid-js"
import { Log } from "@/util/log"
import type { Path } from "@opencode-ai/sdk"
@@ -103,6 +104,8 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
})
const sdk = useSDK()
const kv = useKV()
const [autoaccept] = kv.signal<"none" | "edit">("permission_auto_accept", "edit")
sdk.event.listen((e) => {
const event = e.details
@@ -127,6 +130,13 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
case "permission.asked": {
const request = event.properties
if (autoaccept() === "edit" && request.permission === "edit") {
sdk.client.permission.reply({
reply: "once",
requestID: request.id,
})
break
}
const requests = store.permission[request.sessionID]
if (!requests) {
setStore("permission", request.sessionID, [request])
@@ -441,6 +451,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
get ready() {
return store.status !== "loading"
},
session: {
get(sessionID: string) {
const match = Binary.search(store.session, sessionID, (s) => s.id)

View File

@@ -412,7 +412,7 @@ async function getCustomThemes() {
cwd: dir,
})) {
const name = path.basename(item, ".json")
result[name] = await Bun.file(item).json()
result[name] = await Filesystem.readJson(item)
}
}
return result

View File

@@ -46,6 +46,7 @@ export function Home() {
{
title: tipsHidden() ? "Show tips" : "Hide tips",
value: "tips.toggle",
search: "toggle tips",
keybind: "tips_toggle",
category: "System",
onSelect: (dialog) => {

View File

@@ -523,6 +523,7 @@ export function Session() {
{
title: sidebarVisible() ? "Hide sidebar" : "Show sidebar",
value: "session.sidebar.toggle",
search: "toggle sidebar",
keybind: "sidebar_toggle",
category: "Session",
onSelect: (dialog) => {
@@ -537,6 +538,7 @@ export function Session() {
{
title: conceal() ? "Disable code concealment" : "Enable code concealment",
value: "session.toggle.conceal",
search: "toggle code concealment",
keybind: "messages_toggle_conceal" as any,
category: "Session",
onSelect: (dialog) => {
@@ -547,6 +549,7 @@ export function Session() {
{
title: showTimestamps() ? "Hide timestamps" : "Show timestamps",
value: "session.toggle.timestamps",
search: "toggle timestamps",
category: "Session",
slash: {
name: "timestamps",
@@ -560,6 +563,7 @@ export function Session() {
{
title: showThinking() ? "Hide thinking" : "Show thinking",
value: "session.toggle.thinking",
search: "toggle thinking",
keybind: "display_thinking",
category: "Session",
slash: {
@@ -574,6 +578,7 @@ export function Session() {
{
title: showDetails() ? "Hide tool details" : "Show tool details",
value: "session.toggle.actions",
search: "toggle tool details",
keybind: "tool_details",
category: "Session",
onSelect: (dialog) => {
@@ -582,8 +587,9 @@ export function Session() {
},
},
{
title: "Toggle session scrollbar",
title: showScrollbar() ? "Hide session scrollbar" : "Show session scrollbar",
value: "session.toggle.scrollbar",
search: "toggle session scrollbar",
keybind: "scrollbar_toggle",
category: "Session",
onSelect: (dialog) => {

View File

@@ -3,10 +3,12 @@ import { tui } from "./app"
import { Rpc } from "@/util/rpc"
import { type rpc } from "./worker"
import path from "path"
import { fileURLToPath } from "url"
import { UI } from "@/cli/ui"
import { iife } from "@/util/iife"
import { Log } from "@/util/log"
import { withNetworkOptions, resolveNetworkOptions } from "@/cli/network"
import { Filesystem } from "@/util/filesystem"
import type { Event } from "@opencode-ai/sdk/v2"
import type { EventSource } from "./context/sdk"
import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"
@@ -99,7 +101,7 @@ export const TuiThreadCommand = cmd({
const distWorker = new URL("./cli/cmd/tui/worker.js", import.meta.url)
const workerPath = await iife(async () => {
if (typeof OPENCODE_WORKER_PATH !== "undefined") return OPENCODE_WORKER_PATH
if (await Bun.file(distWorker).exists()) return distWorker
if (await Filesystem.exists(fileURLToPath(distWorker))) return distWorker
return localWorker
})
try {

View File

@@ -34,6 +34,7 @@ export interface DialogSelectOption<T = any> {
title: string
value: T
description?: string
search?: string
footer?: JSX.Element | string
category?: string
disabled?: boolean
@@ -85,8 +86,8 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
// users typically search by the item name, and not its category.
const result = fuzzysort
.go(needle, options, {
keys: ["title", "category"],
scoreFn: (r) => r[0].score * 2 + r[1].score,
keys: ["title", "category", "search"],
scoreFn: (r) => r[0].score * 2 + r[1].score + r[2].score,
})
.map((x) => x.obj)

View File

@@ -89,7 +89,13 @@ export namespace Config {
const remoteConfig = wellknown.config ?? {}
// Add $schema to prevent load() from trying to write back to a non-existent file
if (!remoteConfig.$schema) remoteConfig.$schema = "https://opencode.ai/config.json"
result = merge(result, await load(JSON.stringify(remoteConfig), `${key}/.well-known/opencode`))
result = merge(
result,
await load(JSON.stringify(remoteConfig), {
dir: path.dirname(`${key}/.well-known/opencode`),
source: `${key}/.well-known/opencode`,
}),
)
log.debug("loaded remote config from well-known", { url: key })
}
}
@@ -177,8 +183,14 @@ export namespace Config {
}
// Inline config content overrides all non-managed config sources.
if (Flag.OPENCODE_CONFIG_CONTENT) {
result = merge(result, JSON.parse(Flag.OPENCODE_CONFIG_CONTENT))
if (process.env.OPENCODE_CONFIG_CONTENT) {
result = merge(
result,
await load(process.env.OPENCODE_CONFIG_CONTENT, {
dir: Instance.directory,
source: "OPENCODE_CONFIG_CONTENT",
}),
)
log.debug("loaded custom config from OPENCODE_CONFIG_CONTENT")
}
@@ -780,6 +792,7 @@ export namespace Config {
stash_delete: z.string().optional().default("ctrl+d").describe("Delete stash entry"),
model_provider_list: z.string().optional().default("ctrl+a").describe("Open provider list from model dialog"),
model_favorite_toggle: z.string().optional().default("ctrl+f").describe("Toggle model favorite status"),
model_show_all_toggle: z.string().optional().default("ctrl+o").describe("Toggle showing all models"),
session_share: z.string().optional().default("none").describe("Share current session"),
session_unshare: z.string().optional().default("none").describe("Unshare current session"),
session_interrupt: z.string().optional().default("escape").describe("Interrupt current session"),
@@ -820,7 +833,12 @@ export namespace Config {
command_list: z.string().optional().default("ctrl+p").describe("List available commands"),
agent_list: z.string().optional().default("<leader>a").describe("List agents"),
agent_cycle: z.string().optional().default("tab").describe("Next agent"),
agent_cycle_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
agent_cycle_reverse: z.string().optional().default("none").describe("Previous agent"),
permission_auto_accept_toggle: z
.string()
.optional()
.default("shift+tab")
.describe("Toggle auto-accept mode for permissions"),
variant_cycle: z.string().optional().default("ctrl+t").describe("Cycle model variants"),
input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),
@@ -1236,24 +1254,27 @@ export namespace Config {
throw new JsonError({ path: filepath }, { cause: err })
})
if (!text) return {}
return load(text, filepath)
return load(text, { path: filepath })
}
async function load(text: string, configFilepath: string) {
async function load(text: string, options: { path: string } | { dir: string; source: string }) {
const original = text
const configDir = "path" in options ? path.dirname(options.path) : options.dir
const source = "path" in options ? options.path : options.source
const isFile = "path" in options
text = text.replace(/\{env:([^}]+)\}/g, (_, varName) => {
return process.env[varName] || ""
})
const fileMatches = text.match(/\{file:[^}]+\}/g)
if (fileMatches) {
const configDir = path.dirname(configFilepath)
const lines = text.split("\n")
for (const match of fileMatches) {
const lineIndex = lines.findIndex((line) => line.includes(match))
if (lineIndex !== -1 && lines[lineIndex].trim().startsWith("//")) {
continue // Skip if line is commented
continue
}
let filePath = match.replace(/^\{file:/, "").replace(/\}$/, "")
if (filePath.startsWith("~/")) {
@@ -1261,21 +1282,22 @@ export namespace Config {
}
const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(configDir, filePath)
const fileContent = (
await Filesystem.readText(resolvedPath).catch((error: any) => {
const errMsg = `bad file reference: "${match}"`
if (error.code === "ENOENT") {
throw new InvalidError(
{
path: configFilepath,
message: errMsg + ` ${resolvedPath} does not exist`,
},
{ cause: error },
)
}
throw new InvalidError({ path: configFilepath, message: errMsg }, { cause: error })
})
await Bun.file(resolvedPath)
.text()
.catch((error) => {
const errMsg = `bad file reference: "${match}"`
if (error.code === "ENOENT") {
throw new InvalidError(
{
path: source,
message: errMsg + ` ${resolvedPath} does not exist`,
},
{ cause: error },
)
}
throw new InvalidError({ path: source, message: errMsg }, { cause: error })
})
).trim()
// escape newlines/quotes, strip outer quotes
text = text.replace(match, () => JSON.stringify(fileContent).slice(1, -1))
}
}
@@ -1299,25 +1321,24 @@ export namespace Config {
.join("\n")
throw new JsonError({
path: configFilepath,
path: source,
message: `\n--- JSONC Input ---\n${text}\n--- Errors ---\n${errorDetails}\n--- End ---`,
})
}
const parsed = Info.safeParse(data)
if (parsed.success) {
if (!parsed.data.$schema) {
if (!parsed.data.$schema && isFile) {
parsed.data.$schema = "https://opencode.ai/config.json"
// Write the $schema to the original text to preserve variables like {env:VAR}
const updated = original.replace(/^\s*\{/, '{\n "$schema": "https://opencode.ai/config.json",')
await Filesystem.write(configFilepath, updated).catch(() => {})
await Bun.write(options.path, updated).catch(() => {})
}
const data = parsed.data
if (data.plugin) {
if (data.plugin && isFile) {
for (let i = 0; i < data.plugin.length; i++) {
const plugin = data.plugin[i]
try {
data.plugin[i] = import.meta.resolve!(plugin, configFilepath)
data.plugin[i] = import.meta.resolve!(plugin, options.path)
} catch (err) {}
}
}
@@ -1325,7 +1346,7 @@ export namespace Config {
}
throw new InvalidError({
path: configFilepath,
path: source,
issues: parsed.error.issues,
})
}

View File

@@ -50,7 +50,7 @@ export namespace Flag {
export const OPENCODE_EXPERIMENTAL_LSP_TY = truthy("OPENCODE_EXPERIMENTAL_LSP_TY")
export const OPENCODE_EXPERIMENTAL_LSP_TOOL = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_LSP_TOOL")
export const OPENCODE_DISABLE_FILETIME_CHECK = truthy("OPENCODE_DISABLE_FILETIME_CHECK")
export const OPENCODE_EXPERIMENTAL_PLAN_MODE = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_PLAN_MODE")
export const OPENCODE_EXPERIMENTAL_MARKDOWN = truthy("OPENCODE_EXPERIMENTAL_MARKDOWN")
export const OPENCODE_MODELS_URL = process.env["OPENCODE_MODELS_URL"]
export const OPENCODE_MODELS_PATH = process.env["OPENCODE_MODELS_PATH"]

View File

@@ -147,8 +147,7 @@ export namespace LSPClient {
notify: {
async open(input: { path: string }) {
input.path = path.isAbsolute(input.path) ? input.path : path.resolve(Instance.directory, input.path)
const file = Bun.file(input.path)
const text = await file.text()
const text = await Filesystem.readText(input.path)
const extension = path.extname(input.path)
const languageId = LANGUAGE_EXTENSIONS[extension] ?? "plaintext"

View File

@@ -131,7 +131,7 @@ export namespace LSPServer {
"bin",
"vue-language-server.js",
)
if (!(await Bun.file(js).exists())) {
if (!(await Filesystem.exists(js))) {
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
await Bun.spawn([BunProc.which(), "install", "@vue/language-server"], {
cwd: Global.Path.bin,
@@ -173,14 +173,14 @@ export namespace LSPServer {
if (!eslint) return
log.info("spawning eslint server")
const serverPath = path.join(Global.Path.bin, "vscode-eslint", "server", "out", "eslintServer.js")
if (!(await Bun.file(serverPath).exists())) {
if (!(await Filesystem.exists(serverPath))) {
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
log.info("downloading and building VS Code ESLint server")
const response = await fetch("https://github.com/microsoft/vscode-eslint/archive/refs/heads/main.zip")
if (!response.ok) return
const zipPath = path.join(Global.Path.bin, "vscode-eslint.zip")
await Bun.file(zipPath).write(response)
if (response.body) await Filesystem.writeStream(zipPath, response.body)
const ok = await Archive.extractZip(zipPath, Global.Path.bin)
.then(() => true)
@@ -242,7 +242,7 @@ export namespace LSPServer {
const resolveBin = async (target: string) => {
const localBin = path.join(root, target)
if (await Bun.file(localBin).exists()) return localBin
if (await Filesystem.exists(localBin)) return localBin
const candidates = Filesystem.up({
targets: [target],
@@ -326,7 +326,7 @@ export namespace LSPServer {
async spawn(root) {
const localBin = path.join(root, "node_modules", ".bin", "biome")
let bin: string | undefined
if (await Bun.file(localBin).exists()) bin = localBin
if (await Filesystem.exists(localBin)) bin = localBin
if (!bin) {
const found = Bun.which("biome")
if (found) bin = found
@@ -467,7 +467,7 @@ export namespace LSPServer {
const potentialPythonPath = isWindows
? path.join(venvPath, "Scripts", "python.exe")
: path.join(venvPath, "bin", "python")
if (await Bun.file(potentialPythonPath).exists()) {
if (await Filesystem.exists(potentialPythonPath)) {
initialization["pythonPath"] = potentialPythonPath
break
}
@@ -479,7 +479,7 @@ export namespace LSPServer {
const potentialTyPath = isWindows
? path.join(venvPath, "Scripts", "ty.exe")
: path.join(venvPath, "bin", "ty")
if (await Bun.file(potentialTyPath).exists()) {
if (await Filesystem.exists(potentialTyPath)) {
binary = potentialTyPath
break
}
@@ -511,7 +511,7 @@ export namespace LSPServer {
const args = []
if (!binary) {
const js = path.join(Global.Path.bin, "node_modules", "pyright", "dist", "pyright-langserver.js")
if (!(await Bun.file(js).exists())) {
if (!(await Filesystem.exists(js))) {
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
await Bun.spawn([BunProc.which(), "install", "pyright"], {
cwd: Global.Path.bin,
@@ -536,7 +536,7 @@ export namespace LSPServer {
const potentialPythonPath = isWindows
? path.join(venvPath, "Scripts", "python.exe")
: path.join(venvPath, "bin", "python")
if (await Bun.file(potentialPythonPath).exists()) {
if (await Filesystem.exists(potentialPythonPath)) {
initialization["pythonPath"] = potentialPythonPath
break
}
@@ -571,7 +571,7 @@ export namespace LSPServer {
process.platform === "win32" ? "language_server.bat" : "language_server.sh",
)
if (!(await Bun.file(binary).exists())) {
if (!(await Filesystem.exists(binary))) {
const elixir = Bun.which("elixir")
if (!elixir) {
log.error("elixir is required to run elixir-ls")
@@ -584,7 +584,7 @@ export namespace LSPServer {
const response = await fetch("https://github.com/elixir-lsp/elixir-ls/archive/refs/heads/master.zip")
if (!response.ok) return
const zipPath = path.join(Global.Path.bin, "elixir-ls.zip")
await Bun.file(zipPath).write(response)
if (response.body) await Filesystem.writeStream(zipPath, response.body)
const ok = await Archive.extractZip(zipPath, Global.Path.bin)
.then(() => true)
@@ -692,7 +692,7 @@ export namespace LSPServer {
}
const tempPath = path.join(Global.Path.bin, assetName)
await Bun.file(tempPath).write(downloadResponse)
if (downloadResponse.body) await Filesystem.writeStream(tempPath, downloadResponse.body)
if (ext === "zip") {
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
@@ -710,7 +710,7 @@ export namespace LSPServer {
bin = path.join(Global.Path.bin, "zls" + (platform === "win32" ? ".exe" : ""))
if (!(await Bun.file(bin).exists())) {
if (!(await Filesystem.exists(bin))) {
log.error("Failed to extract zls binary")
return
}
@@ -857,7 +857,7 @@ export namespace LSPServer {
// Stop at filesystem root
const cargoTomlPath = path.join(currentDir, "Cargo.toml")
try {
const cargoTomlContent = await Bun.file(cargoTomlPath).text()
const cargoTomlContent = await Filesystem.readText(cargoTomlPath)
if (cargoTomlContent.includes("[workspace]")) {
return currentDir
}
@@ -907,7 +907,7 @@ export namespace LSPServer {
const ext = process.platform === "win32" ? ".exe" : ""
const direct = path.join(Global.Path.bin, "clangd" + ext)
if (await Bun.file(direct).exists()) {
if (await Filesystem.exists(direct)) {
return {
process: spawn(direct, args, {
cwd: root,
@@ -920,7 +920,7 @@ export namespace LSPServer {
if (!entry.isDirectory()) continue
if (!entry.name.startsWith("clangd_")) continue
const candidate = path.join(Global.Path.bin, entry.name, "bin", "clangd" + ext)
if (await Bun.file(candidate).exists()) {
if (await Filesystem.exists(candidate)) {
return {
process: spawn(candidate, args, {
cwd: root,
@@ -990,7 +990,7 @@ export namespace LSPServer {
log.error("Failed to write clangd archive")
return
}
await Bun.write(archive, buf)
await Filesystem.write(archive, Buffer.from(buf))
const zip = name.endsWith(".zip")
const tar = name.endsWith(".tar.xz")
@@ -1014,7 +1014,7 @@ export namespace LSPServer {
await fs.rm(archive, { force: true })
const bin = path.join(Global.Path.bin, "clangd_" + tag, "bin", "clangd" + ext)
if (!(await Bun.file(bin).exists())) {
if (!(await Filesystem.exists(bin))) {
log.error("Failed to extract clangd binary")
return
}
@@ -1045,7 +1045,7 @@ export namespace LSPServer {
const args: string[] = []
if (!binary) {
const js = path.join(Global.Path.bin, "node_modules", "svelte-language-server", "bin", "server.js")
if (!(await Bun.file(js).exists())) {
if (!(await Filesystem.exists(js))) {
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
await Bun.spawn([BunProc.which(), "install", "svelte-language-server"], {
cwd: Global.Path.bin,
@@ -1092,7 +1092,7 @@ export namespace LSPServer {
const args: string[] = []
if (!binary) {
const js = path.join(Global.Path.bin, "node_modules", "@astrojs", "language-server", "bin", "nodeServer.js")
if (!(await Bun.file(js).exists())) {
if (!(await Filesystem.exists(js))) {
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
await Bun.spawn([BunProc.which(), "install", "@astrojs/language-server"], {
cwd: Global.Path.bin,
@@ -1248,7 +1248,7 @@ export namespace LSPServer {
const distPath = path.join(Global.Path.bin, "kotlin-ls")
const launcherScript =
process.platform === "win32" ? path.join(distPath, "kotlin-lsp.cmd") : path.join(distPath, "kotlin-lsp.sh")
const installed = await Bun.file(launcherScript).exists()
const installed = await Filesystem.exists(launcherScript)
if (!installed) {
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
log.info("Downloading Kotlin Language Server from GitHub.")
@@ -1307,7 +1307,7 @@ export namespace LSPServer {
}
log.info("Installed Kotlin Language Server", { path: launcherScript })
}
if (!(await Bun.file(launcherScript).exists())) {
if (!(await Filesystem.exists(launcherScript))) {
log.error(`Failed to locate the Kotlin LS launcher script in the installed directory: ${distPath}.`)
return
}
@@ -1336,7 +1336,7 @@ export namespace LSPServer {
"src",
"server.js",
)
const exists = await Bun.file(js).exists()
const exists = await Filesystem.exists(js)
if (!exists) {
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
await Bun.spawn([BunProc.which(), "install", "yaml-language-server"], {
@@ -1443,7 +1443,7 @@ export namespace LSPServer {
}
const tempPath = path.join(Global.Path.bin, assetName)
await Bun.file(tempPath).write(downloadResponse)
if (downloadResponse.body) await Filesystem.writeStream(tempPath, downloadResponse.body)
// Unlike zls which is a single self-contained binary,
// lua-language-server needs supporting files (meta/, locale/, etc.)
@@ -1482,7 +1482,7 @@ export namespace LSPServer {
// Binary is located in bin/ subdirectory within the extracted archive
bin = path.join(installDir, "bin", "lua-language-server" + (platform === "win32" ? ".exe" : ""))
if (!(await Bun.file(bin).exists())) {
if (!(await Filesystem.exists(bin))) {
log.error("Failed to extract lua-language-server binary")
return
}
@@ -1516,7 +1516,7 @@ export namespace LSPServer {
const args: string[] = []
if (!binary) {
const js = path.join(Global.Path.bin, "node_modules", "intelephense", "lib", "intelephense.js")
if (!(await Bun.file(js).exists())) {
if (!(await Filesystem.exists(js))) {
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
await Bun.spawn([BunProc.which(), "install", "intelephense"], {
cwd: Global.Path.bin,
@@ -1613,7 +1613,7 @@ export namespace LSPServer {
const args: string[] = []
if (!binary) {
const js = path.join(Global.Path.bin, "node_modules", "bash-language-server", "out", "cli.js")
if (!(await Bun.file(js).exists())) {
if (!(await Filesystem.exists(js))) {
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
await Bun.spawn([BunProc.which(), "install", "bash-language-server"], {
cwd: Global.Path.bin,
@@ -1654,22 +1654,17 @@ export namespace LSPServer {
if (!bin) {
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
log.info("downloading terraform-ls from GitHub releases")
log.info("downloading terraform-ls from HashiCorp releases")
const releaseResponse = await fetch("https://api.github.com/repos/hashicorp/terraform-ls/releases/latest")
const releaseResponse = await fetch("https://api.releases.hashicorp.com/v1/releases/terraform-ls/latest")
if (!releaseResponse.ok) {
log.error("Failed to fetch terraform-ls release info")
return
}
const release = (await releaseResponse.json()) as {
tag_name?: string
assets?: { name?: string; browser_download_url?: string }[]
}
const version = release.tag_name?.replace("v", "")
if (!version) {
log.error("terraform-ls release did not include a version tag")
return
version?: string
builds?: { arch?: string; os?: string; url?: string }[]
}
const platform = process.platform
@@ -1678,23 +1673,21 @@ export namespace LSPServer {
const tfArch = arch === "arm64" ? "arm64" : "amd64"
const tfPlatform = platform === "win32" ? "windows" : platform
const assetName = `terraform-ls_${version}_${tfPlatform}_${tfArch}.zip`
const assets = release.assets ?? []
const asset = assets.find((a) => a.name === assetName)
if (!asset?.browser_download_url) {
log.error(`Could not find asset ${assetName} in terraform-ls release`)
const builds = release.builds ?? []
const build = builds.find((b) => b.arch === tfArch && b.os === tfPlatform)
if (!build?.url) {
log.error(`Could not find build for ${tfPlatform}/${tfArch} terraform-ls release version ${release.version}`)
return
}
const downloadResponse = await fetch(asset.browser_download_url)
const downloadResponse = await fetch(build.url)
if (!downloadResponse.ok) {
log.error("Failed to download terraform-ls")
return
}
const tempPath = path.join(Global.Path.bin, assetName)
await Bun.file(tempPath).write(downloadResponse)
const tempPath = path.join(Global.Path.bin, "terraform-ls.zip")
if (downloadResponse.body) await Filesystem.writeStream(tempPath, downloadResponse.body)
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
.then(() => true)
@@ -1707,7 +1700,7 @@ export namespace LSPServer {
bin = path.join(Global.Path.bin, "terraform-ls" + (platform === "win32" ? ".exe" : ""))
if (!(await Bun.file(bin).exists())) {
if (!(await Filesystem.exists(bin))) {
log.error("Failed to extract terraform-ls binary")
return
}
@@ -1784,7 +1777,7 @@ export namespace LSPServer {
}
const tempPath = path.join(Global.Path.bin, assetName)
await Bun.file(tempPath).write(downloadResponse)
if (downloadResponse.body) await Filesystem.writeStream(tempPath, downloadResponse.body)
if (ext === "zip") {
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
@@ -1803,7 +1796,7 @@ export namespace LSPServer {
bin = path.join(Global.Path.bin, "texlab" + (platform === "win32" ? ".exe" : ""))
if (!(await Bun.file(bin).exists())) {
if (!(await Filesystem.exists(bin))) {
log.error("Failed to extract texlab binary")
return
}
@@ -1832,7 +1825,7 @@ export namespace LSPServer {
const args: string[] = []
if (!binary) {
const js = path.join(Global.Path.bin, "node_modules", "dockerfile-language-server-nodejs", "lib", "server.js")
if (!(await Bun.file(js).exists())) {
if (!(await Filesystem.exists(js))) {
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
await Bun.spawn([BunProc.which(), "install", "dockerfile-language-server-nodejs"], {
cwd: Global.Path.bin,
@@ -1990,7 +1983,7 @@ export namespace LSPServer {
}
const tempPath = path.join(Global.Path.bin, assetName)
await Bun.file(tempPath).write(downloadResponse)
if (downloadResponse.body) await Filesystem.writeStream(tempPath, downloadResponse.body)
if (ext === "zip") {
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
@@ -2008,7 +2001,7 @@ export namespace LSPServer {
bin = path.join(Global.Path.bin, "tinymist" + (platform === "win32" ? ".exe" : ""))
if (!(await Bun.file(bin).exists())) {
if (!(await Filesystem.exists(bin))) {
log.error("Failed to extract tinymist binary")
return
}

View File

@@ -1,6 +1,7 @@
import path from "path"
import z from "zod"
import { Global } from "../global"
import { Filesystem } from "../util/filesystem"
export namespace McpAuth {
export const Tokens = z.object({
@@ -53,25 +54,22 @@ export namespace McpAuth {
}
export async function all(): Promise<Record<string, Entry>> {
const file = Bun.file(filepath)
return file.json().catch(() => ({}))
return Filesystem.readJson<Record<string, Entry>>(filepath).catch(() => ({}))
}
export async function set(mcpName: string, entry: Entry, serverUrl?: string): Promise<void> {
const file = Bun.file(filepath)
const data = await all()
// Always update serverUrl if provided
if (serverUrl) {
entry.serverUrl = serverUrl
}
await Bun.write(file, JSON.stringify({ ...data, [mcpName]: entry }, null, 2), { mode: 0o600 })
await Filesystem.writeJson(filepath, { ...data, [mcpName]: entry }, 0o600)
}
export async function remove(mcpName: string): Promise<void> {
const file = Bun.file(filepath)
const data = await all()
delete data[mcpName]
await Bun.write(file, JSON.stringify(data, null, 2), { mode: 0o600 })
await Filesystem.writeJson(filepath, data, 0o600)
}
export async function updateTokens(mcpName: string, tokens: Tokens, serverUrl?: string): Promise<void> {

View File

@@ -86,8 +86,7 @@ export namespace Project {
const gitBinary = Bun.which("git")
// cached id calculation
let id = await Bun.file(path.join(dotgit, "opencode"))
.text()
let id = await Filesystem.readText(path.join(dotgit, "opencode"))
.then((x) => x.trim())
.catch(() => undefined)
@@ -125,9 +124,7 @@ export namespace Project {
id = roots[0]
if (id) {
void Bun.file(path.join(dotgit, "opencode"))
.write(id)
.catch(() => undefined)
void Filesystem.write(path.join(dotgit, "opencode"), id).catch(() => undefined)
}
}
@@ -277,10 +274,9 @@ export namespace Project {
)
const shortest = matches.sort((a, b) => a.length - b.length)[0]
if (!shortest) return
const file = Bun.file(shortest)
const buffer = await file.arrayBuffer()
const base64 = Buffer.from(buffer).toString("base64")
const mime = file.type || "image/png"
const buffer = await Filesystem.readBytes(shortest)
const base64 = buffer.toString("base64")
const mime = Filesystem.mimeType(shortest) || "image/png"
const url = `data:${mime};base64,${base64}`
await update({
projectID: input.id,
@@ -381,10 +377,8 @@ export namespace Project {
const data = fromRow(row)
const valid: string[] = []
for (const dir of data.sandboxes) {
const stat = await Bun.file(dir)
.stat()
.catch(() => undefined)
if (stat?.isDirectory()) valid.push(dir)
const s = Filesystem.stat(dir)
if (s?.isDirectory()) valid.push(dir)
}
return valid
}

View File

@@ -5,6 +5,7 @@ import z from "zod"
import { Installation } from "../installation"
import { Flag } from "../flag/flag"
import { lazy } from "@/util/lazy"
import { Filesystem } from "../util/filesystem"
// Try to import bundled snapshot (generated at build time)
// Falls back to undefined in dev mode when snapshot doesn't exist
@@ -85,8 +86,7 @@ export namespace ModelsDev {
}
export const Data = lazy(async () => {
const file = Bun.file(Flag.OPENCODE_MODELS_PATH ?? filepath)
const result = await file.json().catch(() => {})
const result = await Filesystem.readJson(Flag.OPENCODE_MODELS_PATH ?? filepath).catch(() => {})
if (result) return result
// @ts-ignore
const snapshot = await import("./models-snapshot")
@@ -104,7 +104,6 @@ export namespace ModelsDev {
}
export async function refresh() {
const file = Bun.file(filepath)
const result = await fetch(`${url()}/api.json`, {
headers: {
"User-Agent": Installation.USER_AGENT,
@@ -116,7 +115,7 @@ export namespace ModelsDev {
})
})
if (result && result.ok) {
await Bun.write(file, await result.text())
await Filesystem.write(filepath, await result.text())
ModelsDev.Data.reset()
}
}

View File

@@ -16,6 +16,7 @@ import { Flag } from "../flag/flag"
import { iife } from "@/util/iife"
import { Global } from "../global"
import path from "path"
import { Filesystem } from "../util/filesystem"
// Direct imports for bundled providers
import { createAmazonBedrock, type AmazonBedrockProviderSettings } from "@ai-sdk/amazon-bedrock"
@@ -39,6 +40,8 @@ import { createTogetherAI } from "@ai-sdk/togetherai"
import { createPerplexity } from "@ai-sdk/perplexity"
import { createVercel } from "@ai-sdk/vercel"
import { createGitLab, VERSION as GITLAB_PROVIDER_VERSION } from "@gitlab/gitlab-ai-provider"
import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
import { GoogleAuth } from "google-auth-library"
import { ProviderTransform } from "./transform"
import { Installation } from "../installation"
@@ -251,8 +254,6 @@ export namespace Provider {
// Only use credential chain if no bearer token exists
// Bearer token takes precedence over credential chain (profiles, access keys, IAM roles, web identity tokens)
if (!awsBearerToken) {
const { fromNodeProviderChain } = await import(await BunProc.install("@aws-sdk/credential-providers"))
// Build credential provider options (only pass profile if specified)
const credentialProviderOptions = profile ? { profile } : {}
@@ -395,11 +396,9 @@ export namespace Provider {
project,
location,
fetch: async (input: RequestInfo | URL, init?: RequestInit) => {
const { GoogleAuth } = await import(await BunProc.install("google-auth-library"))
const auth = new GoogleAuth()
const client = await auth.getApplicationDefault()
const credentials = await client.credential
const token = await credentials.getAccessToken()
const token = await client.credential.getAccessToken()
const headers = new Headers(init?.headers)
headers.set("Authorization", `Bearer ${token.token}`)
@@ -1291,8 +1290,9 @@ export namespace Provider {
if (cfg.model) return parseModel(cfg.model)
const providers = await list()
const recent = (await Bun.file(path.join(Global.Path.state, "model.json"))
.json()
const recent = (await Filesystem.readJson<{ recent?: { providerID: string; modelID: string }[] }>(
path.join(Global.Path.state, "model.json"),
)
.then((x) => (Array.isArray(x.recent) ? x.recent : []))
.catch(() => [])) as { providerID: string; modelID: string }[]
for (const entry of recent) {

View File

@@ -85,7 +85,7 @@ export namespace InstructionPrompt {
}
for (const file of globalFiles()) {
if (await Bun.file(file).exists()) {
if (await Filesystem.exists(file)) {
paths.add(path.resolve(file))
break
}
@@ -120,9 +120,7 @@ export namespace InstructionPrompt {
const paths = await systemPaths()
const files = Array.from(paths).map(async (p) => {
const content = await Bun.file(p)
.text()
.catch(() => "")
const content = await Filesystem.readText(p).catch(() => "")
return content ? "Instructions from: " + p + "\n" + content : ""
})
@@ -164,7 +162,7 @@ export namespace InstructionPrompt {
export async function find(dir: string) {
for (const file of FILES) {
const filepath = path.resolve(path.join(dir, file))
if (await Bun.file(filepath).exists()) return filepath
if (await Filesystem.exists(filepath)) return filepath
}
}
@@ -182,9 +180,7 @@ export namespace InstructionPrompt {
if (found && found !== target && !system.has(found) && !already.has(found) && !isClaimed(messageID, found)) {
claim(messageID, found)
const content = await Bun.file(found)
.text()
.catch(() => undefined)
const content = await Filesystem.readText(found).catch(() => undefined)
if (content) {
results.push({ filepath: found, content: "Instructions from: " + found + "\n" + content })
}

View File

@@ -2,6 +2,7 @@ import path from "path"
import os from "os"
import fs from "fs/promises"
import z from "zod"
import { Filesystem } from "../util/filesystem"
import { Identifier } from "../id/id"
import { MessageV2 } from "./message-v2"
import { Log } from "../util/log"
@@ -1082,11 +1083,9 @@ export namespace SessionPrompt {
// have to normalize, symbol search returns absolute paths
// Decode the pathname since URL constructor doesn't automatically decode it
const filepath = fileURLToPath(part.url)
const stat = await Bun.file(filepath)
.stat()
.catch(() => undefined)
const s = Filesystem.stat(filepath)
if (stat?.isDirectory()) {
if (s?.isDirectory()) {
part.mime = "application/x-directory"
}
@@ -1233,14 +1232,13 @@ export namespace SessionPrompt {
]
}
const file = Bun.file(filepath)
FileTime.read(input.sessionID, filepath)
return [
{
messageID: info.id,
sessionID: input.sessionID,
type: "text",
text: `Called the Read tool with the following input: {\"filePath\":\"${filepath}\"}`,
text: `Called the Read tool with the following input: {"filePath":"${filepath}"}`,
synthetic: true,
},
{
@@ -1248,7 +1246,7 @@ export namespace SessionPrompt {
messageID: info.id,
sessionID: input.sessionID,
type: "file",
url: `data:${part.mime};base64,` + Buffer.from(await file.bytes()).toString("base64"),
url: `data:${part.mime};base64,` + (await Filesystem.readBytes(filepath)).toString("base64"),
mime: part.mime,
filename: part.filename!,
source: part.source,
@@ -1322,39 +1320,13 @@ export namespace SessionPrompt {
const userMessage = input.messages.findLast((msg) => msg.info.role === "user")
if (!userMessage) return input.messages
// Original logic when experimental plan mode is disabled
if (!Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE) {
if (input.agent.name === "plan") {
userMessage.parts.push({
id: Identifier.ascending("part"),
messageID: userMessage.info.id,
sessionID: userMessage.info.sessionID,
type: "text",
text: PROMPT_PLAN,
synthetic: true,
})
}
const wasPlan = input.messages.some((msg) => msg.info.role === "assistant" && msg.info.agent === "plan")
if (wasPlan && input.agent.name === "build") {
userMessage.parts.push({
id: Identifier.ascending("part"),
messageID: userMessage.info.id,
sessionID: userMessage.info.sessionID,
type: "text",
text: BUILD_SWITCH,
synthetic: true,
})
}
return input.messages
}
// New plan mode logic when flag is enabled
// Plan mode logic
const assistantMessage = input.messages.findLast((msg) => msg.info.role === "assistant")
// Switching from plan mode to build mode
if (input.agent.name !== "plan" && assistantMessage?.info.agent === "plan") {
const plan = Session.plan(input.session)
const exists = await Bun.file(plan).exists()
const exists = await Filesystem.exists(plan)
if (exists) {
const part = await Session.updatePart({
id: Identifier.ascending("part"),
@@ -1373,7 +1345,7 @@ export namespace SessionPrompt {
// Entering plan mode
if (input.agent.name === "plan" && assistantMessage?.info.agent !== "plan") {
const plan = Session.plan(input.session)
const exists = await Bun.file(plan).exists()
const exists = await Filesystem.exists(plan)
if (!exists) await fs.mkdir(path.dirname(plan), { recursive: true })
const part = await Session.updatePart({
id: Identifier.ascending("part"),

View File

@@ -1,5 +1,6 @@
import { Flag } from "@/flag/flag"
import { lazy } from "@/util/lazy"
import { Filesystem } from "@/util/filesystem"
import path from "path"
import { spawn, type ChildProcess } from "child_process"
@@ -43,7 +44,7 @@ export namespace Shell {
// git.exe is typically at: C:\Program Files\Git\cmd\git.exe
// bash.exe is at: C:\Program Files\Git\bin\bash.exe
const bash = path.join(git, "..", "..", "bin", "bash.exe")
if (Bun.file(bash).size) return bash
if (Filesystem.stat(bash)?.size) return bash
}
return process.env.COMSPEC || "cmd.exe"
}

View File

@@ -2,6 +2,7 @@ import path from "path"
import { mkdir } from "fs/promises"
import { Log } from "../util/log"
import { Global } from "../global"
import { Filesystem } from "../util/filesystem"
export namespace Discovery {
const log = Log.create({ service: "skill-discovery" })
@@ -19,14 +20,14 @@ export namespace Discovery {
}
async function get(url: string, dest: string): Promise<boolean> {
if (await Bun.file(dest).exists()) return true
if (await Filesystem.exists(dest)) return true
return fetch(url)
.then(async (response) => {
if (!response.ok) {
log.error("failed to download", { url, status: response.status })
return false
}
await Bun.write(dest, await response.text())
if (response.body) await Filesystem.writeStream(dest, response.body)
return true
})
.catch((err) => {
@@ -88,7 +89,7 @@ export namespace Discovery {
)
const md = path.join(root, "SKILL.md")
if (await Bun.file(md).exists()) result.push(root)
if (await Filesystem.exists(md)) result.push(root)
}),
)

View File

@@ -10,7 +10,7 @@ import { Log } from "../util/log"
import { NamedError } from "@opencode-ai/util/error"
import z from "zod"
import path from "path"
import { readFileSync, readdirSync } from "fs"
import { readFileSync, readdirSync, existsSync } from "fs"
import * as schema from "./schema"
declare const OPENCODE_MIGRATIONS: { sql: string; timestamp: number }[] | undefined
@@ -54,7 +54,7 @@ export namespace Database {
const sql = dirs
.map((name) => {
const file = path.join(dir, name, "migration.sql")
if (!Bun.file(file).size) return
if (!existsSync(file)) return
return {
sql: readFileSync(file, "utf-8"),
timestamp: time(name),

View File

@@ -7,6 +7,7 @@ import { SessionTable, MessageTable, PartTable, TodoTable, PermissionTable } fro
import { SessionShareTable } from "../share/share.sql"
import path from "path"
import { existsSync } from "fs"
import { Filesystem } from "../util/filesystem"
export namespace JsonMigration {
const log = Log.create({ service: "json-migration" })
@@ -82,7 +83,7 @@ export namespace JsonMigration {
const count = end - start
const tasks = new Array(count)
for (let i = 0; i < count; i++) {
tasks[i] = Bun.file(files[start + i]).json()
tasks[i] = Filesystem.readJson(files[start + i])
}
const results = await Promise.allSettled(tasks)
const items = new Array(count)

View File

@@ -39,7 +39,7 @@ export namespace Storage {
cwd: path.join(project, projectDir),
absolute: true,
})) {
const json = await Bun.file(msgFile).json()
const json = await Filesystem.readJson<any>(msgFile)
worktree = json.path?.root
if (worktree) break
}
@@ -60,18 +60,15 @@ export namespace Storage {
if (!id) continue
projectID = id
await Bun.write(
path.join(dir, "project", projectID + ".json"),
JSON.stringify({
id,
vcs: "git",
worktree,
time: {
created: Date.now(),
initialized: Date.now(),
},
}),
)
await Filesystem.writeJson(path.join(dir, "project", projectID + ".json"), {
id,
vcs: "git",
worktree,
time: {
created: Date.now(),
initialized: Date.now(),
},
})
log.info(`migrating sessions for project ${projectID}`)
for await (const sessionFile of new Bun.Glob("storage/session/info/*.json").scan({
@@ -83,8 +80,8 @@ export namespace Storage {
sessionFile,
dest,
})
const session = await Bun.file(sessionFile).json()
await Bun.write(dest, JSON.stringify(session))
const session = await Filesystem.readJson<any>(sessionFile)
await Filesystem.writeJson(dest, session)
log.info(`migrating messages for session ${session.id}`)
for await (const msgFile of new Bun.Glob(`storage/session/message/${session.id}/*.json`).scan({
cwd: fullProjectDir,
@@ -95,8 +92,8 @@ export namespace Storage {
msgFile,
dest,
})
const message = await Bun.file(msgFile).json()
await Bun.write(dest, JSON.stringify(message))
const message = await Filesystem.readJson<any>(msgFile)
await Filesystem.writeJson(dest, message)
log.info(`migrating parts for message ${message.id}`)
for await (const partFile of new Bun.Glob(`storage/session/part/${session.id}/${message.id}/*.json`).scan(
@@ -106,12 +103,12 @@ export namespace Storage {
},
)) {
const dest = path.join(dir, "part", message.id, path.basename(partFile))
const part = await Bun.file(partFile).json()
const part = await Filesystem.readJson(partFile)
log.info("copying", {
partFile,
dest,
})
await Bun.write(dest, JSON.stringify(part))
await Filesystem.writeJson(dest, part)
}
}
}
@@ -123,35 +120,32 @@ export namespace Storage {
cwd: dir,
absolute: true,
})) {
const session = await Bun.file(item).json()
const session = await Filesystem.readJson<any>(item)
if (!session.projectID) continue
if (!session.summary?.diffs) continue
const { diffs } = session.summary
await Bun.file(path.join(dir, "session_diff", session.id + ".json")).write(JSON.stringify(diffs))
await Bun.file(path.join(dir, "session", session.projectID, session.id + ".json")).write(
JSON.stringify({
...session,
summary: {
additions: diffs.reduce((sum: any, x: any) => sum + x.additions, 0),
deletions: diffs.reduce((sum: any, x: any) => sum + x.deletions, 0),
},
}),
)
await Filesystem.write(path.join(dir, "session_diff", session.id + ".json"), JSON.stringify(diffs))
await Filesystem.writeJson(path.join(dir, "session", session.projectID, session.id + ".json"), {
...session,
summary: {
additions: diffs.reduce((sum: any, x: any) => sum + x.additions, 0),
deletions: diffs.reduce((sum: any, x: any) => sum + x.deletions, 0),
},
})
}
},
]
const state = lazy(async () => {
const dir = path.join(Global.Path.data, "storage")
const migration = await Bun.file(path.join(dir, "migration"))
.json()
const migration = await Filesystem.readJson<string>(path.join(dir, "migration"))
.then((x) => parseInt(x))
.catch(() => 0)
for (let index = migration; index < MIGRATIONS.length; index++) {
log.info("running migration", { index })
const migration = MIGRATIONS[index]
await migration(dir).catch(() => log.error("failed to run migration", { index }))
await Bun.write(path.join(dir, "migration"), (index + 1).toString())
await Filesystem.write(path.join(dir, "migration"), (index + 1).toString())
}
return {
dir,
@@ -171,7 +165,7 @@ export namespace Storage {
const target = path.join(dir, ...key) + ".json"
return withErrorHandling(async () => {
using _ = await Lock.read(target)
const result = await Bun.file(target).json()
const result = await Filesystem.readJson<T>(target)
return result as T
})
}
@@ -181,10 +175,10 @@ export namespace Storage {
const target = path.join(dir, ...key) + ".json"
return withErrorHandling(async () => {
using _ = await Lock.write(target)
const content = await Bun.file(target).json()
fn(content)
await Bun.write(target, JSON.stringify(content, null, 2))
return content as T
const content = await Filesystem.readJson<T>(target)
fn(content as T)
await Filesystem.writeJson(target, content)
return content
})
}
@@ -193,7 +187,7 @@ export namespace Storage {
const target = path.join(dir, ...key) + ".json"
return withErrorHandling(async () => {
using _ = await Lock.write(target)
await Bun.write(target, JSON.stringify(content, null, 2))
await Filesystem.writeJson(target, content)
})
}

View File

@@ -49,7 +49,7 @@ export const EditTool = Tool.define("edit", {
let contentNew = ""
await FileTime.withLock(filePath, async () => {
if (params.oldString === "") {
const existed = await Bun.file(filePath).exists()
const existed = await Filesystem.exists(filePath)
contentNew = params.newString
diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOld, contentNew))
await ctx.ask({
@@ -61,7 +61,7 @@ export const EditTool = Tool.define("edit", {
diff,
},
})
await Bun.write(filePath, params.newString)
await Filesystem.write(filePath, params.newString)
await Bus.publish(File.Event.Edited, {
file: filePath,
})
@@ -73,12 +73,11 @@ export const EditTool = Tool.define("edit", {
return
}
const file = Bun.file(filePath)
const stats = await file.stat().catch(() => {})
const stats = Filesystem.stat(filePath)
if (!stats) throw new Error(`File ${filePath} not found`)
if (stats.isDirectory()) throw new Error(`Path is a directory, not a file: ${filePath}`)
await FileTime.assert(ctx.sessionID, filePath)
contentOld = await file.text()
contentOld = await Filesystem.readText(filePath)
contentNew = replace(contentOld, params.oldString, params.newString, params.replaceAll)
diff = trimDiff(
@@ -94,7 +93,7 @@ export const EditTool = Tool.define("edit", {
},
})
await file.write(contentNew)
await Filesystem.write(filePath, contentNew)
await Bus.publish(File.Event.Edited, {
file: filePath,
})
@@ -102,7 +101,7 @@ export const EditTool = Tool.define("edit", {
file: filePath,
event: "change",
})
contentNew = await file.text()
contentNew = await Filesystem.readText(filePath)
diff = trimDiff(
createTwoFilesPatch(filePath, filePath, normalizeLineEndings(contentOld), normalizeLineEndings(contentNew)),
)

View File

@@ -1,6 +1,7 @@
import z from "zod"
import path from "path"
import { Tool } from "./tool"
import { Filesystem } from "../util/filesystem"
import DESCRIPTION from "./glob.txt"
import { Ripgrep } from "../file/ripgrep"
import { Instance } from "../project/instance"
@@ -45,10 +46,7 @@ export const GlobTool = Tool.define("glob", {
break
}
const full = path.resolve(search, file)
const stats = await Bun.file(full)
.stat()
.then((x) => x.mtime.getTime())
.catch(() => 0)
const stats = Filesystem.stat(full)?.mtime.getTime() ?? 0
files.push({
path: full,
mtime: stats,

View File

@@ -1,5 +1,6 @@
import z from "zod"
import { Tool } from "./tool"
import { Filesystem } from "../util/filesystem"
import { Ripgrep } from "../file/ripgrep"
import DESCRIPTION from "./grep.txt"
@@ -83,8 +84,7 @@ export const GrepTool = Tool.define("grep", {
const lineNum = parseInt(lineNumStr, 10)
const lineText = lineTextParts.join("|")
const file = Bun.file(filePath)
const stats = await file.stat().catch(() => null)
const stats = Filesystem.stat(filePath)
if (!stats) continue
matches.push({

View File

@@ -6,6 +6,7 @@ import DESCRIPTION from "./lsp.txt"
import { Instance } from "../project/instance"
import { pathToFileURL } from "url"
import { assertExternalDirectory } from "./external-directory"
import { Filesystem } from "../util/filesystem"
const operations = [
"goToDefinition",
@@ -47,7 +48,7 @@ export const LspTool = Tool.define("lsp", {
const relPath = path.relative(Instance.worktree, file)
const title = `${args.operation} ${relPath}:${args.line}:${args.character}`
const exists = await Bun.file(file).exists()
const exists = await Filesystem.exists(file)
if (!exists) {
throw new Error(`File not found: ${file}`)
}

View File

@@ -10,6 +10,7 @@ import DESCRIPTION from "./read.txt"
import { Instance } from "../project/instance"
import { assertExternalDirectory } from "./external-directory"
import { InstructionPrompt } from "../session/instruction"
import { Filesystem } from "../util/filesystem"
const DEFAULT_READ_LIMIT = 2000
const MAX_LINE_LENGTH = 2000
@@ -34,8 +35,7 @@ export const ReadTool = Tool.define("read", {
}
const title = path.relative(Instance.worktree, filepath)
const file = Bun.file(filepath)
const stat = await file.stat().catch(() => undefined)
const stat = Filesystem.stat(filepath)
await assertExternalDirectory(ctx, filepath, {
bypass: Boolean(ctx.extra?.["bypassCwdCheck"]),
@@ -118,11 +118,10 @@ export const ReadTool = Tool.define("read", {
const instructions = await InstructionPrompt.resolve(ctx.messages, filepath, ctx.messageID)
// Exclude SVG (XML-based) and vnd.fastbidsheet (.fbs extension, commonly FlatBuffers schema files)
const isImage =
file.type.startsWith("image/") && file.type !== "image/svg+xml" && file.type !== "image/vnd.fastbidsheet"
const isPdf = file.type === "application/pdf"
const mime = Filesystem.mimeType(filepath)
const isImage = mime.startsWith("image/") && mime !== "image/svg+xml" && mime !== "image/vnd.fastbidsheet"
const isPdf = mime === "application/pdf"
if (isImage || isPdf) {
const mime = file.type
const msg = `${isImage ? "Image" : "PDF"} read successfully`
return {
title,
@@ -136,13 +135,13 @@ export const ReadTool = Tool.define("read", {
{
type: "file",
mime,
url: `data:${mime};base64,${Buffer.from(await file.bytes()).toString("base64")}`,
url: `data:${mime};base64,${Buffer.from(await Filesystem.readBytes(filepath)).toString("base64")}`,
},
],
}
}
const isBinary = await isBinaryFile(filepath, stat.size)
const isBinary = await isBinaryFile(filepath, Number(stat.size))
if (isBinary) throw new Error(`Cannot read binary file: ${filepath}`)
const stream = createReadStream(filepath, { encoding: "utf8" })

View File

@@ -115,7 +115,7 @@ export namespace ToolRegistry {
ApplyPatchTool,
...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [LspTool] : []),
...(config.experimental?.batch_tool === true ? [BatchTool] : []),
...(Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE && Flag.OPENCODE_CLIENT === "cli" ? [PlanExitTool, PlanEnterTool] : []),
...(Flag.OPENCODE_CLIENT === "cli" ? [PlanExitTool, PlanEnterTool] : []),
...custom,
]
}

View File

@@ -5,6 +5,7 @@ import { Identifier } from "../id/id"
import { PermissionNext } from "../permission/next"
import type { Agent } from "../agent/agent"
import { Scheduler } from "../scheduler"
import { Filesystem } from "../util/filesystem"
export namespace Truncate {
export const MAX_LINES = 2000
@@ -91,7 +92,7 @@ export namespace Truncate {
const id = Identifier.ascending("tool")
const filepath = path.join(DIR, id)
await Bun.write(Bun.file(filepath), text)
await Filesystem.write(filepath, text)
const hint = hasTaskTool(agent)
? `The tool call succeeded but the output was truncated. Full output saved to: ${filepath}\nUse the Task tool to have explore agent process this file with Grep and Read (with offset/limit). Do NOT read the full file yourself - delegate to save context.`

View File

@@ -26,9 +26,8 @@ export const WriteTool = Tool.define("write", {
const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
await assertExternalDirectory(ctx, filepath)
const file = Bun.file(filepath)
const exists = await file.exists()
const contentOld = exists ? await file.text() : ""
const exists = await Filesystem.exists(filepath)
const contentOld = exists ? await Filesystem.readText(filepath) : ""
if (exists) await FileTime.assert(ctx.sessionID, filepath)
const diff = trimDiff(createTwoFilesPatch(filepath, filepath, contentOld, params.content))
@@ -42,7 +41,7 @@ export const WriteTool = Tool.define("write", {
},
})
await Bun.write(filepath, params.content)
await Filesystem.write(filepath, params.content)
await Bus.publish(File.Event.Edited, {
file: filepath,
})

View File

@@ -1,8 +1,10 @@
import { mkdir, readFile, writeFile } from "fs/promises"
import { existsSync, statSync } from "fs"
import { chmod, mkdir, readFile, writeFile } from "fs/promises"
import { createWriteStream, existsSync, statSync } from "fs"
import { lookup } from "mime-types"
import { realpathSync } from "fs"
import { dirname, join, relative } from "path"
import { Readable } from "stream"
import { pipeline } from "stream/promises"
export namespace Filesystem {
// Fast sync version for metadata checks
@@ -39,11 +41,16 @@ export namespace Filesystem {
return readFile(p)
}
export async function readArrayBuffer(p: string): Promise<ArrayBuffer> {
const buf = await readFile(p)
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer
}
function isEnoent(e: unknown): e is { code: "ENOENT" } {
return typeof e === "object" && e !== null && "code" in e && (e as { code: string }).code === "ENOENT"
}
export async function write(p: string, content: string | Buffer, mode?: number): Promise<void> {
export async function write(p: string, content: string | Buffer | Uint8Array, mode?: number): Promise<void> {
try {
if (mode) {
await writeFile(p, content, { mode })
@@ -68,6 +75,25 @@ export namespace Filesystem {
return write(p, JSON.stringify(data, null, 2), mode)
}
export async function writeStream(
p: string,
stream: ReadableStream<Uint8Array> | Readable,
mode?: number,
): Promise<void> {
const dir = dirname(p)
if (!existsSync(dir)) {
await mkdir(dir, { recursive: true })
}
const nodeStream = stream instanceof ReadableStream ? Readable.fromWeb(stream as any) : stream
const writeStream = createWriteStream(p)
await pipeline(nodeStream, writeStream)
if (mode) {
await chmod(p, mode)
}
}
export function mimeType(p: string): string {
return lookup(p) || "application/octet-stream"
}

View File

@@ -1,5 +1,6 @@
import path from "path"
import fs from "fs/promises"
import { createWriteStream } from "fs"
import { Global } from "../global"
import z from "zod"
@@ -63,13 +64,15 @@ export namespace Log {
Global.Path.log,
options.dev ? "dev.log" : new Date().toISOString().split(".")[0].replace(/:/g, "") + ".log",
)
const logfile = Bun.file(logpath)
await fs.truncate(logpath).catch(() => {})
const writer = logfile.writer()
const stream = createWriteStream(logpath, { flags: "a" })
write = async (msg: any) => {
const num = writer.write(msg)
writer.flush()
return num
return new Promise((resolve, reject) => {
stream.write(msg, (err) => {
if (err) reject(err)
else resolve(msg.length)
})
})
}
}

View File

@@ -38,7 +38,7 @@ test("build agent has correct default properties", async () => {
expect(build).toBeDefined()
expect(build?.mode).toBe("primary")
expect(build?.native).toBe(true)
expect(evalPerm(build, "edit")).toBe("allow")
expect(evalPerm(build, "edit")).toBe("ask")
expect(evalPerm(build, "bash")).toBe("allow")
},
})
@@ -217,8 +217,8 @@ test("agent permission config merges with defaults", async () => {
expect(build).toBeDefined()
// Specific pattern is denied
expect(PermissionNext.evaluate("bash", "rm -rf *", build!.permission).action).toBe("deny")
// Edit still allowed
expect(evalPerm(build, "edit")).toBe("allow")
// Edit still asks (default behavior)
expect(evalPerm(build, "edit")).toBe("ask")
},
})
})

View File

@@ -7,6 +7,7 @@ import path from "path"
import fs from "fs/promises"
import { pathToFileURL } from "url"
import { Global } from "../../src/global"
import { Filesystem } from "../../src/util/filesystem"
// Get managed config directory from environment (set in preload.ts)
const managedConfigDir = process.env.OPENCODE_TEST_MANAGED_CONFIG_DIR!
@@ -17,11 +18,11 @@ afterEach(async () => {
async function writeManagedSettings(settings: object, filename = "opencode.json") {
await fs.mkdir(managedConfigDir, { recursive: true })
await Bun.write(path.join(managedConfigDir, filename), JSON.stringify(settings))
await Filesystem.write(path.join(managedConfigDir, filename), JSON.stringify(settings))
}
async function writeConfig(dir: string, config: object, name = "opencode.json") {
await Bun.write(path.join(dir, name), JSON.stringify(config))
await Filesystem.write(path.join(dir, name), JSON.stringify(config))
}
test("loads config with defaults when no files exist", async () => {
@@ -58,7 +59,7 @@ test("loads JSON config file", async () => {
test("loads JSONC config file", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.jsonc"),
`{
// This is a comment
@@ -144,7 +145,7 @@ test("preserves env variables when adding $schema to config", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
// Config without $schema - should trigger auto-add
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
theme: "{env:PRESERVE_VAR}",
@@ -159,7 +160,7 @@ test("preserves env variables when adding $schema to config", async () => {
expect(config.theme).toBe("secret_value")
// Read the file to verify the env variable was preserved
const content = await Bun.file(path.join(tmp.path, "opencode.json")).text()
const content = await Filesystem.readText(path.join(tmp.path, "opencode.json"))
expect(content).toContain("{env:PRESERVE_VAR}")
expect(content).not.toContain("secret_value")
expect(content).toContain("$schema")
@@ -177,7 +178,7 @@ test("preserves env variables when adding $schema to config", async () => {
test("handles file inclusion substitution", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(path.join(dir, "included.txt"), "test_theme")
await Filesystem.write(path.join(dir, "included.txt"), "test_theme")
await writeConfig(dir, {
$schema: "https://opencode.ai/config.json",
theme: "{file:included.txt}",
@@ -196,7 +197,7 @@ test("handles file inclusion substitution", async () => {
test("handles file inclusion with replacement tokens", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(path.join(dir, "included.md"), "const out = await Bun.$`echo hi`")
await Filesystem.write(path.join(dir, "included.md"), "const out = await Bun.$`echo hi`")
await writeConfig(dir, {
$schema: "https://opencode.ai/config.json",
theme: "{file:included.md}",
@@ -233,7 +234,7 @@ test("validates config schema and throws on invalid fields", async () => {
test("throws error for invalid JSON", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(path.join(dir, "opencode.json"), "{ invalid json }")
await Filesystem.write(path.join(dir, "opencode.json"), "{ invalid json }")
},
})
await Instance.provide({
@@ -336,7 +337,7 @@ test("handles command configuration", async () => {
test("migrates autoshare to share field", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -358,7 +359,7 @@ test("migrates autoshare to share field", async () => {
test("migrates mode field to agent field", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -395,7 +396,7 @@ test("loads config from .opencode directory", async () => {
const agentDir = path.join(opencodeDir, "agent")
await fs.mkdir(agentDir, { recursive: true })
await Bun.write(
await Filesystem.write(
path.join(agentDir, "test.md"),
`---
model: test/model
@@ -428,7 +429,7 @@ test("loads agents from .opencode/agents (plural)", async () => {
const agentsDir = path.join(opencodeDir, "agents")
await fs.mkdir(path.join(agentsDir, "nested"), { recursive: true })
await Bun.write(
await Filesystem.write(
path.join(agentsDir, "helper.md"),
`---
model: test/model
@@ -437,7 +438,7 @@ mode: subagent
Helper agent prompt`,
)
await Bun.write(
await Filesystem.write(
path.join(agentsDir, "nested", "child.md"),
`---
model: test/model
@@ -479,7 +480,7 @@ test("loads commands from .opencode/command (singular)", async () => {
const commandDir = path.join(opencodeDir, "command")
await fs.mkdir(path.join(commandDir, "nested"), { recursive: true })
await Bun.write(
await Filesystem.write(
path.join(commandDir, "hello.md"),
`---
description: Test command
@@ -487,7 +488,7 @@ description: Test command
Hello from singular command`,
)
await Bun.write(
await Filesystem.write(
path.join(commandDir, "nested", "child.md"),
`---
description: Nested command
@@ -524,7 +525,7 @@ test("loads commands from .opencode/commands (plural)", async () => {
const commandsDir = path.join(opencodeDir, "commands")
await fs.mkdir(path.join(commandsDir, "nested"), { recursive: true })
await Bun.write(
await Filesystem.write(
path.join(commandsDir, "hello.md"),
`---
description: Test command
@@ -532,7 +533,7 @@ description: Test command
Hello from plural commands`,
)
await Bun.write(
await Filesystem.write(
path.join(commandsDir, "nested", "child.md"),
`---
description: Nested command
@@ -568,7 +569,7 @@ test("updates config and writes to file", async () => {
const newConfig = { model: "updated/model" }
await Config.update(newConfig as any)
const writtenConfig = JSON.parse(await Bun.file(path.join(tmp.path, "config.json")).text())
const writtenConfig = await Filesystem.readJson(path.join(tmp.path, "config.json"))
expect(writtenConfig.model).toBe("updated/model")
},
})
@@ -639,8 +640,8 @@ test("installs dependencies in writable OPENCODE_CONFIG_DIR", async () => {
},
})
expect(await Bun.file(path.join(tmp.extra, "package.json")).exists()).toBe(true)
expect(await Bun.file(path.join(tmp.extra, ".gitignore")).exists()).toBe(true)
expect(await Filesystem.exists(path.join(tmp.extra, "package.json"))).toBe(true)
expect(await Filesystem.exists(path.join(tmp.extra, ".gitignore"))).toBe(true)
} finally {
if (prev === undefined) delete process.env.OPENCODE_CONFIG_DIR
else process.env.OPENCODE_CONFIG_DIR = prev
@@ -653,12 +654,12 @@ test("resolves scoped npm plugins in config", async () => {
const pluginDir = path.join(dir, "node_modules", "@scope", "plugin")
await fs.mkdir(pluginDir, { recursive: true })
await Bun.write(
await Filesystem.write(
path.join(dir, "package.json"),
JSON.stringify({ name: "config-fixture", version: "1.0.0", type: "module" }, null, 2),
)
await Bun.write(
await Filesystem.write(
path.join(pluginDir, "package.json"),
JSON.stringify(
{
@@ -672,9 +673,9 @@ test("resolves scoped npm plugins in config", async () => {
),
)
await Bun.write(path.join(pluginDir, "index.js"), "export default {}\n")
await Filesystem.write(path.join(pluginDir, "index.js"), "export default {}\n")
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({ $schema: "https://opencode.ai/config.json", plugin: ["@scope/plugin"] }, null, 2),
)
@@ -708,7 +709,7 @@ test("merges plugin arrays from global and local configs", async () => {
await fs.mkdir(opencodeDir, { recursive: true })
// Global config with plugins
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -717,7 +718,7 @@ test("merges plugin arrays from global and local configs", async () => {
)
// Local .opencode config with different plugins
await Bun.write(
await Filesystem.write(
path.join(opencodeDir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -753,7 +754,7 @@ test("does not error when only custom agent is a subagent", async () => {
const agentDir = path.join(opencodeDir, "agent")
await fs.mkdir(agentDir, { recursive: true })
await Bun.write(
await Filesystem.write(
path.join(agentDir, "helper.md"),
`---
model: test/model
@@ -784,7 +785,7 @@ test("merges instructions arrays from global and local configs", async () => {
const opencodeDir = path.join(projectDir, ".opencode")
await fs.mkdir(opencodeDir, { recursive: true })
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -792,7 +793,7 @@ test("merges instructions arrays from global and local configs", async () => {
}),
)
await Bun.write(
await Filesystem.write(
path.join(opencodeDir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -823,7 +824,7 @@ test("deduplicates duplicate instructions from global and local configs", async
const opencodeDir = path.join(projectDir, ".opencode")
await fs.mkdir(opencodeDir, { recursive: true })
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -831,7 +832,7 @@ test("deduplicates duplicate instructions from global and local configs", async
}),
)
await Bun.write(
await Filesystem.write(
path.join(opencodeDir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -867,7 +868,7 @@ test("deduplicates duplicate plugins from global and local configs", async () =>
await fs.mkdir(opencodeDir, { recursive: true })
// Global config with plugins
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -876,7 +877,7 @@ test("deduplicates duplicate plugins from global and local configs", async () =>
)
// Local .opencode config with some overlapping plugins
await Bun.write(
await Filesystem.write(
path.join(opencodeDir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -915,7 +916,7 @@ test("deduplicates duplicate plugins from global and local configs", async () =>
test("migrates legacy tools config to permissions - allow", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -946,7 +947,7 @@ test("migrates legacy tools config to permissions - allow", async () => {
test("migrates legacy tools config to permissions - deny", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -977,7 +978,7 @@ test("migrates legacy tools config to permissions - deny", async () => {
test("migrates legacy write tool to edit permission", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -1086,7 +1087,7 @@ test("missing managed settings file is not an error", async () => {
test("migrates legacy edit tool to edit permission", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -1115,7 +1116,7 @@ test("migrates legacy edit tool to edit permission", async () => {
test("migrates legacy patch tool to edit permission", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -1144,7 +1145,7 @@ test("migrates legacy patch tool to edit permission", async () => {
test("migrates legacy multiedit tool to edit permission", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -1173,7 +1174,7 @@ test("migrates legacy multiedit tool to edit permission", async () => {
test("migrates mixed legacy tools config", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -1208,7 +1209,7 @@ test("migrates mixed legacy tools config", async () => {
test("merges legacy tools with existing permission config", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -1241,7 +1242,7 @@ test("merges legacy tools with existing permission config", async () => {
test("permission config preserves key order", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -1289,7 +1290,7 @@ test("project config can override MCP server enabled status", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
// Simulates a base config (like from remote .well-known) with disabled MCP
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.jsonc"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -1308,7 +1309,7 @@ test("project config can override MCP server enabled status", async () => {
}),
)
// Project config enables just jira
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -1347,7 +1348,7 @@ test("MCP config deep merges preserving base config properties", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
// Base config with full MCP definition
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.jsonc"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -1364,7 +1365,7 @@ test("MCP config deep merges preserving base config properties", async () => {
}),
)
// Override just enables it, should preserve other properties
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -1399,7 +1400,7 @@ test("local .opencode config can override MCP from project config", async () =>
await using tmp = await tmpdir({
init: async (dir) => {
// Project config with disabled MCP
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -1415,7 +1416,7 @@ test("local .opencode config can override MCP from project config", async () =>
// Local .opencode directory config enables it
const opencodeDir = path.join(dir, ".opencode")
await fs.mkdir(opencodeDir, { recursive: true })
await Bun.write(
await Filesystem.write(
path.join(opencodeDir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -1483,7 +1484,7 @@ test("project config overrides remote well-known config", async () => {
git: true,
init: async (dir) => {
// Project config enables jira (overriding remote default)
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -1576,7 +1577,7 @@ describe("deduplicatePlugins", () => {
const pluginDir = path.join(opencodeDir, "plugin")
await fs.mkdir(pluginDir, { recursive: true })
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -1584,7 +1585,7 @@ describe("deduplicatePlugins", () => {
}),
)
await Bun.write(path.join(pluginDir, "my-plugin.js"), "export default {}")
await Filesystem.write(path.join(pluginDir, "my-plugin.js"), "export default {}")
},
})
@@ -1611,7 +1612,7 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => {
await using tmp = await tmpdir({
init: async (dir) => {
// Create a project config that would normally be loaded
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -1649,7 +1650,7 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => {
// Create a .opencode directory with a command
const opencodeDir = path.join(dir, ".opencode", "command")
await fs.mkdir(opencodeDir, { recursive: true })
await Bun.write(path.join(opencodeDir, "test-cmd.md"), "# Test Command\nThis is a test command.")
await Filesystem.write(path.join(opencodeDir, "test-cmd.md"), "# Test Command\nThis is a test command.")
},
})
await Instance.provide({
@@ -1706,7 +1707,7 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => {
await using tmp = await tmpdir({
init: async (dir) => {
// Create a config with relative instruction path
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -1714,7 +1715,7 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => {
}),
)
// Create the instruction file (should be skipped)
await Bun.write(path.join(dir, "CUSTOM.md"), "# Custom Instructions")
await Filesystem.write(path.join(dir, "CUSTOM.md"), "# Custom Instructions")
},
})
@@ -1752,7 +1753,7 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => {
await using configDirTmp = await tmpdir({
init: async (dir) => {
// Create config in the custom config dir
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -1765,7 +1766,7 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => {
await using projectTmp = await tmpdir({
init: async (dir) => {
// Create config in project (should be ignored)
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -1800,3 +1801,66 @@ describe("OPENCODE_DISABLE_PROJECT_CONFIG", () => {
}
})
})
describe("OPENCODE_CONFIG_CONTENT token substitution", () => {
test("substitutes {env:} tokens in OPENCODE_CONFIG_CONTENT", async () => {
const originalEnv = process.env["OPENCODE_CONFIG_CONTENT"]
const originalTestVar = process.env["TEST_CONFIG_VAR"]
process.env["TEST_CONFIG_VAR"] = "test_api_key_12345"
process.env["OPENCODE_CONFIG_CONTENT"] = JSON.stringify({
$schema: "https://opencode.ai/config.json",
theme: "{env:TEST_CONFIG_VAR}",
})
try {
await using tmp = await tmpdir()
await Instance.provide({
directory: tmp.path,
fn: async () => {
const config = await Config.get()
expect(config.theme).toBe("test_api_key_12345")
},
})
} finally {
if (originalEnv !== undefined) {
process.env["OPENCODE_CONFIG_CONTENT"] = originalEnv
} else {
delete process.env["OPENCODE_CONFIG_CONTENT"]
}
if (originalTestVar !== undefined) {
process.env["TEST_CONFIG_VAR"] = originalTestVar
} else {
delete process.env["TEST_CONFIG_VAR"]
}
}
})
test("substitutes {file:} tokens in OPENCODE_CONFIG_CONTENT", async () => {
const originalEnv = process.env["OPENCODE_CONFIG_CONTENT"]
try {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(path.join(dir, "api_key.txt"), "secret_key_from_file")
process.env["OPENCODE_CONFIG_CONTENT"] = JSON.stringify({
$schema: "https://opencode.ai/config.json",
theme: "{file:./api_key.txt}",
})
},
})
await Instance.provide({
directory: tmp.path,
fn: async () => {
const config = await Config.get()
expect(config.theme).toBe("secret_key_from_file")
},
})
} finally {
if (originalEnv !== undefined) {
process.env["OPENCODE_CONFIG_CONTENT"] = originalEnv
} else {
delete process.env["OPENCODE_CONFIG_CONTENT"]
}
}
})
})

View File

@@ -3,11 +3,12 @@ import path from "path"
import fs from "fs/promises"
import { File } from "../../src/file"
import { Instance } from "../../src/project/instance"
import { Filesystem } from "../../src/util/filesystem"
import { tmpdir } from "../fixture/fixture"
describe("file/index Bun.file patterns", () => {
describe("file/index Filesystem patterns", () => {
describe("File.read() - text content", () => {
test("reads text file via Bun.file().text()", async () => {
test("reads text file via Filesystem.readText()", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "test.txt")
await fs.writeFile(filepath, "Hello World", "utf-8")
@@ -22,7 +23,7 @@ describe("file/index Bun.file patterns", () => {
})
})
test("reads with Bun.file().exists() check", async () => {
test("reads with Filesystem.exists() check", async () => {
await using tmp = await tmpdir()
await Instance.provide({
@@ -81,7 +82,7 @@ describe("file/index Bun.file patterns", () => {
})
describe("File.read() - binary content", () => {
test("reads binary file via Bun.file().arrayBuffer()", async () => {
test("reads binary file via Filesystem.readArrayBuffer()", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "image.png")
const binaryContent = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
@@ -115,8 +116,8 @@ describe("file/index Bun.file patterns", () => {
})
})
describe("File.read() - Bun.file().type", () => {
test("detects MIME type via Bun.file().type", async () => {
describe("File.read() - Filesystem.mimeType()", () => {
test("detects MIME type via Filesystem.mimeType()", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "test.json")
await fs.writeFile(filepath, '{"key": "value"}', "utf-8")
@@ -124,8 +125,7 @@ describe("file/index Bun.file patterns", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
const bunFile = Bun.file(filepath)
expect(bunFile.type).toContain("application/json")
expect(Filesystem.mimeType(filepath)).toContain("application/json")
const result = await File.read("test.json")
expect(result.type).toBe("text")
@@ -149,16 +149,15 @@ describe("file/index Bun.file patterns", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
const bunFile = Bun.file(filepath)
expect(bunFile.type).toContain(mime)
expect(Filesystem.mimeType(filepath)).toContain(mime)
},
})
}
})
})
describe("File.list() - Bun.file().exists() and .text()", () => {
test("reads .gitignore via Bun.file().exists() and .text()", async () => {
describe("File.list() - Filesystem.exists() and readText()", () => {
test("reads .gitignore via Filesystem.exists() and readText()", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
@@ -168,10 +167,9 @@ describe("file/index Bun.file patterns", () => {
await fs.writeFile(gitignorePath, "node_modules\ndist\n", "utf-8")
// This is used internally in File.list()
const bunFile = Bun.file(gitignorePath)
expect(await bunFile.exists()).toBe(true)
expect(await Filesystem.exists(gitignorePath)).toBe(true)
const content = await bunFile.text()
const content = await Filesystem.readText(gitignorePath)
expect(content).toContain("node_modules")
},
})
@@ -186,9 +184,8 @@ describe("file/index Bun.file patterns", () => {
const ignorePath = path.join(tmp.path, ".ignore")
await fs.writeFile(ignorePath, "*.log\n.env\n", "utf-8")
const bunFile = Bun.file(ignorePath)
expect(await bunFile.exists()).toBe(true)
expect(await bunFile.text()).toContain("*.log")
expect(await Filesystem.exists(ignorePath)).toBe(true)
expect(await Filesystem.readText(ignorePath)).toContain("*.log")
},
})
})
@@ -200,8 +197,7 @@ describe("file/index Bun.file patterns", () => {
directory: tmp.path,
fn: async () => {
const gitignorePath = path.join(tmp.path, ".gitignore")
const bunFile = Bun.file(gitignorePath)
expect(await bunFile.exists()).toBe(false)
expect(await Filesystem.exists(gitignorePath)).toBe(false)
// File.list() should still work
const nodes = await File.list()
@@ -211,8 +207,8 @@ describe("file/index Bun.file patterns", () => {
})
})
describe("File.changed() - Bun.file().text() for untracked files", () => {
test("reads untracked files via Bun.file().text()", async () => {
describe("File.changed() - Filesystem.readText() for untracked files", () => {
test("reads untracked files via Filesystem.readText()", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
@@ -222,8 +218,7 @@ describe("file/index Bun.file patterns", () => {
await fs.writeFile(untrackedPath, "new content\nwith multiple lines", "utf-8")
// This is how File.changed() reads untracked files
const bunFile = Bun.file(untrackedPath)
const content = await bunFile.text()
const content = await Filesystem.readText(untrackedPath)
const lines = content.split("\n").length
expect(lines).toBe(2)
},
@@ -232,7 +227,7 @@ describe("file/index Bun.file patterns", () => {
})
describe("Error handling", () => {
test("handles errors gracefully in Bun.file().text()", async () => {
test("handles errors gracefully in Filesystem.readText()", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "readonly.txt")
await fs.writeFile(filepath, "content", "utf-8")
@@ -240,9 +235,9 @@ describe("file/index Bun.file patterns", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
const nonExistentFile = Bun.file(path.join(tmp.path, "does-not-exist.txt"))
// Bun.file().text() on non-existent file throws
await expect(nonExistentFile.text()).rejects.toThrow()
const nonExistentPath = path.join(tmp.path, "does-not-exist.txt")
// Filesystem.readText() on non-existent file throws
await expect(Filesystem.readText(nonExistentPath)).rejects.toThrow()
// But File.read() handles this gracefully
const result = await File.read("does-not-exist.txt")
@@ -251,14 +246,14 @@ describe("file/index Bun.file patterns", () => {
})
})
test("handles errors in Bun.file().arrayBuffer()", async () => {
test("handles errors in Filesystem.readArrayBuffer()", async () => {
await using tmp = await tmpdir()
await Instance.provide({
directory: tmp.path,
fn: async () => {
const nonExistentFile = Bun.file(path.join(tmp.path, "does-not-exist.bin"))
const buffer = await nonExistentFile.arrayBuffer().catch(() => new ArrayBuffer(0))
const nonExistentPath = path.join(tmp.path, "does-not-exist.bin")
const buffer = await Filesystem.readArrayBuffer(nonExistentPath).catch(() => new ArrayBuffer(0))
expect(buffer.byteLength).toBe(0)
},
})
@@ -272,7 +267,6 @@ describe("file/index Bun.file patterns", () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
const bunFile = Bun.file(filepath)
// File.read() handles missing images gracefully
const result = await File.read("broken.png")
expect(result.type).toBe("text")

View File

@@ -3,6 +3,7 @@ import path from "path"
import fs from "fs/promises"
import { FileTime } from "../../src/file/time"
import { Instance } from "../../src/project/instance"
import { Filesystem } from "../../src/util/filesystem"
import { tmpdir } from "../fixture/fixture"
describe("file/time", () => {
@@ -312,8 +313,8 @@ describe("file/time", () => {
})
})
describe("stat() Bun.file pattern", () => {
test("reads file modification time via Bun.file().stat()", async () => {
describe("stat() Filesystem.stat pattern", () => {
test("reads file modification time via Filesystem.stat()", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "file.txt")
await fs.writeFile(filepath, "content", "utf-8")
@@ -323,9 +324,9 @@ describe("file/time", () => {
fn: async () => {
FileTime.read(sessionID, filepath)
const stats = await Bun.file(filepath).stat()
expect(stats.mtime).toBeInstanceOf(Date)
expect(stats.mtime.getTime()).toBeGreaterThan(0)
const stats = Filesystem.stat(filepath)
expect(stats?.mtime).toBeInstanceOf(Date)
expect(stats!.mtime.getTime()).toBeGreaterThan(0)
// FileTime.assert uses this stat internally
await FileTime.assert(sessionID, filepath)
@@ -343,14 +344,14 @@ describe("file/time", () => {
fn: async () => {
FileTime.read(sessionID, filepath)
const originalStat = await Bun.file(filepath).stat()
const originalStat = Filesystem.stat(filepath)
// Wait and modify
await new Promise((resolve) => setTimeout(resolve, 100))
await fs.writeFile(filepath, "modified", "utf-8")
const newStat = await Bun.file(filepath).stat()
expect(newStat.mtime.getTime()).toBeGreaterThan(originalStat.mtime.getTime())
const newStat = Filesystem.stat(filepath)
expect(newStat!.mtime.getTime()).toBeGreaterThan(originalStat!.mtime.getTime())
await expect(FileTime.assert(sessionID, filepath)).rejects.toThrow()
},

View File

@@ -4,6 +4,7 @@ import { Log } from "../../src/util/log"
import { $ } from "bun"
import path from "path"
import { tmpdir } from "../fixture/fixture"
import { Filesystem } from "../../src/util/filesystem"
import { GlobalBus } from "../../src/bus/global"
Log.init({ print: false })
@@ -78,7 +79,7 @@ describe("Project.fromDirectory", () => {
expect(project.worktree).toBe(tmp.path)
const opencodeFile = path.join(tmp.path, ".git", "opencode")
const fileExists = await Bun.file(opencodeFile).exists()
const fileExists = await Filesystem.exists(opencodeFile)
expect(fileExists).toBe(false)
})
@@ -94,7 +95,7 @@ describe("Project.fromDirectory", () => {
expect(project.worktree).toBe(tmp.path)
const opencodeFile = path.join(tmp.path, ".git", "opencode")
const fileExists = await Bun.file(opencodeFile).exists()
const fileExists = await Filesystem.exists(opencodeFile)
expect(fileExists).toBe(true)
})

View File

@@ -4,6 +4,7 @@ import fs from "fs/promises"
import path from "path"
import { Instance } from "../../src/project/instance"
import { Worktree } from "../../src/worktree"
import { Filesystem } from "../../src/util/filesystem"
import { tmpdir } from "../fixture/fixture"
describe("Worktree.remove", () => {
@@ -53,7 +54,7 @@ describe("Worktree.remove", () => {
})()
expect(ok).toBe(true)
expect(await Bun.file(dir).exists()).toBe(false)
expect(await Filesystem.exists(dir)).toBe(false)
const list = await $`git worktree list --porcelain`.cwd(root).quiet().text()
expect(list).not.toContain(`worktree ${dir}`)

View File

@@ -7,11 +7,12 @@ import { Instance } from "../../src/project/instance"
import { Provider } from "../../src/provider/provider"
import { Env } from "../../src/env"
import { Global } from "../../src/global"
import { Filesystem } from "../../src/util/filesystem"
test("Bedrock: config region takes precedence over AWS_REGION env var", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -43,7 +44,7 @@ test("Bedrock: config region takes precedence over AWS_REGION env var", async ()
test("Bedrock: falls back to AWS_REGION env var when no config region", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -68,7 +69,7 @@ test("Bedrock: falls back to AWS_REGION env var when no config region", async ()
test("Bedrock: loads when bearer token from auth.json is present", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -89,14 +90,14 @@ test("Bedrock: loads when bearer token from auth.json is present", async () => {
// Save original auth.json if it exists
let originalAuth: string | undefined
try {
originalAuth = await Bun.file(authPath).text()
originalAuth = await Filesystem.readText(authPath)
} catch {
// File doesn't exist, that's fine
}
try {
// Write test auth.json
await Bun.write(
await Filesystem.write(
authPath,
JSON.stringify({
"amazon-bedrock": {
@@ -122,7 +123,7 @@ test("Bedrock: loads when bearer token from auth.json is present", async () => {
} finally {
// Restore original or delete
if (originalAuth !== undefined) {
await Bun.write(authPath, originalAuth)
await Filesystem.write(authPath, originalAuth)
} else {
try {
await unlink(authPath)
@@ -136,7 +137,7 @@ test("Bedrock: loads when bearer token from auth.json is present", async () => {
test("Bedrock: config profile takes precedence over AWS_PROFILE env var", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -169,7 +170,7 @@ test("Bedrock: config profile takes precedence over AWS_PROFILE env var", async
test("Bedrock: includes custom endpoint in options when specified", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -202,7 +203,7 @@ test("Bedrock: includes custom endpoint in options when specified", async () =>
test("Bedrock: autoloads when AWS_WEB_IDENTITY_TOKEN_FILE is present", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -240,7 +241,7 @@ test("Bedrock: autoloads when AWS_WEB_IDENTITY_TOKEN_FILE is present", async ()
test("Bedrock: model with us. prefix should not be double-prefixed", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -277,7 +278,7 @@ test("Bedrock: model with us. prefix should not be double-prefixed", async () =>
test("Bedrock: model with global. prefix should not be prefixed", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -313,7 +314,7 @@ test("Bedrock: model with global. prefix should not be prefixed", async () => {
test("Bedrock: model with eu. prefix should not be double-prefixed", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
@@ -349,7 +350,7 @@ test("Bedrock: model with eu. prefix should not be double-prefixed", async () =>
test("Bedrock: model without prefix in US region should get us. prefix added", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
await Filesystem.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",

View File

@@ -7,6 +7,7 @@ import { Instance } from "../../src/project/instance"
import { Provider } from "../../src/provider/provider"
import { ProviderTransform } from "../../src/provider/transform"
import { ModelsDev } from "../../src/provider/models"
import { Filesystem } from "../../src/util/filesystem"
import { tmpdir } from "../fixture/fixture"
import type { Agent } from "../../src/agent/agent"
import type { MessageV2 } from "../../src/session/message-v2"
@@ -185,7 +186,7 @@ function createChatStream(text: string) {
async function loadFixture(providerID: string, modelID: string) {
const fixturePath = path.join(import.meta.dir, "../tool/fixtures/models-api.json")
const data = (await Bun.file(fixturePath).json()) as Record<string, ModelsDev.Provider>
const data = await Filesystem.readJson<Record<string, ModelsDev.Provider>>(fixturePath)
const provider = data[providerID]
if (!provider) {
throw new Error(`Missing provider in fixture: ${providerID}`)

View File

@@ -1,5 +1,6 @@
import { describe, test, expect } from "bun:test"
import { Discovery } from "../../src/skill/discovery"
import { Filesystem } from "../../src/util/filesystem"
import path from "path"
const CLOUDFLARE_SKILLS_URL = "https://developers.cloudflare.com/.well-known/skills/"
@@ -11,7 +12,7 @@ describe("Discovery.pull", () => {
for (const dir of dirs) {
expect(dir).toStartWith(Discovery.dir())
const md = path.join(dir, "SKILL.md")
expect(await Bun.file(md).exists()).toBe(true)
expect(await Filesystem.exists(md)).toBe(true)
}
}, 30_000)
@@ -20,7 +21,7 @@ describe("Discovery.pull", () => {
expect(dirs.length).toBeGreaterThan(0)
for (const dir of dirs) {
const md = path.join(dir, "SKILL.md")
expect(await Bun.file(md).exists()).toBe(true)
expect(await Filesystem.exists(md)).toBe(true)
}
}, 30_000)
@@ -40,7 +41,7 @@ describe("Discovery.pull", () => {
const agentsSdk = dirs.find((d) => d.endsWith("/agents-sdk"))
if (agentsSdk) {
const refs = path.join(agentsSdk, "references")
expect(await Bun.file(path.join(agentsSdk, "SKILL.md")).exists()).toBe(true)
expect(await Filesystem.exists(path.join(agentsSdk, "SKILL.md"))).toBe(true)
// agents-sdk has reference files per the index
const refDir = await Array.fromAsync(new Bun.Glob("**/*.md").scan({ cwd: refs, onlyFiles: true }))
expect(refDir.length).toBeGreaterThan(0)

View File

@@ -1,7 +1,9 @@
import { test, expect } from "bun:test"
import { $ } from "bun"
import fs from "fs/promises"
import { Snapshot } from "../../src/snapshot"
import { Instance } from "../../src/project/instance"
import { Filesystem } from "../../src/util/filesystem"
import { tmpdir } from "../fixture/fixture"
async function bootstrap() {
@@ -11,8 +13,8 @@ async function bootstrap() {
const unique = Math.random().toString(36).slice(2)
const aContent = `A${unique}`
const bContent = `B${unique}`
await Bun.write(`${dir}/a.txt`, aContent)
await Bun.write(`${dir}/b.txt`, bContent)
await Filesystem.write(`${dir}/a.txt`, aContent)
await Filesystem.write(`${dir}/b.txt`, bContent)
await $`git add .`.cwd(dir).quiet()
await $`git commit --no-gpg-sign -m init`.cwd(dir).quiet()
return {
@@ -46,11 +48,16 @@ test("revert should remove new files", async () => {
const before = await Snapshot.track()
expect(before).toBeTruthy()
await Bun.write(`${tmp.path}/new.txt`, "NEW")
await Filesystem.write(`${tmp.path}/new.txt`, "NEW")
await Snapshot.revert([await Snapshot.patch(before!)])
expect(await Bun.file(`${tmp.path}/new.txt`).exists()).toBe(false)
expect(
await fs
.access(`${tmp.path}/new.txt`)
.then(() => true)
.catch(() => false),
).toBe(false)
},
})
})
@@ -64,11 +71,16 @@ test("revert in subdirectory", async () => {
expect(before).toBeTruthy()
await $`mkdir -p ${tmp.path}/sub`.quiet()
await Bun.write(`${tmp.path}/sub/file.txt`, "SUB")
await Filesystem.write(`${tmp.path}/sub/file.txt`, "SUB")
await Snapshot.revert([await Snapshot.patch(before!)])
expect(await Bun.file(`${tmp.path}/sub/file.txt`).exists()).toBe(false)
expect(
await fs
.access(`${tmp.path}/sub/file.txt`)
.then(() => true)
.catch(() => false),
).toBe(false)
// Note: revert currently only removes files, not directories
// The empty subdirectory will remain
},
@@ -84,18 +96,23 @@ test("multiple file operations", async () => {
expect(before).toBeTruthy()
await $`rm ${tmp.path}/a.txt`.quiet()
await Bun.write(`${tmp.path}/c.txt`, "C")
await Filesystem.write(`${tmp.path}/c.txt`, "C")
await $`mkdir -p ${tmp.path}/dir`.quiet()
await Bun.write(`${tmp.path}/dir/d.txt`, "D")
await Bun.write(`${tmp.path}/b.txt`, "MODIFIED")
await Filesystem.write(`${tmp.path}/dir/d.txt`, "D")
await Filesystem.write(`${tmp.path}/b.txt`, "MODIFIED")
await Snapshot.revert([await Snapshot.patch(before!)])
expect(await Bun.file(`${tmp.path}/a.txt`).text()).toBe(tmp.extra.aContent)
expect(await Bun.file(`${tmp.path}/c.txt`).exists()).toBe(false)
expect(await fs.readFile(`${tmp.path}/a.txt`, "utf-8")).toBe(tmp.extra.aContent)
expect(
await fs
.access(`${tmp.path}/c.txt`)
.then(() => true)
.catch(() => false),
).toBe(false)
// Note: revert currently only removes files, not directories
// The empty directory will remain
expect(await Bun.file(`${tmp.path}/b.txt`).text()).toBe(tmp.extra.bContent)
expect(await fs.readFile(`${tmp.path}/b.txt`, "utf-8")).toBe(tmp.extra.bContent)
},
})
})
@@ -123,13 +140,18 @@ test("binary file handling", async () => {
const before = await Snapshot.track()
expect(before).toBeTruthy()
await Bun.write(`${tmp.path}/image.png`, new Uint8Array([0x89, 0x50, 0x4e, 0x47]))
await Filesystem.write(`${tmp.path}/image.png`, new Uint8Array([0x89, 0x50, 0x4e, 0x47]))
const patch = await Snapshot.patch(before!)
expect(patch.files).toContain(`${tmp.path}/image.png`)
await Snapshot.revert([patch])
expect(await Bun.file(`${tmp.path}/image.png`).exists()).toBe(false)
expect(
await fs
.access(`${tmp.path}/image.png`)
.then(() => true)
.catch(() => false),
).toBe(false)
},
})
})
@@ -157,7 +179,7 @@ test("large file handling", async () => {
const before = await Snapshot.track()
expect(before).toBeTruthy()
await Bun.write(`${tmp.path}/large.txt`, "x".repeat(1024 * 1024))
await Filesystem.write(`${tmp.path}/large.txt`, "x".repeat(1024 * 1024))
expect((await Snapshot.patch(before!)).files).toContain(`${tmp.path}/large.txt`)
},
@@ -173,11 +195,16 @@ test("nested directory revert", async () => {
expect(before).toBeTruthy()
await $`mkdir -p ${tmp.path}/level1/level2/level3`.quiet()
await Bun.write(`${tmp.path}/level1/level2/level3/deep.txt`, "DEEP")
await Filesystem.write(`${tmp.path}/level1/level2/level3/deep.txt`, "DEEP")
await Snapshot.revert([await Snapshot.patch(before!)])
expect(await Bun.file(`${tmp.path}/level1/level2/level3/deep.txt`).exists()).toBe(false)
expect(
await fs
.access(`${tmp.path}/level1/level2/level3/deep.txt`)
.then(() => true)
.catch(() => false),
).toBe(false)
},
})
})
@@ -190,9 +217,9 @@ test("special characters in filenames", async () => {
const before = await Snapshot.track()
expect(before).toBeTruthy()
await Bun.write(`${tmp.path}/file with spaces.txt`, "SPACES")
await Bun.write(`${tmp.path}/file-with-dashes.txt`, "DASHES")
await Bun.write(`${tmp.path}/file_with_underscores.txt`, "UNDERSCORES")
await Filesystem.write(`${tmp.path}/file with spaces.txt`, "SPACES")
await Filesystem.write(`${tmp.path}/file-with-dashes.txt`, "DASHES")
await Filesystem.write(`${tmp.path}/file_with_underscores.txt`, "UNDERSCORES")
const files = (await Snapshot.patch(before!)).files
expect(files).toContain(`${tmp.path}/file with spaces.txt`)
@@ -225,7 +252,7 @@ test("patch with invalid hash", async () => {
expect(before).toBeTruthy()
// Create a change
await Bun.write(`${tmp.path}/test.txt`, "TEST")
await Filesystem.write(`${tmp.path}/test.txt`, "TEST")
// Try to patch with invalid hash - should handle gracefully
const patch = await Snapshot.patch("invalid-hash-12345")
@@ -273,7 +300,7 @@ test("unicode filenames", async () => {
]
for (const file of unicodeFiles) {
await Bun.write(file.path, file.content)
await Filesystem.write(file.path, file.content)
}
const patch = await Snapshot.patch(before!)
@@ -286,7 +313,12 @@ test("unicode filenames", async () => {
await Snapshot.revert([patch])
for (const file of unicodeFiles) {
expect(await Bun.file(file.path).exists()).toBe(false)
expect(
await fs
.access(file.path)
.then(() => true)
.catch(() => false),
).toBe(false)
}
},
})
@@ -300,14 +332,14 @@ test.skip("unicode filenames modification and restore", async () => {
const chineseFile = `${tmp.path}/文件.txt`
const cyrillicFile = `${tmp.path}/файл.txt`
await Bun.write(chineseFile, "original chinese")
await Bun.write(cyrillicFile, "original cyrillic")
await Filesystem.write(chineseFile, "original chinese")
await Filesystem.write(cyrillicFile, "original cyrillic")
const before = await Snapshot.track()
expect(before).toBeTruthy()
await Bun.write(chineseFile, "modified chinese")
await Bun.write(cyrillicFile, "modified cyrillic")
await Filesystem.write(chineseFile, "modified chinese")
await Filesystem.write(cyrillicFile, "modified cyrillic")
const patch = await Snapshot.patch(before!)
expect(patch.files).toContain(chineseFile)
@@ -315,8 +347,8 @@ test.skip("unicode filenames modification and restore", async () => {
await Snapshot.revert([patch])
expect(await Bun.file(chineseFile).text()).toBe("original chinese")
expect(await Bun.file(cyrillicFile).text()).toBe("original cyrillic")
expect(await fs.readFile(chineseFile, "utf-8")).toBe("original chinese")
expect(await fs.readFile(cyrillicFile, "utf-8")).toBe("original cyrillic")
},
})
})
@@ -331,13 +363,18 @@ test("unicode filenames in subdirectories", async () => {
await $`mkdir -p "${tmp.path}/目录/подкаталог"`.quiet()
const deepFile = `${tmp.path}/目录/подкаталог/文件.txt`
await Bun.write(deepFile, "deep unicode content")
await Filesystem.write(deepFile, "deep unicode content")
const patch = await Snapshot.patch(before!)
expect(patch.files).toContain(deepFile)
await Snapshot.revert([patch])
expect(await Bun.file(deepFile).exists()).toBe(false)
expect(
await fs
.access(deepFile)
.then(() => true)
.catch(() => false),
).toBe(false)
},
})
})
@@ -353,13 +390,18 @@ test("very long filenames", async () => {
const longName = "a".repeat(200) + ".txt"
const longFile = `${tmp.path}/${longName}`
await Bun.write(longFile, "long filename content")
await Filesystem.write(longFile, "long filename content")
const patch = await Snapshot.patch(before!)
expect(patch.files).toContain(longFile)
await Snapshot.revert([patch])
expect(await Bun.file(longFile).exists()).toBe(false)
expect(
await fs
.access(longFile)
.then(() => true)
.catch(() => false),
).toBe(false)
},
})
})
@@ -372,9 +414,9 @@ test("hidden files", async () => {
const before = await Snapshot.track()
expect(before).toBeTruthy()
await Bun.write(`${tmp.path}/.hidden`, "hidden content")
await Bun.write(`${tmp.path}/.gitignore`, "*.log")
await Bun.write(`${tmp.path}/.config`, "config content")
await Filesystem.write(`${tmp.path}/.hidden`, "hidden content")
await Filesystem.write(`${tmp.path}/.gitignore`, "*.log")
await Filesystem.write(`${tmp.path}/.config`, "config content")
const patch = await Snapshot.patch(before!)
expect(patch.files).toContain(`${tmp.path}/.hidden`)
@@ -393,7 +435,7 @@ test("nested symlinks", async () => {
expect(before).toBeTruthy()
await $`mkdir -p ${tmp.path}/sub/dir`.quiet()
await Bun.write(`${tmp.path}/sub/dir/target.txt`, "target content")
await Filesystem.write(`${tmp.path}/sub/dir/target.txt`, "target content")
await $`ln -s ${tmp.path}/sub/dir/target.txt ${tmp.path}/sub/dir/link.txt`.quiet()
await $`ln -s ${tmp.path}/sub ${tmp.path}/sub-link`.quiet()
@@ -450,9 +492,9 @@ test("gitignore changes", async () => {
const before = await Snapshot.track()
expect(before).toBeTruthy()
await Bun.write(`${tmp.path}/.gitignore`, "*.ignored")
await Bun.write(`${tmp.path}/test.ignored`, "ignored content")
await Bun.write(`${tmp.path}/normal.txt`, "normal content")
await Filesystem.write(`${tmp.path}/.gitignore`, "*.ignored")
await Filesystem.write(`${tmp.path}/test.ignored`, "ignored content")
await Filesystem.write(`${tmp.path}/normal.txt`, "normal content")
const patch = await Snapshot.patch(before!)
@@ -477,7 +519,7 @@ test("concurrent file operations during patch", async () => {
// Start creating files
const createPromise = (async () => {
for (let i = 0; i < 10; i++) {
await Bun.write(`${tmp.path}/concurrent${i}.txt`, `concurrent${i}`)
await Filesystem.write(`${tmp.path}/concurrent${i}.txt`, `concurrent${i}`)
// Small delay to simulate concurrent operations
await new Promise((resolve) => setTimeout(resolve, 1))
}
@@ -504,7 +546,7 @@ test("snapshot state isolation between projects", async () => {
directory: tmp1.path,
fn: async () => {
const before1 = await Snapshot.track()
await Bun.write(`${tmp1.path}/project1.txt`, "project1 content")
await Filesystem.write(`${tmp1.path}/project1.txt`, "project1 content")
const patch1 = await Snapshot.patch(before1!)
expect(patch1.files).toContain(`${tmp1.path}/project1.txt`)
},
@@ -514,7 +556,7 @@ test("snapshot state isolation between projects", async () => {
directory: tmp2.path,
fn: async () => {
const before2 = await Snapshot.track()
await Bun.write(`${tmp2.path}/project2.txt`, "project2 content")
await Filesystem.write(`${tmp2.path}/project2.txt`, "project2 content")
const patch2 = await Snapshot.patch(before2!)
expect(patch2.files).toContain(`${tmp2.path}/project2.txt`)
@@ -544,7 +586,7 @@ test("patch detects changes in secondary worktree", async () => {
expect(before).toBeTruthy()
const worktreeFile = `${worktreePath}/worktree.txt`
await Bun.write(worktreeFile, "worktree content")
await Filesystem.write(worktreeFile, "worktree content")
const patch = await Snapshot.patch(before!)
expect(patch.files).toContain(worktreeFile)
@@ -569,7 +611,7 @@ test("revert only removes files in invoking worktree", async () => {
},
})
const primaryFile = `${tmp.path}/worktree.txt`
await Bun.write(primaryFile, "primary content")
await Filesystem.write(primaryFile, "primary content")
await Instance.provide({
directory: worktreePath,
@@ -578,16 +620,21 @@ test("revert only removes files in invoking worktree", async () => {
expect(before).toBeTruthy()
const worktreeFile = `${worktreePath}/worktree.txt`
await Bun.write(worktreeFile, "worktree content")
await Filesystem.write(worktreeFile, "worktree content")
const patch = await Snapshot.patch(before!)
await Snapshot.revert([patch])
expect(await Bun.file(worktreeFile).exists()).toBe(false)
expect(
await fs
.access(worktreeFile)
.then(() => true)
.catch(() => false),
).toBe(false)
},
})
expect(await Bun.file(primaryFile).text()).toBe("primary content")
expect(await fs.readFile(primaryFile, "utf-8")).toBe("primary content")
} finally {
await $`git worktree remove --force ${worktreePath}`.cwd(tmp.path).quiet().nothrow()
await $`rm -rf ${worktreePath}`.quiet()
@@ -614,10 +661,10 @@ test("diff reports worktree-only/shared edits and ignores primary-only", async (
const before = await Snapshot.track()
expect(before).toBeTruthy()
await Bun.write(`${worktreePath}/worktree-only.txt`, "worktree diff content")
await Bun.write(`${worktreePath}/shared.txt`, "worktree edit")
await Bun.write(`${tmp.path}/shared.txt`, "primary edit")
await Bun.write(`${tmp.path}/primary-only.txt`, "primary change")
await Filesystem.write(`${worktreePath}/worktree-only.txt`, "worktree diff content")
await Filesystem.write(`${worktreePath}/shared.txt`, "worktree edit")
await Filesystem.write(`${tmp.path}/shared.txt`, "primary edit")
await Filesystem.write(`${tmp.path}/primary-only.txt`, "primary change")
const diff = await Snapshot.diff(before!)
expect(diff).toContain("worktree-only.txt")
@@ -662,8 +709,8 @@ test("diff function with various changes", async () => {
// Make various changes
await $`rm ${tmp.path}/a.txt`.quiet()
await Bun.write(`${tmp.path}/new.txt`, "new content")
await Bun.write(`${tmp.path}/b.txt`, "modified content")
await Filesystem.write(`${tmp.path}/new.txt`, "new content")
await Filesystem.write(`${tmp.path}/b.txt`, "modified content")
const diff = await Snapshot.diff(before!)
expect(diff).toContain("a.txt")
@@ -683,16 +730,26 @@ test("restore function", async () => {
// Make changes
await $`rm ${tmp.path}/a.txt`.quiet()
await Bun.write(`${tmp.path}/new.txt`, "new content")
await Bun.write(`${tmp.path}/b.txt`, "modified")
await Filesystem.write(`${tmp.path}/new.txt`, "new content")
await Filesystem.write(`${tmp.path}/b.txt`, "modified")
// Restore to original state
await Snapshot.restore(before!)
expect(await Bun.file(`${tmp.path}/a.txt`).exists()).toBe(true)
expect(await Bun.file(`${tmp.path}/a.txt`).text()).toBe(tmp.extra.aContent)
expect(await Bun.file(`${tmp.path}/new.txt`).exists()).toBe(true) // New files should remain
expect(await Bun.file(`${tmp.path}/b.txt`).text()).toBe(tmp.extra.bContent)
expect(
await fs
.access(`${tmp.path}/a.txt`)
.then(() => true)
.catch(() => false),
).toBe(true)
expect(await fs.readFile(`${tmp.path}/a.txt`, "utf-8")).toBe(tmp.extra.aContent)
expect(
await fs
.access(`${tmp.path}/new.txt`)
.then(() => true)
.catch(() => false),
).toBe(true) // New files should remain
expect(await fs.readFile(`${tmp.path}/b.txt`, "utf-8")).toBe(tmp.extra.bContent)
},
})
})
@@ -710,14 +767,19 @@ test("revert should not delete files that existed but were deleted in snapshot",
const snapshot2 = await Snapshot.track()
expect(snapshot2).toBeTruthy()
await Bun.write(`${tmp.path}/a.txt`, "recreated content")
await Filesystem.write(`${tmp.path}/a.txt`, "recreated content")
const patch = await Snapshot.patch(snapshot2!)
expect(patch.files).toContain(`${tmp.path}/a.txt`)
await Snapshot.revert([patch])
expect(await Bun.file(`${tmp.path}/a.txt`).exists()).toBe(false)
expect(
await fs
.access(`${tmp.path}/a.txt`)
.then(() => true)
.catch(() => false),
).toBe(false)
},
})
})
@@ -727,14 +789,14 @@ test("revert preserves file that existed in snapshot when deleted then recreated
await Instance.provide({
directory: tmp.path,
fn: async () => {
await Bun.write(`${tmp.path}/existing.txt`, "original content")
await Filesystem.write(`${tmp.path}/existing.txt`, "original content")
const snapshot = await Snapshot.track()
expect(snapshot).toBeTruthy()
await $`rm ${tmp.path}/existing.txt`.quiet()
await Bun.write(`${tmp.path}/existing.txt`, "recreated")
await Bun.write(`${tmp.path}/newfile.txt`, "new")
await Filesystem.write(`${tmp.path}/existing.txt`, "recreated")
await Filesystem.write(`${tmp.path}/newfile.txt`, "new")
const patch = await Snapshot.patch(snapshot!)
expect(patch.files).toContain(`${tmp.path}/existing.txt`)
@@ -742,9 +804,19 @@ test("revert preserves file that existed in snapshot when deleted then recreated
await Snapshot.revert([patch])
expect(await Bun.file(`${tmp.path}/newfile.txt`).exists()).toBe(false)
expect(await Bun.file(`${tmp.path}/existing.txt`).exists()).toBe(true)
expect(await Bun.file(`${tmp.path}/existing.txt`).text()).toBe("original content")
expect(
await fs
.access(`${tmp.path}/newfile.txt`)
.then(() => true)
.catch(() => false),
).toBe(false)
expect(
await fs
.access(`${tmp.path}/existing.txt`)
.then(() => true)
.catch(() => false),
).toBe(true)
expect(await fs.readFile(`${tmp.path}/existing.txt`, "utf-8")).toBe("original content")
},
})
})
@@ -754,17 +826,17 @@ test("diffFull sets status based on git change type", async () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
await Bun.write(`${tmp.path}/grow.txt`, "one\n")
await Bun.write(`${tmp.path}/trim.txt`, "line1\nline2\n")
await Bun.write(`${tmp.path}/delete.txt`, "gone")
await Filesystem.write(`${tmp.path}/grow.txt`, "one\n")
await Filesystem.write(`${tmp.path}/trim.txt`, "line1\nline2\n")
await Filesystem.write(`${tmp.path}/delete.txt`, "gone")
const before = await Snapshot.track()
expect(before).toBeTruthy()
await Bun.write(`${tmp.path}/grow.txt`, "one\ntwo\n")
await Bun.write(`${tmp.path}/trim.txt`, "line1\n")
await Filesystem.write(`${tmp.path}/grow.txt`, "one\ntwo\n")
await Filesystem.write(`${tmp.path}/trim.txt`, "line1\n")
await $`rm ${tmp.path}/delete.txt`.quiet()
await Bun.write(`${tmp.path}/added.txt`, "new")
await Filesystem.write(`${tmp.path}/added.txt`, "new")
const after = await Snapshot.track()
expect(after).toBeTruthy()
@@ -803,7 +875,7 @@ test("diffFull with new file additions", async () => {
const before = await Snapshot.track()
expect(before).toBeTruthy()
await Bun.write(`${tmp.path}/new.txt`, "new content")
await Filesystem.write(`${tmp.path}/new.txt`, "new content")
const after = await Snapshot.track()
expect(after).toBeTruthy()
@@ -829,7 +901,7 @@ test("diffFull with file modifications", async () => {
const before = await Snapshot.track()
expect(before).toBeTruthy()
await Bun.write(`${tmp.path}/b.txt`, "modified content")
await Filesystem.write(`${tmp.path}/b.txt`, "modified content")
const after = await Snapshot.track()
expect(after).toBeTruthy()
@@ -881,7 +953,7 @@ test("diffFull with multiple line additions", async () => {
const before = await Snapshot.track()
expect(before).toBeTruthy()
await Bun.write(`${tmp.path}/multi.txt`, "line1\nline2\nline3")
await Filesystem.write(`${tmp.path}/multi.txt`, "line1\nline2\nline3")
const after = await Snapshot.track()
expect(after).toBeTruthy()
@@ -907,7 +979,7 @@ test("diffFull with addition and deletion", async () => {
const before = await Snapshot.track()
expect(before).toBeTruthy()
await Bun.write(`${tmp.path}/added.txt`, "added content")
await Filesystem.write(`${tmp.path}/added.txt`, "added content")
await $`rm ${tmp.path}/a.txt`.quiet()
const after = await Snapshot.track()
@@ -941,8 +1013,8 @@ test("diffFull with multiple additions and deletions", async () => {
const before = await Snapshot.track()
expect(before).toBeTruthy()
await Bun.write(`${tmp.path}/multi1.txt`, "line1\nline2\nline3")
await Bun.write(`${tmp.path}/multi2.txt`, "single line")
await Filesystem.write(`${tmp.path}/multi1.txt`, "line1\nline2\nline3")
await Filesystem.write(`${tmp.path}/multi2.txt`, "single line")
await $`rm ${tmp.path}/a.txt`.quiet()
await $`rm ${tmp.path}/b.txt`.quiet()
@@ -1000,7 +1072,7 @@ test("diffFull with binary file changes", async () => {
const before = await Snapshot.track()
expect(before).toBeTruthy()
await Bun.write(`${tmp.path}/binary.bin`, new Uint8Array([0x00, 0x01, 0x02, 0x03]))
await Filesystem.write(`${tmp.path}/binary.bin`, new Uint8Array([0x00, 0x01, 0x02, 0x03]))
const after = await Snapshot.track()
expect(after).toBeTruthy()
@@ -1020,11 +1092,11 @@ test("diffFull with whitespace changes", async () => {
await Instance.provide({
directory: tmp.path,
fn: async () => {
await Bun.write(`${tmp.path}/whitespace.txt`, "line1\nline2")
await Filesystem.write(`${tmp.path}/whitespace.txt`, "line1\nline2")
const before = await Snapshot.track()
expect(before).toBeTruthy()
await Bun.write(`${tmp.path}/whitespace.txt`, "line1\n\nline2\n")
await Filesystem.write(`${tmp.path}/whitespace.txt`, "line1\n\nline2\n")
const after = await Snapshot.track()
expect(after).toBeTruthy()

View File

@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test"
import path from "path"
import { BashTool } from "../../src/tool/bash"
import { Instance } from "../../src/project/instance"
import { Filesystem } from "../../src/util/filesystem"
import { tmpdir } from "../fixture/fixture"
import type { PermissionNext } from "../../src/permission/next"
import { Truncate } from "../../src/tool/truncation"
@@ -388,7 +389,7 @@ describe("tool.bash truncation", () => {
const filepath = (result.metadata as any).outputPath
expect(filepath).toBeTruthy()
const saved = await Bun.file(filepath).text()
const saved = await Filesystem.readText(filepath)
const lines = saved.trim().split("\n")
expect(lines.length).toBe(lineCount)
expect(lines[0]).toBe("1")

View File

@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test"
import path from "path"
import { ReadTool } from "../../src/tool/read"
import { Instance } from "../../src/project/instance"
import { Filesystem } from "../../src/util/filesystem"
import { tmpdir } from "../fixture/fixture"
import { PermissionNext } from "../../src/permission/next"
import { Agent } from "../../src/agent/agent"
@@ -199,10 +200,10 @@ describe("tool.read truncation", () => {
test("truncates large file by bytes and sets truncated metadata", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
const base = await Bun.file(path.join(FIXTURES_DIR, "models-api.json")).text()
const base = await Filesystem.readText(path.join(FIXTURES_DIR, "models-api.json"))
const target = 60 * 1024
const content = base.length >= target ? base : base.repeat(Math.ceil(target / base.length))
await Bun.write(path.join(dir, "large.json"), content)
await Filesystem.write(path.join(dir, "large.json"), content)
},
})
await Instance.provide({

View File

@@ -1,6 +1,7 @@
import { describe, test, expect, afterAll } from "bun:test"
import { Truncate } from "../../src/tool/truncation"
import { Identifier } from "../../src/id/id"
import { Filesystem } from "../../src/util/filesystem"
import fs from "fs/promises"
import path from "path"
@@ -9,7 +10,7 @@ const FIXTURES_DIR = path.join(import.meta.dir, "fixtures")
describe("Truncate", () => {
describe("output", () => {
test("truncates large json file by bytes", async () => {
const content = await Bun.file(path.join(FIXTURES_DIR, "models-api.json")).text()
const content = await Filesystem.readText(path.join(FIXTURES_DIR, "models-api.json"))
const result = await Truncate.output(content)
expect(result.truncated).toBe(true)
@@ -69,7 +70,7 @@ describe("Truncate", () => {
})
test("large single-line file truncates with byte message", async () => {
const content = await Bun.file(path.join(FIXTURES_DIR, "models-api.json")).text()
const content = await Filesystem.readText(path.join(FIXTURES_DIR, "models-api.json"))
const result = await Truncate.output(content)
expect(result.truncated).toBe(true)
@@ -88,7 +89,7 @@ describe("Truncate", () => {
expect(result.outputPath).toBeDefined()
expect(result.outputPath).toContain("tool_")
const written = await Bun.file(result.outputPath).text()
const written = await Filesystem.readText(result.outputPath!)
expect(written).toBe(lines)
})
@@ -139,21 +140,21 @@ describe("Truncate", () => {
const oldTimestamp = Date.now() - 10 * DAY_MS
const oldId = Identifier.create("tool", false, oldTimestamp)
oldFile = path.join(Truncate.DIR, oldId)
await Bun.write(Bun.file(oldFile), "old content")
await Filesystem.write(oldFile, "old content")
// Create a recent file (3 days ago)
const recentTimestamp = Date.now() - 3 * DAY_MS
const recentId = Identifier.create("tool", false, recentTimestamp)
recentFile = path.join(Truncate.DIR, recentId)
await Bun.write(Bun.file(recentFile), "recent content")
await Filesystem.write(recentFile, "recent content")
await Truncate.cleanup()
// Old file should be deleted
expect(await Bun.file(oldFile).exists()).toBe(false)
expect(await Filesystem.exists(oldFile)).toBe(false)
// Recent file should still exist
expect(await Bun.file(recentFile).exists()).toBe(true)
expect(await Filesystem.exists(recentFile)).toBe(true)
})
})
})

View File

@@ -285,4 +285,125 @@ describe("filesystem", () => {
expect(Filesystem.mimeType("Makefile")).toBe("application/octet-stream")
})
})
describe("writeStream()", () => {
test("writes from Web ReadableStream", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "streamed.txt")
const content = "Hello from stream!"
const encoder = new TextEncoder()
const stream = new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode(content))
controller.close()
},
})
await Filesystem.writeStream(filepath, stream)
expect(await fs.readFile(filepath, "utf-8")).toBe(content)
})
test("writes from Node.js Readable stream", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "node-streamed.txt")
const content = "Hello from Node stream!"
const { Readable } = await import("stream")
const stream = Readable.from([content])
await Filesystem.writeStream(filepath, stream)
expect(await fs.readFile(filepath, "utf-8")).toBe(content)
})
test("writes binary data from Web ReadableStream", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "binary.dat")
const binaryData = new Uint8Array([0x00, 0x01, 0x02, 0x03, 0xff])
const stream = new ReadableStream({
start(controller) {
controller.enqueue(binaryData)
controller.close()
},
})
await Filesystem.writeStream(filepath, stream)
const read = await fs.readFile(filepath)
expect(Buffer.from(read)).toEqual(Buffer.from(binaryData))
})
test("writes large content in chunks", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "large.txt")
const chunks = ["chunk1", "chunk2", "chunk3", "chunk4", "chunk5"]
const stream = new ReadableStream({
start(controller) {
for (const chunk of chunks) {
controller.enqueue(new TextEncoder().encode(chunk))
}
controller.close()
},
})
await Filesystem.writeStream(filepath, stream)
expect(await fs.readFile(filepath, "utf-8")).toBe(chunks.join(""))
})
test("creates parent directories", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "nested", "deep", "streamed.txt")
const content = "nested stream content"
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode(content))
controller.close()
},
})
await Filesystem.writeStream(filepath, stream)
expect(await fs.readFile(filepath, "utf-8")).toBe(content)
})
test("writes with permissions", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "protected-stream.txt")
const content = "secret stream content"
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode(content))
controller.close()
},
})
await Filesystem.writeStream(filepath, stream, 0o600)
const stats = await fs.stat(filepath)
if (process.platform !== "win32") {
expect(stats.mode & 0o777).toBe(0o600)
}
})
test("writes executable with permissions", async () => {
await using tmp = await tmpdir()
const filepath = path.join(tmp.path, "script.sh")
const content = "#!/bin/bash\necho hello"
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode(content))
controller.close()
},
})
await Filesystem.writeStream(filepath, stream, 0o755)
const stats = await fs.stat(filepath)
if (process.platform !== "win32") {
expect(stats.mode & 0o777).toBe(0o755)
}
expect(await fs.readFile(filepath, "utf-8")).toBe(content)
})
})
})

View File

@@ -1067,6 +1067,10 @@ export type KeybindsConfig = {
* Toggle model favorite status
*/
model_favorite_toggle?: string
/**
* Toggle showing all models
*/
model_show_all_toggle?: string
/**
* Share current session
*/
@@ -1183,6 +1187,10 @@ export type KeybindsConfig = {
* Previous agent
*/
agent_cycle_reverse?: string
/**
* Toggle auto-accept mode for permissions
*/
permission_auto_accept_toggle?: string
/**
* Cycle model variants
*/

View File

@@ -600,4 +600,3 @@ These environment variables enable experimental features that may change or be r
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Enable experimental Exa features |
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Enable experimental LSP type checking |
| `OPENCODE_EXPERIMENTAL_MARKDOWN` | boolean | Enable experimental markdown features |
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Enable plan mode |

View File

@@ -1,79 +0,0 @@
#!/usr/bin/env bun
import path from "path"
import { pathToFileURL } from "bun"
import { createOpencode } from "@opencode-ai/sdk"
import { parseArgs } from "util"
async function main() {
const { values, positionals } = parseArgs({
args: Bun.argv.slice(2),
options: {
file: { type: "string", short: "f" },
help: { type: "boolean", short: "h", default: false },
},
allowPositionals: true,
})
if (values.help) {
console.log(`
Usage: bun script/duplicate-issue.ts [options] <message>
Options:
-f, --file <path> File to attach to the prompt
-h, --help Show this help message
Examples:
bun script/duplicate-issue.ts -f issue_info.txt "Check the attached file for issue details"
`)
process.exit(0)
}
const message = positionals.join(" ")
if (!message) {
console.error("Error: message is required")
process.exit(1)
}
const opencode = await createOpencode({ port: 0 })
try {
const parts: Array<{ type: "text"; text: string } | { type: "file"; url: string; filename: string; mime: string }> =
[]
if (values.file) {
const resolved = path.resolve(process.cwd(), values.file)
const file = Bun.file(resolved)
if (!(await file.exists())) {
console.error(`Error: file not found: ${values.file}`)
process.exit(1)
}
parts.push({
type: "file",
url: pathToFileURL(resolved).href,
filename: path.basename(resolved),
mime: "text/plain",
})
}
parts.push({ type: "text", text: message })
const session = await opencode.client.session.create()
const result = await opencode.client.session
.prompt({
path: { id: session.data!.id },
body: {
agent: "duplicate-issue",
parts,
},
signal: AbortSignal.timeout(120_000),
})
.then((x) => x.data?.parts?.find((y) => y.type === "text")?.text ?? "")
console.log(result.trim())
} finally {
opencode.server.close()
}
}
main()