Compare commits

...

127 Commits

Author SHA1 Message Date
opencode-agent[bot]
40ffe1b489 Apply PR #14215: Remove use of Bun.file 2026-02-19 15:22:54 +00:00
opencode-agent[bot]
d290518c95 Apply PR #14038: feat: add list sessions for all sessions 2026-02-19 15:22:53 +00:00
opencode-agent[bot]
641b17cc62 Apply PR #12633: feat(tui): add auto-accept mode for permission requests 2026-02-19 15:22:53 +00:00
opencode-agent[bot]
82a9028931 Apply PR #12022: feat: update tui model dialog to utilize model family to reduce noise in list 2026-02-19 15:22:52 +00:00
opencode-agent[bot]
b87f62b847 Apply PR #11811: feat: make plan mode the default 2026-02-19 15:22:52 +00:00
Adam
0fcba68d4c chore: cleanup 2026-02-19 09:00:43 -06:00
Adam
338393c016 fix(app): accordion styles 2026-02-19 09:00:43 -06:00
Adam
8ebdbe0ea2 fix(core): text files missclassified as binary 2026-02-19 07:32:58 -06:00
Adam
38f7071da9 chore: cleanup 2026-02-19 07:32:58 -06:00
Brendan Allan
d2d5f3c04b app: fix typecheck 2026-02-19 21:27:44 +08:00
Brendan Allan
885d71636f desktop: fetch defaultServer at top level 2026-02-19 21:14:59 +08:00
Adam
d07f09925f fix(app): terminal rework (#14217) 2026-02-19 06:35:14 -06:00
opencode-agent[bot]
c7b35342dd chore: update nix node_modules hashes 2026-02-19 06:40:44 +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 Raad
3a416f6f33 sdk: fix nested exports transformation in publish script
The publish script now recursively transforms export paths to handle nested export objects. This ensures all SDK entry points are correctly mapped to their compiled dist/ locations and type definitions when publishing to npm.
2026-02-18 20:43:50 -05:00
Dax Raad
11a37834c2 tui: ensure onExit callback fires after terminal output is written 2026-02-18 20:43:50 -05:00
Brendan Allan
d620455531 app: deduplicate allServers list 2026-02-19 09:34:23 +08:00
Dax Raad
568eccb4c6 Revert: all refactor commits migrating from Bun.file() to Filesystem module 2026-02-18 19:41:14 -05:00
Dax
3a07dd8d96 refactor: migrate src/project/project.ts from Bun.file() to Filesystem/stat modules (#14126) 2026-02-18 19:37:10 -05:00
David Hill
802ccd3788 tweak(ui): rotate collapsible chevron icon 2026-02-19 00:35:12 +00:00
David Hill
6042785c57 tweak(ui): rtl-truncate edited file paths 2026-02-19 00:32:12 +00:00
David Hill
5d8664c13e tweak(app): adjust session turn horizontal padding 2026-02-19 00:32:12 +00:00
David Hill
3d0f24067c tweak(app): tighten prompt dock padding 2026-02-19 00:32:12 +00:00
David Hill
44049540b0 tweak(ui): add open-file tooltip icon 2026-02-19 00:32:12 +00:00
David Hill
40f00ccc1c tweak(ui): use chevron icons for review diff rows 2026-02-19 00:32:12 +00:00
David Hill
a301051263 tweak(ui): tighten review diff file info gap 2026-02-19 00:32:12 +00:00
David Hill
fd61be4078 tweak(ui): show added diff counts in review 2026-02-19 00:32:12 +00:00
David Hill
4a8bdc3c75 tweak(ui): group edited files list styling 2026-02-19 00:32:12 +00:00
David Hill
9c7629ce61 Update oc-2.json 2026-02-19 00:32:12 +00:00
David Hill
ba53c56a21 tweak(ui): combine diffs in review into one group 2026-02-19 00:32:12 +00:00
Dax
14c0989411 refactor: migrate src/tool/read.ts from Bun.file() to Filesystem module (#14118) 2026-02-18 19:29:11 -05:00
Dax
36bc07a5af refactor: migrate src/tool/write.ts from Bun.file() to Filesystem module (#14119) 2026-02-18 19:27:11 -05:00
Dax
270b807cdf refactor: migrate src/tool/edit.ts from Bun.file() to Filesystem module (#14120) 2026-02-18 19:26:45 -05:00
Dax
bd52ce5640 refactor: migrate remaining tool files from Bun.file() to Filesystem/stat modules (#14121) 2026-02-18 19:24:21 -05:00
Dax
a624871ccd refactor: migrate src/storage/storage.ts from Bun.file()/Bun.write() to Filesystem module (#14122) 2026-02-18 19:21:21 -05:00
Dax
819d09e64e refactor: migrate src/storage/json-migration.ts from Bun.file() to Filesystem module (#14123) 2026-02-18 19:20:40 -05:00
Dax
9e6cb89101 refactor: migrate src/mcp/auth.ts from Bun.file()/Bun.write() to Filesystem module (#14125) 2026-02-18 19:20:16 -05:00
Dax
a8347c3762 refactor: migrate src/storage/db.ts from Bun.file() to statSync (#14124) 2026-02-18 19:20:03 -05:00
Dax
57b63ea83d refactor: migrate src/session/prompt.ts from Bun.file() to Filesystem/stat modules (#14128) 2026-02-19 00:18:05 +00:00
Ryan Vogel
84643f4192 feat: move session listing to experimental routes with enhanced query parameters 2026-02-18 19:04:18 -05:00
Ryan Vogel
c162074888 chore: skip PR standards checks for PRs created before Feb 18 2026 6PM EST (#14208) 2026-02-18 18:16:43 -05:00
Aiden Cline
088eac9d4e fix: opencode run crashing, and show errored tool calls in output (#14206) 2026-02-18 17:13:01 -06:00
Dax
5fe237a3fd refactor: migrate src/skill/discovery.ts from Bun.file()/Bun.write() to Filesystem module (#14133) 2026-02-18 23:10:24 +00:00
Dax
ae398539c5 refactor: migrate src/session/instruction.ts from Bun.file() to Filesystem module (#14130) 2026-02-18 18:09:45 -05:00
Dax
359360ad86 refactor: migrate src/provider/provider.ts from Bun.file() to Filesystem module (#14132) 2026-02-18 18:08:48 -05:00
Dax
5d12eb9528 refactor: migrate src/shell/shell.ts from Bun.file() to statSync (#14134) 2026-02-18 22:55:50 +00:00
Dax
6fb4f2a7a5 refactor: migrate src/cli/cmd/tui/thread.ts from Bun.file() to Filesystem module (#14135) 2026-02-18 17:28:41 -05:00
Dax
48dfa45a9a refactor: migrate src/util/log.ts from Bun.file() to Node.js fs module (#14136) 2026-02-18 17:28:08 -05:00
Dax
97520c827e refactor: migrate src/provider/models.ts from Bun.file()/Bun.write() to Filesystem module (#14131) 2026-02-18 17:26:13 -05:00
Dax
b75a89776d refactor: migrate src/lsp/client.ts from Bun.file() to Filesystem module (#14137) 2026-02-18 17:22:06 -05:00
opencode-agent[bot]
b909679367 chore: generate 2026-02-18 22:21:17 +00:00
Ryan Vogel
639d1dd8fe chore: add compliance checks for issues and PRs with recheck on edit (#14170) 2026-02-18 17:20:23 -05:00
Luke Parker
7033b4d0a8 fix(win32): Sidecar spawning a window (#14197) 2026-02-19 08:18:15 +10:00
Eduardo Bellido Bellido
87c16374aa fix(lsp): use HashiCorp releases API for installing terraform-ls (#14200) 2026-02-18 16:11:57 -06:00
Dax
d366a1430f refactor: migrate src/lsp/server.ts from Bun.file()/Bun.write() to Filesystem module (#14138) 2026-02-18 21:41:07 +00:00
David Hill
cfea5c73de tweak(app): delay prompt mode toggle tooltip 2026-02-18 21:33:14 +00:00
David Hill
2589eb207f tweak(app): shorten prompt mode toggle tooltips 2026-02-18 21:33:14 +00:00
David Hill
ec7c72da3f tweak(ui): restyle reasoning blocks 2026-02-18 21:33:14 +00:00
Dax
a4b36a72ad refactor: migrate src/file/time.ts from Bun.file() to stat (#14141) 2026-02-18 21:22:08 +00:00
Dax
e37a9081a6 refactor: migrate src/cli/cmd/session.ts from Bun.file() to statSync (#14144) 2026-02-18 16:20:58 -05:00
Dax
a2469d933e refactor: migrate src/acp/agent.ts from Bun.file() to Filesystem module (#14139) 2026-02-18 16:14:20 -05:00
Dax
3cde93bf2d refactor: migrate src/auth/index.ts from Bun.file()/Bun.write() to Filesystem module (#14140) 2026-02-18 16:13:50 -05:00
Dax
898bcdec87 refactor: migrate src/cli/cmd/agent.ts from Bun.file()/Bun.write() to Filesystem module (#14142) 2026-02-18 21:08:01 +00:00
Dax
d5971e2da5 refactor: migrate src/cli/cmd/import.ts from Bun.file() to Filesystem module (#14143) 2026-02-18 21:07:32 +00:00
David Hill
c71f4d4847 Update oc-2.json 2026-02-18 20:32:54 +00:00
opencode-agent[bot]
dec7827548 chore: generate 2026-02-18 20:19:27 +00:00
David Hill
7faa8cb110 tweak(ui): reduce review panel padding 2026-02-18 20:18:17 +00:00
David Hill
d8a4a125c0 Update oc-2.json 2026-02-18 20:18:17 +00:00
David Hill
50923f06f1 tweak(ui): remove pressed scale for secondary buttons 2026-02-18 20:18:17 +00:00
David Hill
ba919fb619 tweak(ui): shrink review expand/collapse width 2026-02-18 20:18:17 +00:00
David Hill
47b4de3531 tweak(ui): tighten review header action spacing 2026-02-18 20:18:17 +00:00
David Hill
bb6d1d502f tweak(ui): adjust review diff style hover radius 2026-02-18 20:18:17 +00:00
David Hill
31e964e7cf Update oc-2.json 2026-02-18 20:18:17 +00:00
David Hill
06b2304a5f tweak(ui): override for the radio group in the review 2026-02-18 20:18:17 +00:00
David Hill
1b67339e4d Update radio-group.css 2026-02-18 20:18:17 +00:00
David Hill
1571246ba8 tweak(ui): use default cursor for segmented control 2026-02-18 20:18:17 +00:00
David Hill
d730d8be01 tweak(ui): shrink review diff style toggle 2026-02-18 20:18:17 +00:00
David Hill
e42cc85112 Update oc-2.json 2026-02-18 20:18:17 +00:00
David Hill
c7a79f1877 Update icon-button.css 2026-02-18 20:18:17 +00:00
David Hill
431f5347af tweak(ui): search button style 2026-02-18 20:18:17 +00:00
David Hill
1ed4a98233 tweak(ui): remove pressed transition for secondary buttons 2026-02-18 20:18:17 +00:00
David Hill
db4ff89579 Update oc-2.json 2026-02-18 20:18:17 +00:00
David Hill
2f56761060 tweak(ui): expanded color state on titlebar buttons 2026-02-18 20:18:17 +00:00
David Hill
09286ccae0 tweak(ui): oc-2 theme updates 2026-02-18 20:18:17 +00:00
David Hill
4e959849f6 tweak(ui): hover and active styles for filetree tabs 2026-02-18 20:18:17 +00:00
David Hill
3690cafeb8 tweak(ui): hover and active styles for title bar buttons 2026-02-18 20:18:17 +00:00
David Hill
bcca253dec tweak(ui): hover and active styles for title bar buttons 2026-02-18 20:18:17 +00:00
David Hill
6d69ad5574 tweak(ui): update oc-2 secondary button colors 2026-02-18 20:18:17 +00:00
David Hill
1f9be63e96 tweak(ui): use weak border and base icon color for secondary 2026-02-18 20:18:17 +00:00
David Hill
0873908030 tweak(ui): theme color updates 2026-02-18 20:18:17 +00:00
David Hill
4db2d94854 tweak(ui): shrink filetree tab height 2026-02-18 20:18:17 +00:00
David Hill
e5d52e4eb5 tweak(ui): align pill tabs pressed background 2026-02-18 20:18:16 +00:00
David Hill
f20c0bffd3 tweak(ui): unify titlebar expanded button background 2026-02-18 20:18:16 +00:00
David Hill
9110e6a2a7 tweak(ui): share button border 2026-02-18 20:18:16 +00:00
David Hill
0888c02379 tweak(ui): file tree background color 2026-02-18 20:18:16 +00:00
David Hill
24ce49d9d7 fix(ui): add previous smoke colors 2026-02-18 20:18:16 +00:00
David Hill
5d69f00282 button style tweaks 2026-02-18 20:18:16 +00:00
David Hill
12016c8eb4 oc-2 theme init 2026-02-18 20:18:16 +00:00
David Hill
d6331cf792 Update colors.css 2026-02-18 20:18:16 +00:00
opencode-agent[bot]
2d7c9c9692 chore: generate 2026-02-18 20:15:14 +00:00
Helge Tesdal
1aa18c6cd6 feat(plugin): pass sessionID and callID to shell.env hook input (#13662) 2026-02-18 14:14:18 -06:00
Adam
de25703e9d fix(app): terminal cross-talk (#14184) 2026-02-18 13:56:05 -06:00
Adam
1133d87be0 chore: cleanup 2026-02-18 13:38:51 -06:00
Adam
42aa28d512 chore: cleanup (#14181) 2026-02-18 13:23:20 -06:00
opencode-agent[bot]
c6bd320003 chore: update nix node_modules hashes 2026-02-18 19:08:12 +00:00
Ryan Vogel
34e69735cb feat: add cursor pagination to global session list 2026-02-17 19:47:55 -05:00
Ryan Vogel
b9ec4e6b97 Merge branch 'dev' into feat/global-recent-sessions 2026-02-17 18:38:45 -05:00
Ryan Vogel
007e633465 Merge branch 'dev' into feat/global-recent-sessions 2026-02-17 18:33:15 -05:00
Ryan Vogel
c0e0156680 feat: add list sessions for all sessions 2026-02-17 18:27:11 -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
143 changed files with 3163 additions and 1035 deletions

15
.github/TEAM_MEMBERS vendored Normal file
View File

@@ -0,0 +1,15 @@
adamdotdevin
Brendonovich
fwang
Hona
iamdavidhill
jayair
jlongster
kitlangton
kommander
MrMushrooooom
nexxeln
R44VC0RP
rekram1-node
RhysSullivan
thdxr

View File

@@ -1,7 +1,29 @@
### Issue for this PR
Closes #
### Type of change
- [ ] Bug fix
- [ ] New feature
- [ ] Refactor / code improvement
- [ ] Documentation
### What does this PR do?
Please provide a description of the issue (if there is one), the changes you made to fix it, and why they work. It is expected that you understand why your changes work and if you do not understand why at least say as much so a maintainer knows how much to value the PR.
Please provide a description of the issue, the changes you made to fix it, and why they work. It is expected that you understand why your changes work and if you do not understand why at least say as much so a maintainer knows how much to value the PR.
**If you paste a large clearly AI generated description here your PR may be IGNORED or CLOSED!**
### How did you verify your code works?
### Screenshots / recordings
_If this is a UI change, please include a screenshot or recording._
### Checklist
- [ ] I have tested my changes locally
- [ ] I have not included unrelated changes in this PR
_If you do not follow this template your PR will be automatically rejected._

View File

@@ -2,10 +2,11 @@ name: duplicate-issues
on:
issues:
types: [opened]
types: [opened, edited]
jobs:
check-duplicates:
if: github.event.action == 'opened'
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
@@ -34,7 +35,7 @@ jobs:
"webfetch": "deny"
}
run: |
opencode run -m opencode/claude-haiku-4-5 "A new issue has been created:
opencode run -m opencode/claude-sonnet-4-6 "A new issue has been created:
Issue number: ${{ github.event.issue.number }}
@@ -115,3 +116,62 @@ jobs:
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')
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: ./.github/actions/setup-bun
- name: Install opencode
run: curl -fsSL https://opencode.ai/install | bash
- 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: |
opencode run -m opencode/claude-sonnet-4-6 "Issue #${{ github.event.issue.number }} was previously flagged as non-compliant and has been edited.
Lookup this issue with gh issue view ${{ github.event.issue.number }}.
Re-check whether the issue now 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.
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.
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,17 +6,6 @@ on:
jobs:
check-duplicates:
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 != 'opencode-agent[bot]'
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
@@ -27,16 +16,31 @@ jobs:
with:
fetch-depth: 1
- name: Check team membership
id: team-check
run: |
LOGIN="${{ github.event.pull_request.user.login }}"
if [ "$LOGIN" = "opencode-agent[bot]" ] || grep -qxF "$LOGIN" .github/TEAM_MEMBERS; then
echo "is_team=true" >> "$GITHUB_OUTPUT"
echo "Skipping: $LOGIN is a team member or bot"
else
echo "is_team=false" >> "$GITHUB_OUTPUT"
fi
- name: Setup Bun
if: steps.team-check.outputs.is_team != 'true'
uses: ./.github/actions/setup-bun
- name: Install dependencies
if: steps.team-check.outputs.is_team != 'true'
run: bun install
- name: Install opencode
if: steps.team-check.outputs.is_team != 'true'
run: curl -fsSL https://opencode.ai/install | bash
- name: Build prompt
if: steps.team-check.outputs.is_team != 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
@@ -53,6 +57,7 @@ jobs:
} > pr_info.txt
- name: Check for duplicate PRs
if: steps.team-check.outputs.is_team != 'true'
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -6,19 +6,9 @@ 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 != 'opencode-agent[bot]'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Check PR standards
@@ -26,6 +16,30 @@ jobs:
with:
script: |
const pr = context.payload.pull_request;
const login = pr.user.login;
// Skip PRs older than Feb 18, 2026 at 6PM EST (Feb 19, 2026 00:00 UTC)
const cutoff = new Date('2026-02-19T00:00:00Z');
const prCreated = new Date(pr.created_at);
if (prCreated < cutoff) {
console.log(`Skipping: PR #${pr.number} was created before cutoff (${prCreated.toISOString()})`);
return;
}
// Check if author is a team member or bot
if (login === 'opencode-agent[bot]') return;
const { data: file } = await github.rest.repos.getContent({
owner: context.repo.owner,
repo: context.repo.repo,
path: '.github/TEAM_MEMBERS',
ref: 'dev'
});
const members = Buffer.from(file.content, 'base64').toString().split('\n').map(l => l.trim()).filter(Boolean);
if (members.includes(login)) {
console.log(`Skipping: ${login} is a team member`);
return;
}
const title = pr.title;
async function addLabel(label) {
@@ -137,3 +151,201 @@ jobs:
await removeLabel('needs:issue');
console.log('PR meets all standards');
check-compliance:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Check PR template compliance
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const login = pr.user.login;
// Skip PRs older than Feb 18, 2026 at 6PM EST (Feb 19, 2026 00:00 UTC)
const cutoff = new Date('2026-02-19T00:00:00Z');
const prCreated = new Date(pr.created_at);
if (prCreated < cutoff) {
console.log(`Skipping: PR #${pr.number} was created before cutoff (${prCreated.toISOString()})`);
return;
}
// Check if author is a team member or bot
if (login === 'opencode-agent[bot]') return;
const { data: file } = await github.rest.repos.getContent({
owner: context.repo.owner,
repo: context.repo.repo,
path: '.github/TEAM_MEMBERS',
ref: 'dev'
});
const members = Buffer.from(file.content, 'base64').toString().split('\n').map(l => l.trim()).filter(Boolean);
if (members.includes(login)) {
console.log(`Skipping: ${login} is a team member`);
return;
}
const body = pr.body || '';
const title = pr.title;
const isDocsOrRefactor = /^(docs|refactor)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title);
const issues = [];
// Check: template sections exist
const hasWhatSection = /### What does this PR do\?/.test(body);
const hasTypeSection = /### Type of change/.test(body);
const hasVerifySection = /### How did you verify your code works\?/.test(body);
const hasChecklistSection = /### Checklist/.test(body);
const hasIssueSection = /### Issue for this PR/.test(body);
if (!hasWhatSection || !hasTypeSection || !hasVerifySection || !hasChecklistSection || !hasIssueSection) {
issues.push('PR description is missing required template sections. Please use the [PR template](../blob/dev/.github/pull_request_template.md).');
}
// Check: "What does this PR do?" has real content (not just placeholder text)
if (hasWhatSection) {
const whatMatch = body.match(/### What does this PR do\?\s*\n([\s\S]*?)(?=###|$)/);
const whatContent = whatMatch ? whatMatch[1].trim() : '';
const placeholder = 'Please provide a description of the issue';
const onlyPlaceholder = whatContent.includes(placeholder) && whatContent.replace(placeholder, '').replace(/[*\s]/g, '').length < 20;
if (!whatContent || onlyPlaceholder) {
issues.push('"What does this PR do?" section is empty or only contains placeholder text. Please describe your changes.');
}
}
// Check: at least one "Type of change" checkbox is checked
if (hasTypeSection) {
const typeMatch = body.match(/### Type of change\s*\n([\s\S]*?)(?=###|$)/);
const typeContent = typeMatch ? typeMatch[1] : '';
const hasCheckedBox = /- \[x\]/i.test(typeContent);
if (!hasCheckedBox) {
issues.push('No "Type of change" checkbox is checked. Please select at least one.');
}
}
// Check: issue reference (skip for docs/refactor)
if (!isDocsOrRefactor && hasIssueSection) {
const issueMatch = body.match(/### Issue for this PR\s*\n([\s\S]*?)(?=###|$)/);
const issueContent = issueMatch ? issueMatch[1].trim() : '';
const hasIssueRef = /(closes|fixes|resolves)\s+#\d+/i.test(issueContent) || /#\d+/.test(issueContent);
if (!hasIssueRef) {
issues.push('No issue referenced. Please add `Closes #<number>` linking to the relevant issue.');
}
}
// Check: "How did you verify" has content
if (hasVerifySection) {
const verifyMatch = body.match(/### How did you verify your code works\?\s*\n([\s\S]*?)(?=###|$)/);
const verifyContent = verifyMatch ? verifyMatch[1].trim() : '';
if (!verifyContent) {
issues.push('"How did you verify your code works?" section is empty. Please explain how you tested.');
}
}
// Check: checklist boxes are checked
if (hasChecklistSection) {
const checklistMatch = body.match(/### Checklist\s*\n([\s\S]*?)(?=###|$)/);
const checklistContent = checklistMatch ? checklistMatch[1] : '';
const unchecked = (checklistContent.match(/- \[ \]/g) || []).length;
const checked = (checklistContent.match(/- \[x\]/gi) || []).length;
if (checked < 2) {
issues.push('Not all checklist items are checked. Please confirm you have tested locally and have not included unrelated changes.');
}
}
// Helper functions
async function addLabel(label) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: [label]
});
}
async function removeLabel(label) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
name: label
});
} catch (e) {}
}
const hasComplianceLabel = pr.labels.some(l => l.name === 'needs:compliance');
if (issues.length > 0) {
// Non-compliant
if (!hasComplianceLabel) {
await addLabel('needs:compliance');
}
const marker = '<!-- issue-compliance -->';
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number
});
const existing = comments.find(c => c.body.includes(marker));
const body_text = `${marker}
This PR doesn't fully meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md) and [PR template](../blob/dev/.github/pull_request_template.md).
**What needs to be fixed:**
${issues.map(i => `- ${i}`).join('\n')}
Please edit this PR description to address the above within **2 hours**, or it will be automatically closed.
If you believe this was flagged incorrectly, please let a maintainer know.`;
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: body_text
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: body_text
});
}
console.log(`PR #${pr.number} is non-compliant: ${issues.join(', ')}`);
} else if (hasComplianceLabel) {
// Was non-compliant, now fixed
await removeLabel('needs:compliance');
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number
});
const marker = '<!-- issue-compliance -->';
const existing = comments.find(c => c.body.includes(marker));
if (existing) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id
});
}
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: 'Thanks for updating your PR! It now meets our contributing guidelines. :+1:'
});
console.log(`PR #${pr.number} is now compliant, label removed`);
} else {
console.log(`PR #${pr.number} is compliant`);
}

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.

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-5sXHoHbRdXbqM/zRJZiXt26sm/yyyZN/4OOHUtdofhk=",
"aarch64-linux": "sha256-JCMm5X7e27BBV4wyaknCMM4CBt4Lr72SSvaGxEeNsJE=",
"aarch64-darwin": "sha256-DBQJURlTPqFt0OYUHSvZZ4H0NUf020aic4zNX5CXzDc=",
"x86_64-darwin": "sha256-t2luVxqCcRSgq/WNWkm4ZpKXO22n2RnAWP6msoTOr+A="
"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

@@ -427,6 +427,7 @@ export function DialogSelectServer() {
}
>
{(i) => {
const key = ServerConnection.key(i)
return (
<div class="flex items-center gap-3 min-w-0 flex-1 group/item">
<Show
@@ -446,8 +447,8 @@ export function DialogSelectServer() {
>
<ServerRow
conn={i}
status={store.status[ServerConnection.key(i)]}
dimmed={store.status[ServerConnection.key(i)]?.healthy === false}
status={store.status[key]}
dimmed={store.status[key]?.healthy === false}
class="flex items-center gap-3 px-4 min-w-0 flex-1"
badge={
<Show when={defaultUrl() === i.http.url}>
@@ -460,7 +461,7 @@ export function DialogSelectServer() {
</Show>
<Show when={store.editServer.id !== i.http.url}>
<div class="flex items-center justify-center gap-5 pl-4">
<Show when={current() === i}>
<Show when={ServerConnection.key(current()) === key}>
<p class="text-text-weak text-12-regular">{language.t("dialog.server.current")}</p>
</Show>

View File

@@ -403,15 +403,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const [composing, setComposing] = createSignal(false)
const isImeComposing = (event: KeyboardEvent) => event.isComposing || composing() || event.keyCode === 229
createEffect(() => {
if (!isFocused()) closePopover()
})
// Safety: reset composing state on focus change to prevent stuck state
// This handles edge cases where compositionend event may not fire
createEffect(() => {
if (!isFocused()) setComposing(false)
})
const handleBlur = () => {
closePopover()
setComposing(false)
}
const agentList = createMemo(() =>
sync.data.agent
@@ -1118,6 +1113,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
onPaste={handlePaste}
onCompositionStart={() => setComposing(true)}
onCompositionEnd={() => setComposing(false)}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
classList={{
"select-text": true,
@@ -1367,7 +1363,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<TooltipKeybind
placement="top"
gutter={4}
title={language.t(mode === "shell" ? "command.prompt.mode.shell" : "command.prompt.mode.normal")}
openDelay={2000}
title={language.t(mode === "shell" ? "prompt.mode.shell" : "prompt.mode.normal")}
keybind={command.keybind(mode === "shell" ? "prompt.mode.shell" : "prompt.mode.normal")}
class="size-full flex items-center justify-center"
>

View File

@@ -257,27 +257,12 @@ export function SessionHeader() {
] as const
})
const checksReady = createMemo(() => {
if (platform.platform !== "desktop") return true
if (!platform.checkAppExists) return true
const list = apps()
return list.every((app) => exists[app.id] !== undefined)
})
const [prefs, setPrefs] = persisted(Persist.global("open.app"), createStore({ app: "finder" as OpenApp }))
const [menu, setMenu] = createStore({ open: false })
const canOpen = createMemo(() => platform.platform === "desktop" && !!platform.openPath && server.isLocal())
const current = createMemo(() => options().find((o) => o.id === prefs.app) ?? options()[0])
createEffect(() => {
if (platform.platform !== "desktop") return
if (!checksReady()) return
const value = prefs.app
if (options().some((o) => o.id === value)) return
setPrefs("app", options()[0]?.id ?? "finder")
})
const openDir = (app: OpenApp) => {
const directory = projectDirectory()
if (!directory) return
@@ -319,9 +304,11 @@ export function SessionHeader() {
<Show when={centerMount()}>
{(mount) => (
<Portal mount={mount()}>
<button
<Button
type="button"
class="hidden md:flex w-[240px] max-w-full min-w-0 h-[24px] pl-0.5 pr-2 items-center gap-2 justify-between rounded-md border border-border-weak-base bg-surface-panel transition-colors cursor-default hover:bg-surface-raised-base-hover focus-visible:bg-surface-raised-base-hover active:bg-surface-raised-base-active"
variant="ghost"
size="small"
class="hidden md:flex w-[240px] max-w-full min-w-0 pl-0.5 pr-2 items-center gap-2 justify-between rounded-md border border-border-weak-base bg-surface-panel shadow-none cursor-default"
onClick={() => command.trigger("file.open")}
aria-label={language.t("session.header.searchFiles")}
>
@@ -337,7 +324,7 @@ export function SessionHeader() {
<Keybind class="shrink-0 !border-0 !bg-transparent !shadow-none px-0">{keybind()}</Keybind>
)}
</Show>
</button>
</Button>
</Portal>
)}
</Show>
@@ -398,7 +385,7 @@ export function SessionHeader() {
<DropdownMenu.Group>
<DropdownMenu.GroupLabel>{language.t("session.header.openIn")}</DropdownMenu.GroupLabel>
<DropdownMenu.RadioGroup
value={prefs.app}
value={current().id}
onChange={(value) => {
if (!OPEN_APPS.includes(value as OpenApp)) return
setPrefs("app", value as OpenApp)
@@ -464,7 +451,7 @@ export function SessionHeader() {
triggerProps={{
variant: "ghost",
class:
"rounded-md h-[24px] px-3 border border-border-base bg-surface-panel shadow-none data-[expanded]:bg-surface-raised-base-active",
"rounded-md h-[24px] px-3 border border-border-weak-base bg-surface-panel shadow-none data-[expanded]:bg-surface-base-active",
classList: { "rounded-r-none": share.shareUrl() !== undefined },
style: { scale: 1 },
}}
@@ -537,7 +524,7 @@ export function SessionHeader() {
<IconButton
icon={share.state.copied ? "check" : "link"}
variant="ghost"
class="rounded-l-none h-[24px] border border-border-base bg-surface-panel shadow-none"
class="rounded-l-none h-[24px] border border-border-weak-base bg-surface-panel shadow-none"
onClick={() => share.copyLink((error) => showRequestError(language, error))}
disabled={share.state.unshare}
aria-label={

View File

@@ -203,7 +203,7 @@ export function StatusPopover() {
triggerProps={{
variant: "ghost",
class:
"rounded-md h-[24px] pr-3 pl-0.5 gap-2 border border-border-weak-base bg-surface-panel shadow-none data-[expanded]:bg-surface-raised-base-hover",
"rounded-md h-[24px] pr-3 pl-0.5 gap-2 border border-border-weak-base bg-surface-panel shadow-none data-[expanded]:bg-surface-base-active",
style: { scale: 1 },
}}
trigger={

View File

@@ -320,8 +320,6 @@ export const Terminal = (props: TerminalProps) => {
const mod = loaded.mod
const g = loaded.ghostty
const once = { value: false }
const restore = typeof local.pty.buffer === "string" ? local.pty.buffer : ""
const restoreSize =
restore &&
@@ -416,20 +414,28 @@ export const Terminal = (props: TerminalProps) => {
cleanups.push(() => window.removeEventListener("resize", handleResize))
}
if (restore && restoreSize) {
t.write(restore, () => {
fit.fit()
scheduleSize(t.cols, t.rows)
if (typeof local.pty.scrollY === "number") t.scrollToLine(local.pty.scrollY)
startResize()
const write = (data: string) =>
new Promise<void>((resolve) => {
if (!output) {
resolve()
return
}
output.push(data)
output.flush(resolve)
})
if (restore && restoreSize) {
await write(restore)
fit.fit()
scheduleSize(t.cols, t.rows)
if (typeof local.pty.scrollY === "number") t.scrollToLine(local.pty.scrollY)
startResize()
} else {
fit.fit()
scheduleSize(t.cols, t.rows)
if (restore) {
t.write(restore, () => {
if (typeof local.pty.scrollY === "number") t.scrollToLine(local.pty.scrollY)
})
await write(restore)
if (typeof local.pty.scrollY === "number") t.scrollToLine(local.pty.scrollY)
}
startResize()
}
@@ -438,38 +444,32 @@ export const Terminal = (props: TerminalProps) => {
// console.log("Scroll position:", ydisp)
// })
const once = { value: false }
let closing = false
const url = new URL(sdk.url + `/pty/${local.pty.id}/connect`)
url.searchParams.set("directory", sdk.directory)
url.searchParams.set("cursor", String(start !== undefined ? start : local.pty.buffer ? -1 : 0))
url.protocol = url.protocol === "https:" ? "wss:" : "ws:"
url.username = server.current?.http.username ?? ""
url.password = server.current?.http.password ?? ""
const socket = new WebSocket(url)
socket.binaryType = "arraybuffer"
ws = socket
cleanups.push(() => {
if (socket.readyState !== WebSocket.CLOSED && socket.readyState !== WebSocket.CLOSING) socket.close()
})
if (disposed) {
cleanup()
return
}
const handleOpen = () => {
local.onConnect?.()
scheduleSize(t.cols, t.rows)
}
socket.addEventListener("open", handleOpen)
cleanups.push(() => socket.removeEventListener("open", handleOpen))
if (socket.readyState === WebSocket.OPEN) handleOpen()
const decoder = new TextDecoder()
const handleMessage = (event: MessageEvent) => {
if (disposed) return
if (closing) return
if (event.data instanceof ArrayBuffer) {
// WebSocket control frame: 0x00 + UTF-8 JSON (currently { cursor }).
const bytes = new Uint8Array(event.data)
if (bytes[0] !== 0) return
const json = decoder.decode(bytes.subarray(1))
@@ -491,20 +491,20 @@ export const Terminal = (props: TerminalProps) => {
cursor += data.length
}
socket.addEventListener("message", handleMessage)
cleanups.push(() => socket.removeEventListener("message", handleMessage))
const handleError = (error: Event) => {
if (disposed) return
if (closing) return
if (once.value) return
once.value = true
console.error("WebSocket error:", error)
local.onConnectError?.(error)
}
socket.addEventListener("error", handleError)
cleanups.push(() => socket.removeEventListener("error", handleError))
const handleClose = (event: CloseEvent) => {
if (disposed) return
if (closing) return
// Normal closure (code 1000) means PTY process exited - server event handles cleanup
// For other codes (network issues, server restart), trigger error handler
if (event.code !== 1000) {
@@ -514,7 +514,15 @@ export const Terminal = (props: TerminalProps) => {
}
}
socket.addEventListener("close", handleClose)
cleanups.push(() => socket.removeEventListener("close", handleClose))
cleanups.push(() => {
closing = true
socket.removeEventListener("open", handleOpen)
socket.removeEventListener("message", handleMessage)
socket.removeEventListener("error", handleError)
socket.removeEventListener("close", handleClose)
if (socket.readyState !== WebSocket.CLOSED && socket.readyState !== WebSocket.CLOSING) socket.close(1000)
})
}
void run().catch((err) => {

View File

@@ -174,6 +174,10 @@ function detectLocale(): Locale {
return "en"
}
function normalizeLocale(value: string): Locale {
return LOCALES.includes(value as Locale) ? (value as Locale) : "en"
}
export const { use: useLanguage, provider: LanguageProvider } = createSimpleContext({
name: "Language",
init: () => {
@@ -184,15 +188,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont
}),
)
const locale = createMemo<Locale>(() =>
LOCALES.includes(store.locale as Locale) ? (store.locale as Locale) : "en",
)
createEffect(() => {
const current = locale()
if (store.locale === current) return
setStore("locale", current)
})
const locale = createMemo<Locale>(() => normalizeLocale(store.locale))
const dict = createMemo<Dictionary>(() => DICT[locale()])
@@ -213,7 +209,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont
label,
t,
setLocale(next: Locale) {
setStore("locale", next)
setStore("locale", normalizeLocale(next))
},
}
},

View File

@@ -1,5 +1,5 @@
import { createSimpleContext } from "@opencode-ai/ui/context"
import { AsyncStorage, SyncStorage } from "@solid-primitives/storage"
import type { AsyncStorage, SyncStorage } from "@solid-primitives/storage"
import type { Accessor } from "solid-js"
type PickerPaths = string | string[] | null
@@ -58,7 +58,7 @@ export type Platform = {
fetch?: typeof fetch
/** Get the configured default server URL (platform-specific) */
getDefaultServerUrl?(): Promise<string | null> | string | null
getDefaultServerUrl?(): Promise<string | null>
/** Set the default server URL to use on app startup (platform-specific) */
setDefaultServerUrl?(url: string | null): Promise<void> | void

View File

@@ -102,15 +102,19 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext(
}),
)
const allServers = createMemo(
(): Array<ServerConnection.Any> => [
const allServers = createMemo((): Array<ServerConnection.Any> => {
const servers = [
...(props.servers ?? []),
...store.list.map((value) => ({
type: "http" as const,
http: typeof value === "string" ? { url: value } : value,
})),
],
)
]
const deduped = new Map(servers.map((conn) => [ServerConnection.key(conn), conn]))
return [...deduped.values()]
})
const [state, setState] = createStore({
active: props.defaultServer,

View File

@@ -106,7 +106,7 @@ const platform: Platform = {
forward,
restart,
notify,
getDefaultServerUrl: readDefaultServerUrl,
getDefaultServerUrl: async () => readDefaultServerUrl(),
setDefaultServerUrl: writeDefaultServerUrl,
}

View File

@@ -63,8 +63,8 @@ export const dict = {
"command.agent.cycle.reverse.description": "التبديل إلى الوكيل السابق",
"command.model.variant.cycle": "تغيير جهد التفكير",
"command.model.variant.cycle.description": "التبديل إلى مستوى الجهد التالي",
"command.prompt.mode.shell": "التبديل إلى وضع Shell",
"command.prompt.mode.normal": "التبديل إلى وضع Prompt",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
"command.permissions.autoaccept.enable": "قبول التعديلات تلقائيًا",
"command.permissions.autoaccept.disable": "إيقاف قبول التعديلات تلقائيًا",
"command.workspace.toggle": "تبديل مساحات العمل",

View File

@@ -63,8 +63,8 @@ export const dict = {
"command.agent.cycle.reverse.description": "Mudar para o agente anterior",
"command.model.variant.cycle": "Alternar nível de raciocínio",
"command.model.variant.cycle.description": "Mudar para o próximo nível de esforço",
"command.prompt.mode.shell": "Alternar para o modo Shell",
"command.prompt.mode.normal": "Alternar para o modo Prompt",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
"command.permissions.autoaccept.enable": "Aceitar edições automaticamente",
"command.permissions.autoaccept.disable": "Parar de aceitar edições automaticamente",
"command.workspace.toggle": "Alternar espaços de trabalho",

View File

@@ -69,8 +69,8 @@ export const dict = {
"command.agent.cycle.reverse.description": "Prebaci na prethodnog agenta",
"command.model.variant.cycle": "Promijeni nivo razmišljanja",
"command.model.variant.cycle.description": "Prebaci na sljedeći nivo",
"command.prompt.mode.shell": "Prebaci na Shell način",
"command.prompt.mode.normal": "Prebaci na Prompt način",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
"command.permissions.autoaccept.enable": "Automatski prihvataj izmjene",
"command.permissions.autoaccept.disable": "Zaustavi automatsko prihvatanje izmjena",
"command.workspace.toggle": "Prikaži/sakrij radne prostore",

View File

@@ -69,8 +69,8 @@ export const dict = {
"command.agent.cycle.reverse.description": "Skift til forrige agent",
"command.model.variant.cycle": "Skift tænkeindsats",
"command.model.variant.cycle.description": "Skift til næste indsatsniveau",
"command.prompt.mode.shell": "Skift til shell-tilstand",
"command.prompt.mode.normal": "Skift til prompt-tilstand",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
"command.permissions.autoaccept.enable": "Accepter ændringer automatisk",
"command.permissions.autoaccept.disable": "Stop automatisk accept af ændringer",
"command.workspace.toggle": "Skift arbejdsområder",

View File

@@ -67,8 +67,8 @@ export const dict = {
"command.agent.cycle.reverse.description": "Zum vorherigen Agenten wechseln",
"command.model.variant.cycle": "Denkaufwand wechseln",
"command.model.variant.cycle.description": "Zum nächsten Aufwandslevel wechseln",
"command.prompt.mode.shell": "In den Shell-Modus wechseln",
"command.prompt.mode.normal": "In den Prompt-Modus wechseln",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
"command.permissions.autoaccept.enable": "Änderungen automatisch akzeptieren",
"command.permissions.autoaccept.disable": "Automatische Annahme von Änderungen stoppen",
"command.workspace.toggle": "Arbeitsbereiche umschalten",

View File

@@ -69,8 +69,8 @@ export const dict = {
"command.agent.cycle.reverse.description": "Switch to the previous agent",
"command.model.variant.cycle": "Cycle thinking effort",
"command.model.variant.cycle.description": "Switch to the next effort level",
"command.prompt.mode.shell": "Switch to shell mode",
"command.prompt.mode.normal": "Switch to prompt mode",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
"command.permissions.autoaccept.enable": "Auto-accept edits",
"command.permissions.autoaccept.disable": "Stop auto-accepting edits",
"command.workspace.toggle": "Toggle workspaces",

View File

@@ -69,8 +69,8 @@ export const dict = {
"command.agent.cycle.reverse.description": "Cambiar al agente anterior",
"command.model.variant.cycle": "Alternar esfuerzo de pensamiento",
"command.model.variant.cycle.description": "Cambiar al siguiente nivel de esfuerzo",
"command.prompt.mode.shell": "Cambiar al modo Shell",
"command.prompt.mode.normal": "Cambiar al modo Prompt",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
"command.permissions.autoaccept.enable": "Aceptar ediciones automáticamente",
"command.permissions.autoaccept.disable": "Dejar de aceptar ediciones automáticamente",
"command.workspace.toggle": "Alternar espacios de trabajo",

View File

@@ -63,8 +63,8 @@ export const dict = {
"command.agent.cycle.reverse.description": "Passer à l'agent précédent",
"command.model.variant.cycle": "Changer l'effort de réflexion",
"command.model.variant.cycle.description": "Passer au niveau d'effort suivant",
"command.prompt.mode.shell": "Passer en mode Shell",
"command.prompt.mode.normal": "Passer en mode Prompt",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
"command.permissions.autoaccept.enable": "Accepter automatiquement les modifications",
"command.permissions.autoaccept.disable": "Arrêter l'acceptation automatique des modifications",
"command.workspace.toggle": "Basculer les espaces de travail",

View File

@@ -63,8 +63,8 @@ export const dict = {
"command.agent.cycle.reverse.description": "前のエージェントに切り替え",
"command.model.variant.cycle": "思考レベルの切り替え",
"command.model.variant.cycle.description": "次の思考レベルに切り替え",
"command.prompt.mode.shell": "シェルモードに切り替える",
"command.prompt.mode.normal": "プロンプトモードに切り替える",
"command.prompt.mode.shell": "シェル",
"command.prompt.mode.normal": "プロンプト",
"command.permissions.autoaccept.enable": "編集を自動承認",
"command.permissions.autoaccept.disable": "編集の自動承認を停止",
"command.workspace.toggle": "ワークスペースを切り替え",

View File

@@ -67,8 +67,8 @@ export const dict = {
"command.agent.cycle.reverse.description": "이전 에이전트로 전환",
"command.model.variant.cycle": "생각 수준 순환",
"command.model.variant.cycle.description": "다음 생각 수준으로 전환",
"command.prompt.mode.shell": "셸 모드로 전환",
"command.prompt.mode.normal": "프롬프트 모드로 전환",
"command.prompt.mode.shell": "셸",
"command.prompt.mode.normal": "프롬프트",
"command.permissions.autoaccept.enable": "편집 자동 수락",
"command.permissions.autoaccept.disable": "편집 자동 수락 중지",
"command.workspace.toggle": "작업 공간 전환",

View File

@@ -72,8 +72,8 @@ export const dict = {
"command.agent.cycle.reverse.description": "Bytt til forrige agent",
"command.model.variant.cycle": "Bytt tenkeinnsats",
"command.model.variant.cycle.description": "Bytt til neste innsatsnivå",
"command.prompt.mode.shell": "Bytt til Shell-modus",
"command.prompt.mode.normal": "Bytt til Prompt-modus",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
"command.permissions.autoaccept.enable": "Godta endringer automatisk",
"command.permissions.autoaccept.disable": "Slutt å godta endringer automatisk",
"command.workspace.toggle": "Veksle arbeidsområder",

View File

@@ -63,8 +63,8 @@ export const dict = {
"command.agent.cycle.reverse.description": "Przełącz na poprzedniego agenta",
"command.model.variant.cycle": "Przełącz wysiłek myślowy",
"command.model.variant.cycle.description": "Przełącz na następny poziom wysiłku",
"command.prompt.mode.shell": "Przełącz na tryb terminala",
"command.prompt.mode.normal": "Przełącz na tryb Prompt",
"command.prompt.mode.shell": "Terminal",
"command.prompt.mode.normal": "Prompt",
"command.permissions.autoaccept.enable": "Automatyczne akceptowanie edycji",
"command.permissions.autoaccept.disable": "Zatrzymaj automatyczne akceptowanie edycji",
"command.workspace.toggle": "Przełącz przestrzenie robocze",

View File

@@ -69,8 +69,8 @@ export const dict = {
"command.agent.cycle.reverse.description": "Переключиться к предыдущему агенту",
"command.model.variant.cycle": "Цикл режимов мышления",
"command.model.variant.cycle.description": "Переключиться к следующему уровню усилий",
"command.prompt.mode.shell": "Переключиться в режим оболочки",
"command.prompt.mode.normal": ереключиться в режим промпта",
"command.prompt.mode.shell": "Оболочка",
"command.prompt.mode.normal": "Промпт",
"command.permissions.autoaccept.enable": "Авто-принятие изменений",
"command.permissions.autoaccept.disable": "Прекратить авто-принятие изменений",
"command.workspace.toggle": "Переключить рабочие пространства",

View File

@@ -69,8 +69,8 @@ export const dict = {
"command.agent.cycle.reverse.description": "สลับไปยังเอเจนต์ก่อนหน้า",
"command.model.variant.cycle": "เปลี่ยนความพยายามในการคิด",
"command.model.variant.cycle.description": "สลับไปยังระดับความพยายามถัดไป",
"command.prompt.mode.shell": "สลับไปยังโหมดเชลล์",
"command.prompt.mode.normal": "สลับไปยังโหมดพรอมต์",
"command.prompt.mode.shell": "เชลล์",
"command.prompt.mode.normal": "พรอมต์",
"command.permissions.autoaccept.enable": "ยอมรับการแก้ไขโดยอัตโนมัติ",
"command.permissions.autoaccept.disable": "หยุดยอมรับการแก้ไขโดยอัตโนมัติ",
"command.workspace.toggle": "สลับพื้นที่ทำงาน",

View File

@@ -93,8 +93,8 @@ export const dict = {
"command.model.variant.cycle": "切换思考强度",
"command.model.variant.cycle.description": "切换到下一个强度等级",
"command.prompt.mode.shell": "切换到 Shell 模式",
"command.prompt.mode.normal": "切换到 Prompt 模式",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
"command.permissions.autoaccept.enable": "自动接受编辑",
"command.permissions.autoaccept.disable": "停止自动接受编辑",

View File

@@ -73,8 +73,8 @@ export const dict = {
"command.agent.cycle.reverse.description": "切換到上一個代理程式",
"command.model.variant.cycle": "循環思考強度",
"command.model.variant.cycle.description": "切換到下一個強度等級",
"command.prompt.mode.shell": "切換到 Shell 模式",
"command.prompt.mode.normal": "切換到 Prompt 模式",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
"command.permissions.autoaccept.enable": "自動接受編輯",
"command.permissions.autoaccept.disable": "停止自動接受編輯",
"command.workspace.toggle": "切換工作區",

View File

@@ -177,7 +177,12 @@ export default function Layout(props: ParentProps) {
const sidebarHovering = createMemo(() => !layout.sidebar.opened() && state.hoverProject !== undefined)
const sidebarExpanded = createMemo(() => layout.sidebar.opened() || sidebarHovering())
const clearHoverProjectSoon = () => queueMicrotask(() => setState("hoverProject", undefined))
const setHoverProject = (value: string | undefined) => {
setState("hoverProject", value)
if (value !== undefined) return
aim.reset()
}
const clearHoverProjectSoon = () => queueMicrotask(() => setHoverProject(undefined))
const setHoverSession = (id: string | undefined) => setState("hoverSession", id)
const hoverProjectData = createMemo(() => {
@@ -188,13 +193,7 @@ export default function Layout(props: ParentProps) {
createEffect(() => {
if (!layout.sidebar.opened()) return
aim.reset()
setState("hoverProject", undefined)
})
createEffect(() => {
if (state.hoverProject !== undefined) return
aim.reset()
setHoverProject(undefined)
})
const autoselecting = createMemo(() => {
@@ -225,7 +224,7 @@ export default function Layout(props: ParentProps) {
const clearSidebarHoverState = () => {
if (layout.sidebar.opened()) return
setState("hoverSession", undefined)
setState("hoverProject", undefined)
setHoverProject(undefined)
}
const navigateWithSidebarReset = (href: string) => {
@@ -1490,7 +1489,7 @@ export default function Layout(props: ParentProps) {
function handleDragStart(event: unknown) {
const id = getDraggableId(event)
if (!id) return
setState("hoverProject", undefined)
setHoverProject(undefined)
setStore("activeProject", id)
}
@@ -1924,7 +1923,7 @@ export default function Layout(props: ParentProps) {
if (navLeave.current !== undefined) clearTimeout(navLeave.current)
navLeave.current = window.setTimeout(() => {
navLeave.current = undefined
setState("hoverProject", undefined)
setHoverProject(undefined)
setState("hoverSession", undefined)
}, 300)
}}

View File

@@ -1,4 +1,4 @@
import { onCleanup, Show, Match, Switch, createMemo, createEffect, on } from "solid-js"
import { onCleanup, Show, Match, Switch, createMemo, createEffect, on, onMount } from "solid-js"
import { createMediaQuery } from "@solid-primitives/media"
import { createResizeObserver } from "@solid-primitives/resize-observer"
import { useLocal } from "@/context/local"
@@ -981,7 +981,7 @@ export default function Page() {
consumePendingMessage: layout.pendingMessage.consume,
})
createEffect(() => {
onMount(() => {
document.addEventListener("keydown", handleKeyDown)
})

View File

@@ -168,6 +168,13 @@ export function FileTabContent(props: { tab: string }) {
draftTop: undefined as number | undefined,
})
const setCommenting = (range: SelectedLineRange | null) => {
setNote("commenting", range)
scheduleComments()
if (!range) return
setNote("draft", "")
}
const getRoot = () => {
const el = wrap
if (!el) return
@@ -260,13 +267,6 @@ export function FileTabContent(props: { tab: string }) {
scheduleComments()
})
createEffect(() => {
const range = note.commenting
scheduleComments()
if (!range) return
setNote("draft", "")
})
createEffect(() => {
const focus = comments.focus()
const p = path()
@@ -278,7 +278,7 @@ export function FileTabContent(props: { tab: string }) {
if (!target) return
setNote("openedComment", target.id)
setNote("commenting", null)
setCommenting(null)
file.setSelectedLines(p, target.selection)
requestAnimationFrame(() => comments.clearFocus())
})
@@ -438,16 +438,16 @@ export function FileTabContent(props: { tab: string }) {
const p = path()
if (!p) return
file.setSelectedLines(p, range)
if (!range) setNote("commenting", null)
if (!range) setCommenting(null)
}}
onLineSelectionEnd={(range: SelectedLineRange | null) => {
if (!range) {
setNote("commenting", null)
setCommenting(null)
return
}
setNote("openedComment", null)
setNote("commenting", range)
setCommenting(range)
}}
overflow="scroll"
class="select-text"
@@ -468,7 +468,7 @@ export function FileTabContent(props: { tab: string }) {
onClick={() => {
const p = path()
if (!p) return
setNote("commenting", null)
setCommenting(null)
setNote("openedComment", (current) => (current === comment.id ? null : comment.id))
file.setSelectedLines(p, comment.selection)
}}
@@ -483,12 +483,12 @@ export function FileTabContent(props: { tab: string }) {
value={note.draft}
selection={formatCommentLabel(range())}
onInput={(value) => setNote("draft", value)}
onCancel={() => setNote("commenting", null)}
onCancel={() => setCommenting(null)}
onSubmit={(value) => {
const p = path()
if (!p) return
addCommentToContext({ file: p, selection: range(), comment: value, origin: "file" })
setNote("commenting", null)
setCommenting(null)
}}
onPopoverFocusOut={(e: FocusEvent) => {
const current = e.currentTarget as HTMLDivElement
@@ -497,7 +497,7 @@ export function FileTabContent(props: { tab: string }) {
setTimeout(() => {
if (!document.activeElement || !current.contains(document.activeElement)) {
setNote("commenting", null)
setCommenting(null)
}
}, 0)
}}

View File

@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test"
import { createOpenReviewFile, focusTerminalById, getTabReorderIndex } from "./helpers"
import { createOpenReviewFile, createOpenSessionFileTab, focusTerminalById, getTabReorderIndex } from "./helpers"
describe("createOpenReviewFile", () => {
test("opens and loads selected review file", () => {
@@ -20,6 +20,37 @@ describe("createOpenReviewFile", () => {
})
})
describe("createOpenSessionFileTab", () => {
test("activates the opened file tab", () => {
const calls: string[] = []
const openTab = createOpenSessionFileTab({
normalizeTab: (value) => {
calls.push(`normalize:${value}`)
return `file://${value}`
},
openTab: (tab) => calls.push(`open:${tab}`),
pathFromTab: (tab) => {
calls.push(`path:${tab}`)
return tab.slice("file://".length)
},
loadFile: (path) => calls.push(`load:${path}`),
openReviewPanel: () => calls.push("review"),
setActive: (tab) => calls.push(`active:${tab}`),
})
openTab("src/a.ts")
expect(calls).toEqual([
"normalize:src/a.ts",
"open:file://src/a.ts",
"path:file://src/a.ts",
"load:src/a.ts",
"review",
"active:file://src/a.ts",
])
})
})
describe("focusTerminalById", () => {
test("focuses textarea when present", () => {
document.body.innerHTML = `<div id="terminal-wrapper-one"><div data-component="terminal"><textarea></textarea></div></div>`

View File

@@ -35,6 +35,27 @@ export const createOpenReviewFile = (input: {
}
}
export const createOpenSessionFileTab = (input: {
normalizeTab: (tab: string) => string
openTab: (tab: string) => void
pathFromTab: (tab: string) => string | undefined
loadFile: (path: string) => void
openReviewPanel: () => void
setActive: (tab: string) => void
}) => {
return (value: string) => {
const next = input.normalizeTab(value)
input.openTab(next)
const path = input.pathFromTab(next)
if (!path) return
input.loadFile(path)
input.openReviewPanel()
input.setActive(next)
}
}
export const getTabReorderIndex = (tabs: readonly string[], from: string, to: string) => {
const fromIndex = tabs.indexOf(from)
const toIndex = tabs.indexOf(to)

View File

@@ -366,7 +366,10 @@ export function MessageTimeline(props: {
}}
onClick={props.onAutoScrollInteraction}
class="relative min-w-0 w-full h-full overflow-y-auto session-scroller"
style={{ "--session-title-height": showHeader() ? "40px" : "0px" }}
style={{
"--session-title-height": showHeader() ? "40px" : "0px",
"--sticky-accordion-top": showHeader() ? "64px" : "0px",
}}
>
<Show when={showHeader()}>
<div
@@ -535,7 +538,7 @@ export function MessageTimeline(props: {
classes={{
root: "min-w-0 w-full relative",
content: "flex flex-col justify-between !overflow-visible",
container: "w-full px-4 md:px-6",
container: "w-full px-4 md:px-5",
}}
/>
</div>

View File

@@ -144,8 +144,8 @@ export function SessionReviewTab(props: SessionReviewTabProps) {
onOpenChange={props.view().review.setOpen}
classes={{
root: props.classes?.root ?? "pb-6",
header: props.classes?.header ?? "px-6",
container: props.classes?.container ?? "px-6",
header: props.classes?.header ?? "px-3",
container: props.classes?.container ?? "px-3",
}}
diffs={props.diffs()}
diffStyle={props.diffStyle}

View File

@@ -70,29 +70,28 @@ export function SessionPromptDock(props: {
setSessionHandoff(sessionKey(), { prompt: previewPrompt() })
})
const [responding, setResponding] = createSignal(false)
createEffect(
on(
() => permissionRequest()?.id,
() => setResponding(false),
{ defer: true },
),
)
const [responding, setResponding] = createSignal<string | undefined>()
const permissionResponding = () => {
const perm = permissionRequest()
if (!perm) return false
return responding() === perm.id
}
const decide = (response: "once" | "always" | "reject") => {
const perm = permissionRequest()
if (!perm) return
if (responding()) return
if (responding() === perm.id) return
setResponding(true)
setResponding(perm.id)
sdk.client.permission
.respond({ sessionID: perm.sessionID, permissionID: perm.id, response })
.catch((err: unknown) => {
const message = err instanceof Error ? err.message : String(err)
showToast({ title: language.t("common.requestFailed"), description: message })
})
.finally(() => setResponding(false))
.finally(() => {
setResponding((id) => (id === perm.id ? undefined : id))
})
}
const done = createMemo(
@@ -175,11 +174,11 @@ export function SessionPromptDock(props: {
<div
ref={props.setPromptDockRef}
data-component="session-prompt-dock"
class="shrink-0 w-full pb-4 flex flex-col justify-center items-center bg-background-stronger pointer-events-none"
class="shrink-0 w-full pb-3 flex flex-col justify-center items-center bg-background-stronger pointer-events-none"
>
<div
classList={{
"w-full px-4 pointer-events-auto": true,
"w-full px-3 pointer-events-auto": true,
"md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered,
}}
>
@@ -218,18 +217,28 @@ export function SessionPromptDock(props: {
<>
<div />
<div data-slot="permission-footer-actions">
<Button variant="ghost" size="normal" onClick={() => decide("reject")} disabled={responding()}>
<Button
variant="ghost"
size="normal"
onClick={() => decide("reject")}
disabled={permissionResponding()}
>
{language.t("ui.permission.deny")}
</Button>
<Button
variant="secondary"
size="normal"
onClick={() => decide("always")}
disabled={responding()}
disabled={permissionResponding()}
>
{language.t("ui.permission.allowAlways")}
</Button>
<Button variant="primary" size="normal" onClick={() => decide("once")} disabled={responding()}>
<Button
variant="primary"
size="normal"
onClick={() => decide("once")}
disabled={permissionResponding()}
>
{language.t("ui.permission.allowOnce")}
</Button>
</div>

View File

@@ -23,7 +23,7 @@ import { useLayout } from "@/context/layout"
import { useSync } from "@/context/sync"
import { createFileTabListSync } from "@/pages/session/file-tab-scroll"
import { FileTabContent } from "@/pages/session/file-tabs"
import { getTabReorderIndex } from "@/pages/session/helpers"
import { createOpenSessionFileTab, getTabReorderIndex } from "@/pages/session/helpers"
import { StickyAddButton } from "@/pages/session/review-tab"
import { setSessionHandoff } from "@/pages/session/handoff"
@@ -96,15 +96,14 @@ export function SessionSidePanel(props: {
if (!view().reviewPanel.opened()) view().reviewPanel.open()
}
const openTab = (value: string) => {
const next = normalizeTab(value)
tabs().open(next)
const path = file.pathFromTab(next)
if (!path) return
file.load(path)
openReviewPanel()
}
const openTab = createOpenSessionFileTab({
normalizeTab,
openTab: tabs().open,
pathFromTab: file.pathFromTab,
loadFile: file.load,
openReviewPanel,
setActive: tabs().setActive,
})
const contextOpen = createMemo(() => tabs().active() === "context" || tabs().all().includes("context"))
const openedTabs = createMemo(() =>
@@ -355,7 +354,7 @@ export function SessionSidePanel(props: {
{language.t("session.files.all")}
</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="changes" class="bg-background-base px-3 py-0">
<Tabs.Content value="changes" class="bg-background-stronger px-3 py-0">
<Switch>
<Match when={hasReview()}>
<Show
@@ -384,7 +383,7 @@ export function SessionSidePanel(props: {
</Match>
</Switch>
</Tabs.Content>
<Tabs.Content value="all" class="bg-background-base px-3 py-0">
<Tabs.Content value="all" class="bg-background-stronger px-3 py-0">
<FileTree
path=""
modified={diffFiles()}

View File

@@ -38,9 +38,34 @@ export function TerminalPanel() {
const [store, setStore] = createStore({
autoCreated: false,
everOpened: false,
activeDraggable: undefined as string | undefined,
})
const rendered = createMemo(() => isDesktop() && (opened() || store.everOpened))
createEffect(
on(open, (isOpen, prev) => {
if (isOpen) {
if (!store.everOpened) setStore("everOpened", true)
const activeId = terminal.active()
if (!activeId) return
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur()
}
setTimeout(() => focusTerminalById(activeId), 0)
return
}
if (!prev) return
const panel = document.getElementById("terminal-panel")
const activeElement = document.activeElement
if (!panel || !(activeElement instanceof HTMLElement)) return
if (!panel.contains(activeElement)) return
activeElement.blur()
}),
)
createEffect(() => {
if (!opened()) {
setStore("autoCreated", false)
@@ -67,7 +92,7 @@ export function TerminalPanel() {
on(
() => terminal.active(),
(activeId) => {
if (!activeId || !opened()) return
if (!activeId || !open()) return
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur()
}
@@ -133,23 +158,32 @@ export function TerminalPanel() {
}
return (
<Show when={open()}>
<Show when={rendered()}>
<div
id="terminal-panel"
role="region"
aria-label={language.t("terminal.title")}
class="relative w-full flex flex-col shrink-0 border-t border-border-weak-base"
style={{ height: `${height()}px` }}
classList={{
"relative w-full flex flex-col shrink-0 overflow-hidden": true,
"border-t border-border-weak-base": open(),
"pointer-events-none": !open(),
}}
style={{
height: `${height()}px`,
display: open() ? "flex" : "none",
}}
>
<ResizeHandle
direction="vertical"
size={height()}
min={100}
max={typeof window === "undefined" ? 1000 : window.innerHeight * 0.6}
collapseThreshold={50}
onResize={layout.terminal.resize}
onCollapse={close}
/>
<Show when={open()}>
<ResizeHandle
direction="vertical"
size={height()}
min={100}
max={typeof window === "undefined" ? 1000 : window.innerHeight * 0.6}
collapseThreshold={50}
onResize={layout.terminal.resize}
onCollapse={close}
/>
</Show>
<Show
when={terminal.ready()}
fallback={

View File

@@ -3136,6 +3136,7 @@ dependencies = [
"tracing-subscriber",
"uuid",
"webkit2gtk",
"windows 0.62.2",
]
[[package]]

View File

@@ -54,6 +54,9 @@ chrono = "0.4"
tokio-stream = { version = "0.1.18", features = ["sync"] }
process-wrap = { version = "9.0.3", features = ["tokio1"] }
[target.'cfg(windows)'.dependencies]
windows = { version = "0.62", features = ["Win32_System_Threading"] }
[target.'cfg(target_os = "linux")'.dependencies]
gtk = "0.18.2"
webkit2gtk = "=2.0.2"

View File

@@ -3,7 +3,7 @@ use process_wrap::tokio::CommandWrap;
#[cfg(unix)]
use process_wrap::tokio::ProcessGroup;
#[cfg(windows)]
use process_wrap::tokio::{JobObject, KillOnDrop};
use process_wrap::tokio::{CommandWrapper, JobObject, KillOnDrop};
#[cfg(unix)]
use std::os::unix::process::ExitStatusExt;
use std::sync::Arc;
@@ -18,9 +18,24 @@ use tokio::{
};
use tokio_stream::wrappers::ReceiverStream;
use tracing::Instrument;
#[cfg(windows)]
use windows::Win32::System::Threading::{CREATE_NO_WINDOW, CREATE_SUSPENDED};
use crate::server::get_wsl_config;
#[cfg(windows)]
#[derive(Clone, Copy, Debug)]
// Keep this as a custom wrapper instead of process_wrap::CreationFlags.
// JobObject pre_spawn rewrites creation flags, so this must run after it.
struct WinCreationFlags;
#[cfg(windows)]
impl CommandWrapper for WinCreationFlags {
fn pre_spawn(&mut self, command: &mut Command, _core: &CommandWrap) -> std::io::Result<()> {
command.creation_flags((CREATE_NO_WINDOW | CREATE_SUSPENDED).0);
Ok(())
}
}
const CLI_INSTALL_DIR: &str = ".opencode/bin";
const CLI_BINARY_NAME: &str = "opencode";
@@ -203,7 +218,7 @@ fn get_user_shell() -> String {
}
fn is_wsl_enabled(_app: &tauri::AppHandle) -> bool {
get_wsl_config(_app.clone()).is_ok_and(|v| v.enabled)
get_wsl_config(_app.clone()).is_ok_and(|v| v.enabled)
}
fn shell_escape(input: &str) -> String {
@@ -318,9 +333,6 @@ pub fn spawn_command(
cmd.stderr(Stdio::piped());
cmd.stdin(Stdio::null());
#[cfg(windows)]
cmd.creation_flags(0x0800_0000);
let mut wrap = CommandWrap::from(cmd);
#[cfg(unix)]
@@ -330,7 +342,7 @@ pub fn spawn_command(
#[cfg(windows)]
{
wrap.wrap(JobObject).wrap(KillOnDrop);
wrap.wrap(JobObject).wrap(WinCreationFlags).wrap(KillOnDrop);
}
let mut child = wrap.spawn()?;

View File

@@ -426,6 +426,12 @@ void listenForDeepLinks()
render(() => {
const platform = createPlatform()
const [defaultServer] = createResource(() =>
platform.getDefaultServerUrl?.().then((url) => {
if (url) return ServerConnection.key({ type: "http", http: { url } })
}),
)
function handleClick(e: MouseEvent) {
const link = (e.target as HTMLElement).closest("a.external-link") as HTMLAnchorElement | null
if (link?.href) {
@@ -466,9 +472,13 @@ render(() => {
}
return (
<AppInterface defaultServer={ServerConnection.key(server)} servers={[server]}>
<Inner />
</AppInterface>
<Show when={defaultServer.loading ? false : defaultServer.latest}>
{(defaultServer) => (
<AppInterface defaultServer={defaultServer() ?? ServerConnection.key(server)} servers={[server]}>
<Inner />
</AppInterface>
)}
</Show>
)
}}
</ServerGate>

View File

@@ -74,6 +74,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",
@@ -106,6 +107,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:",

View File

@@ -30,6 +30,7 @@ import {
import { Log } from "../util/log"
import { pathToFileURL } from "bun"
import { Filesystem } from "../util/filesystem"
import { ACPSessionManager } from "./session"
import type { ACPConfig } from "./types"
import { Provider } from "../provider/provider"
@@ -228,8 +229,7 @@ export namespace ACP {
const metadata = permission.metadata || {}
const filepath = typeof metadata["filepath"] === "string" ? metadata["filepath"] : ""
const diff = typeof metadata["diff"] === "string" ? metadata["diff"] : ""
const file = Bun.file(filepath)
const content = (await file.exists()) ? await file.text() : ""
const content = (await Filesystem.exists(filepath)) ? await Filesystem.readText(filepath) : ""
const newContent = getNewContent(content, diff)
if (newContent) {

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

@@ -1,6 +1,7 @@
import path from "path"
import { Global } from "../global"
import z from "zod"
import { Filesystem } from "../util/filesystem"
export const OAUTH_DUMMY_KEY = "opencode-oauth-dummy-key"
@@ -42,8 +43,7 @@ export namespace Auth {
}
export async function all(): Promise<Record<string, Info>> {
const file = Bun.file(filepath)
const data = await file.json().catch(() => ({}) as Record<string, unknown>)
const data = await Filesystem.readJson<Record<string, unknown>>(filepath).catch(() => ({}))
return Object.entries(data).reduce(
(acc, [key, value]) => {
const parsed = Info.safeParse(value)
@@ -56,15 +56,13 @@ export namespace Auth {
}
export async function set(key: string, info: Info) {
const file = Bun.file(filepath)
const data = await all()
await Bun.write(file, JSON.stringify({ ...data, [key]: info }, null, 2), { mode: 0o600 })
await Filesystem.writeJson(filepath, { ...data, [key]: info }, 0o600)
}
export async function remove(key: string) {
const file = Bun.file(filepath)
const data = await all()
delete data[key]
await Bun.write(file, JSON.stringify(data, null, 2), { mode: 0o600 })
await Filesystem.writeJson(filepath, data, 0o600)
}
}

View File

@@ -6,6 +6,7 @@ import { Agent } from "../../agent/agent"
import { Provider } from "../../provider/provider"
import path from "path"
import fs from "fs/promises"
import { Filesystem } from "../../util/filesystem"
import matter from "gray-matter"
import { Instance } from "../../project/instance"
import { EOL } from "os"
@@ -202,8 +203,7 @@ const AgentCreateCommand = cmd({
await fs.mkdir(targetPath, { recursive: true })
const file = Bun.file(filePath)
if (await file.exists()) {
if (await Filesystem.exists(filePath)) {
if (isFullyNonInteractive) {
console.error(`Error: Agent file already exists: ${filePath}`)
process.exit(1)
@@ -212,7 +212,7 @@ const AgentCreateCommand = cmd({
throw new UI.CancelledError()
}
await Bun.write(filePath, content)
await Filesystem.write(filePath, content)
if (isFullyNonInteractive) {
console.log(filePath)

View File

@@ -8,6 +8,7 @@ import { SessionTable, MessageTable, PartTable } from "../../session/session.sql
import { Instance } from "../../project/instance"
import { ShareNext } from "../../share/share-next"
import { EOL } from "os"
import { Filesystem } from "../../util/filesystem"
/** Discriminated union returned by the ShareNext API (GET /api/share/:id/data) */
export type ShareData =
@@ -116,8 +117,7 @@ export const ImportCommand = cmd({
exportData = transformed
} else {
const file = Bun.file(args.file)
exportData = await file.json().catch(() => {})
exportData = await Filesystem.readJson<NonNullable<typeof exportData>>(args.file).catch(() => undefined)
if (!exportData) {
process.stdout.write(`File not found: ${args.file}`)
process.stdout.write(EOL)

View File

@@ -168,12 +168,17 @@ function websearch(info: ToolProps<typeof WebSearchTool>) {
}
function task(info: ToolProps<typeof TaskTool>) {
const agent = Locale.titlecase(info.input.subagent_type)
const desc = info.input.description
const started = info.part.state.status === "running"
const input = info.part.state.input
const status = info.part.state.status
const subagent =
typeof input.subagent_type === "string" && input.subagent_type.trim().length > 0 ? input.subagent_type : "unknown"
const agent = Locale.titlecase(subagent)
const desc =
typeof input.description === "string" && input.description.trim().length > 0 ? input.description : undefined
const icon = status === "error" ? "✗" : status === "running" ? "•" : "✓"
const name = desc ?? `${agent} Task`
inline({
icon: started ? "•" : "✓",
icon,
title: name,
description: desc ? `${agent} Agent` : undefined,
})
@@ -360,6 +365,11 @@ export const RunCommand = cmd({
action: "deny",
pattern: "*",
},
{
permission: "edit",
action: "allow",
pattern: "*",
},
]
function title() {
@@ -451,9 +461,17 @@ export const RunCommand = cmd({
const part = event.properties.part
if (part.sessionID !== sessionID) continue
if (part.type === "tool" && part.state.status === "completed") {
if (part.type === "tool" && (part.state.status === "completed" || part.state.status === "error")) {
if (emit("tool_use", { part })) continue
tool(part)
if (part.state.status === "completed") {
tool(part)
continue
}
inline({
icon: "✗",
title: `${part.tool} failed`,
})
UI.error(part.state.error)
}
if (

View File

@@ -5,6 +5,7 @@ import { bootstrap } from "../bootstrap"
import { UI } from "../ui"
import { Locale } from "../../util/locale"
import { Flag } from "../../flag/flag"
import { Filesystem } from "../../util/filesystem"
import { EOL } from "os"
import path from "path"
@@ -17,18 +18,18 @@ function pagerCmd(): string[] {
// user could have less installed via other options
const lessOnPath = Bun.which("less")
if (lessOnPath) {
if (Bun.file(lessOnPath).size) return [lessOnPath, ...lessOptions]
if (Filesystem.stat(lessOnPath)?.size) return [lessOnPath, ...lessOptions]
}
if (Flag.OPENCODE_GIT_BASH_PATH) {
const less = path.join(Flag.OPENCODE_GIT_BASH_PATH, "..", "..", "usr", "bin", "less.exe")
if (Bun.file(less).size) return [less, ...lessOptions]
if (Filesystem.stat(less)?.size) return [less, ...lessOptions]
}
const git = Bun.which("git")
if (git) {
const less = path.join(git, "..", "..", "usr", "bin", "less.exe")
if (Bun.file(less).size) return [less, ...lessOptions]
if (Filesystem.stat(less)?.size) return [less, ...lessOptions]
}
// Fall back to Windows built-in more (via cmd.exe)

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

@@ -34,7 +34,6 @@ export const { use: useExit, provider: ExitProvider } = createSimpleContext({
renderer.setTerminalTitle("")
renderer.destroy()
win32FlushInputBuffer()
await input.onExit?.()
if (reason) {
const formatted = FormatError(reason) ?? FormatUnknownError(reason)
if (formatted) {
@@ -43,7 +42,7 @@ export const { use: useExit, provider: ExitProvider } = createSimpleContext({
}
const text = store.get()
if (text) process.stdout.write(text + "\n")
process.exit(0)
await input.onExit?.()
},
{
message: store,

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

@@ -104,6 +104,9 @@ export namespace UI {
}
export function error(message: string) {
if (message.startsWith("Error: ")) {
message = message.slice("Error: ".length)
}
println(Style.TEXT_DANGER_BOLD + "Error: " + Style.TEXT_NORMAL + message)
}

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

@@ -166,7 +166,6 @@ export namespace File {
"efi",
"rom",
"com",
"bat",
"cmd",
"ps1",
"sh",
@@ -203,11 +202,77 @@ export namespace File {
"x3f",
])
const textExtensions = new Set([
"ts",
"tsx",
"mts",
"cts",
"mtsx",
"ctsx",
"js",
"jsx",
"mjs",
"cjs",
"sh",
"bash",
"zsh",
"fish",
"ps1",
"psm1",
"cmd",
"bat",
"json",
"jsonc",
"json5",
"yaml",
"yml",
"toml",
"md",
"mdx",
"txt",
"xml",
"html",
"htm",
"css",
"scss",
"sass",
"less",
"graphql",
"gql",
"sql",
"ini",
"cfg",
"conf",
"env",
])
const textNames = new Set([
"dockerfile",
"makefile",
".gitignore",
".gitattributes",
".editorconfig",
".npmrc",
".nvmrc",
".prettierrc",
".eslintrc",
])
function isImageByExtension(filepath: string): boolean {
const ext = path.extname(filepath).toLowerCase().slice(1)
return imageExtensions.has(ext)
}
function isTextByExtension(filepath: string): boolean {
const ext = path.extname(filepath).toLowerCase().slice(1)
return textExtensions.has(ext)
}
function isTextByName(filepath: string): boolean {
const name = path.basename(filepath).toLowerCase()
return textNames.has(name)
}
function getImageMimeType(filepath: string): string {
const ext = path.extname(filepath).toLowerCase().slice(1)
const mimeTypes: Record<string, string> = {
@@ -445,7 +510,9 @@ export namespace File {
return { type: "text", content: "" }
}
if (isBinaryByExtension(file)) {
const text = isTextByExtension(file) || isTextByName(file)
if (isBinaryByExtension(file) && !text) {
return { type: "binary", content: "" }
}
@@ -454,7 +521,7 @@ export namespace File {
}
const mimeType = Filesystem.mimeType(full)
const encode = await shouldEncode(mimeType)
const encode = text ? false : await shouldEncode(mimeType)
if (encode && !isImage(mimeType)) {
return { type: "binary", content: "", mimeType }

View File

@@ -1,6 +1,7 @@
import { Instance } from "../project/instance"
import { Log } from "../util/log"
import { Flag } from "../flag/flag"
import { Filesystem } from "../util/filesystem"
export namespace FileTime {
const log = Log.create({ service: "file.time" })
@@ -59,10 +60,10 @@ export namespace FileTime {
const time = get(sessionID, filepath)
if (!time) throw new Error(`You must read file ${filepath} before overwriting it. Use the Read tool first`)
const stats = await Bun.file(filepath).stat()
if (stats.mtime.getTime() > time.getTime()) {
const mtime = Filesystem.stat(filepath)?.mtime
if (mtime && mtime.getTime() > time.getTime()) {
throw new Error(
`File ${filepath} has been modified since it was last read.\nLast modification: ${stats.mtime.toISOString()}\nLast read: ${time.toISOString()}\n\nPlease read the file again before modifying it.`,
`File ${filepath} has been modified since it was last read.\nLast modification: ${mtime.toISOString()}\nLast read: ${time.toISOString()}\n\nPlease read the file again before modifying it.`,
)
}
}

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

@@ -18,11 +18,16 @@ export namespace Pty {
type Socket = {
readyState: number
send: (data: string | Uint8Array<ArrayBuffer> | ArrayBuffer) => void
send: (data: string | Uint8Array | ArrayBuffer) => void
close: (code?: number, reason?: string) => void
}
type Subscriber = {
id: number
}
const sockets = new WeakMap<object, number>()
const owners = new WeakMap<object, string>()
let socketCounter = 0
const tagSocket = (ws: Socket) => {
@@ -32,7 +37,7 @@ export namespace Pty {
return next
}
// WebSocket control frame: 0x00 + UTF-8 JSON (currently { cursor }).
// WebSocket control frame: 0x00 + UTF-8 JSON.
const meta = (cursor: number) => {
const json = JSON.stringify({ cursor })
const bytes = encoder.encode(json)
@@ -96,7 +101,7 @@ export namespace Pty {
buffer: string
bufferCursor: number
cursor: number
subscribers: Map<Socket, number>
subscribers: Map<Socket, Subscriber>
}
const state = Instance.state(
@@ -176,26 +181,27 @@ export namespace Pty {
subscribers: new Map(),
}
state().set(id, session)
ptyProcess.onData((data) => {
session.cursor += data.length
ptyProcess.onData((chunk) => {
session.cursor += chunk.length
for (const [ws, id] of session.subscribers) {
for (const [ws, sub] of session.subscribers) {
if (ws.readyState !== 1) {
session.subscribers.delete(ws)
continue
}
if (typeof ws === "object" && sockets.get(ws) !== id) {
if (typeof ws === "object" && sockets.get(ws) !== sub.id) {
session.subscribers.delete(ws)
continue
}
try {
ws.send(data)
ws.send(chunk)
} catch {
session.subscribers.delete(ws)
}
}
session.buffer += data
session.buffer += chunk
if (session.buffer.length <= BUFFER_LIMIT) return
const excess = session.buffer.length - BUFFER_LIMIT
session.buffer = session.buffer.slice(excess)
@@ -273,6 +279,25 @@ export namespace Pty {
}
log.info("client connected to session", { id })
const socketId = tagSocket(ws)
if (socketId === undefined) {
ws.close()
return
}
const previous = owners.get(ws)
if (previous && previous !== id) {
state().get(previous)?.subscribers.delete(ws)
}
owners.set(ws, id)
session.subscribers.set(ws, { id: socketId })
const cleanup = () => {
session.subscribers.delete(ws)
if (owners.get(ws) === id) owners.delete(ws)
}
const start = session.bufferCursor
const end = session.cursor
@@ -293,6 +318,7 @@ export namespace Pty {
ws.send(data.slice(i, i + BUFFER_CHUNK))
}
} catch {
cleanup()
ws.close()
return
}
@@ -301,19 +327,17 @@ export namespace Pty {
try {
ws.send(meta(end))
} catch {
cleanup()
ws.close()
return
}
const socketId = tagSocket(ws)
if (typeof socketId === "number") session.subscribers.set(ws, socketId)
return {
onMessage: (message: string | ArrayBuffer) => {
session.process.write(String(message))
},
onClose: () => {
log.info("client disconnected from session", { id })
session.subscribers.delete(ws)
cleanup()
},
}
}

View File

@@ -6,6 +6,7 @@ import { Worktree } from "../../worktree"
import { Instance } from "../../project/instance"
import { Project } from "../../project/project"
import { MCP } from "../../mcp"
import { Session } from "../../session"
import { zodToJsonSchema } from "zod-to-json-schema"
import { errors } from "../error"
import { lazy } from "../../util/lazy"
@@ -184,6 +185,65 @@ export const ExperimentalRoutes = lazy(() =>
return c.json(true)
},
)
.get(
"/session",
describeRoute({
summary: "List sessions",
description:
"Get a list of all OpenCode sessions across projects, sorted by most recently updated. Archived sessions are excluded by default.",
operationId: "experimental.session.list",
responses: {
200: {
description: "List of sessions",
content: {
"application/json": {
schema: resolver(Session.GlobalInfo.array()),
},
},
},
},
}),
validator(
"query",
z.object({
directory: z.string().optional().meta({ description: "Filter sessions by project directory" }),
roots: z.coerce.boolean().optional().meta({ description: "Only return root sessions (no parentID)" }),
start: z.coerce
.number()
.optional()
.meta({ description: "Filter sessions updated on or after this timestamp (milliseconds since epoch)" }),
cursor: z.coerce
.number()
.optional()
.meta({ description: "Return sessions updated before this timestamp (milliseconds since epoch)" }),
search: z.string().optional().meta({ description: "Filter sessions by title (case-insensitive)" }),
limit: z.coerce.number().optional().meta({ description: "Maximum number of sessions to return" }),
archived: z.coerce.boolean().optional().meta({ description: "Include archived sessions (default false)" }),
}),
),
async (c) => {
const query = c.req.valid("query")
const limit = query.limit ?? 100
const sessions: Session.GlobalInfo[] = []
for await (const session of Session.listGlobal({
directory: query.directory,
roots: query.roots,
start: query.start,
cursor: query.cursor,
search: query.search,
limit: limit + 1,
archived: query.archived,
})) {
sessions.push(session)
}
const hasMore = sessions.length > limit
const list = hasMore ? sessions.slice(0, limit) : sessions
if (hasMore && list.length > 0) {
c.header("x-next-cursor", String(list[list.length - 1].time.updated))
}
return c.json(list)
},
)
.get(
"/resource",
describeRoute({

View File

@@ -163,7 +163,7 @@ export const PtyRoutes = lazy(() =>
type Socket = {
readyState: number
send: (data: string | Uint8Array<ArrayBuffer> | ArrayBuffer) => void
send: (data: string | Uint8Array | ArrayBuffer) => void
close: (code?: number, reason?: string) => void
}
@@ -177,11 +177,16 @@ export const PtyRoutes = lazy(() =>
return {
onOpen(_event, ws) {
const socket = isSocket(ws.raw) ? ws.raw : ws
const socket = ws.raw
if (!isSocket(socket)) {
ws.close()
return
}
handler = Pty.connect(id, socket, cursor)
},
onMessage(event) {
handler?.onMessage(String(event.data))
if (typeof event.data !== "string") return
handler?.onMessage(event.data)
},
onClose() {
handler?.onClose()

View File

@@ -10,8 +10,10 @@ import { Flag } from "../flag/flag"
import { Identifier } from "../id/id"
import { Installation } from "../installation"
import { Database, NotFoundError, eq, and, or, gte, isNull, desc, like } from "../storage/db"
import { Database, NotFoundError, eq, and, or, gte, isNull, desc, like, inArray, lt } from "../storage/db"
import type { SQL } from "../storage/db"
import { SessionTable, MessageTable, PartTable } from "./session.sql"
import { ProjectTable } from "../project/project.sql"
import { Storage } from "@/storage/storage"
import { Log } from "../util/log"
import { MessageV2 } from "./message-v2"
@@ -154,6 +156,24 @@ export namespace Session {
})
export type Info = z.output<typeof Info>
export const ProjectInfo = z
.object({
id: z.string(),
name: z.string().optional(),
worktree: z.string(),
})
.meta({
ref: "ProjectSummary",
})
export type ProjectInfo = z.output<typeof ProjectInfo>
export const GlobalInfo = Info.extend({
project: ProjectInfo.nullable(),
}).meta({
ref: "GlobalSession",
})
export type GlobalInfo = z.output<typeof GlobalInfo>
export const Event = {
Created: BusEvent.define(
"session.created",
@@ -544,6 +564,75 @@ export namespace Session {
}
}
export function* listGlobal(input?: {
directory?: string
roots?: boolean
start?: number
cursor?: number
search?: string
limit?: number
archived?: boolean
}) {
const conditions: SQL[] = []
if (input?.directory) {
conditions.push(eq(SessionTable.directory, input.directory))
}
if (input?.roots) {
conditions.push(isNull(SessionTable.parent_id))
}
if (input?.start) {
conditions.push(gte(SessionTable.time_updated, input.start))
}
if (input?.cursor) {
conditions.push(lt(SessionTable.time_updated, input.cursor))
}
if (input?.search) {
conditions.push(like(SessionTable.title, `%${input.search}%`))
}
if (!input?.archived) {
conditions.push(isNull(SessionTable.time_archived))
}
const limit = input?.limit ?? 100
const rows = Database.use((db) => {
const query =
conditions.length > 0
? db
.select()
.from(SessionTable)
.where(and(...conditions))
: db.select().from(SessionTable)
return query.orderBy(desc(SessionTable.time_updated), desc(SessionTable.id)).limit(limit).all()
})
const ids = [...new Set(rows.map((row) => row.project_id))]
const projects = new Map<string, ProjectInfo>()
if (ids.length > 0) {
const items = Database.use((db) =>
db
.select({ id: ProjectTable.id, name: ProjectTable.name, worktree: ProjectTable.worktree })
.from(ProjectTable)
.where(inArray(ProjectTable.id, ids))
.all(),
)
for (const item of items) {
projects.set(item.id, {
id: item.id,
name: item.name ?? undefined,
worktree: item.worktree,
})
}
}
for (const row of rows) {
const project = projects.get(row.project_id) ?? null
yield { ...fromRow(row), project }
}
}
export const children = fn(Identifier.schema("session"), async (parentID) => {
const project = Instance.project
const rows = Database.use((db) =>

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"),
@@ -1618,7 +1590,11 @@ NOTE: At any point in time through this workflow you should feel free to ask the
const args = matchingInvocation?.args
const cwd = Instance.directory
const shellEnv = await Plugin.trigger("shell.env", { cwd }, { env: {} })
const shellEnv = await Plugin.trigger(
"shell.env",
{ cwd, sessionID: input.sessionID, callID: part.callID },
{ env: {} },
)
const proc = spawn(shell, args, {
cwd,
detached: process.platform !== "win32",

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

@@ -163,7 +163,11 @@ export const BashTool = Tool.define("bash", async () => {
})
}
const shellEnv = await Plugin.trigger("shell.env", { cwd }, { env: {} })
const shellEnv = await Plugin.trigger(
"shell.env",
{ cwd, sessionID: ctx.sessionID, callID: ctx.callID },
{ env: {} },
)
const proc = spawn(params.command, {
shell,
cwd,

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.`

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