Compare commits

..

94 Commits

Author SHA1 Message Date
Gregory Schier
4b54c22012 Fix weird type recursion in MCP plugin 2026-01-04 15:46:05 -08:00
Gregory Schier
4f7e67b106 Fix listing installed filesystem plugins 2026-01-04 14:00:33 -08:00
Gregory Schier
8b637d53c4 Add configurable timeouts for plugin events
- Add timeout parameter to send_to_plugins_and_wait and send_to_plugin_and_wait
- Use 5 second timeout for standard operations (themes, actions, configs, etc.)
- Use 5 minute timeout for user-interactive operations:
  - Authentication actions (OAuth login flows)
  - Authentication requests (token refresh, OAuth)
  - Template function calls (credential prompts, OAuth, etc.)
- Fixes issue where auth flows would timeout after 5 seconds
2026-01-04 09:57:58 -08:00
Gregory Schier
00bf5920e3 Add configurable hotkeys support (#343) 2026-01-04 08:36:22 -08:00
Gregory Schier
58bf55704a Preserve sidebar item active color when showing context menu 2026-01-03 15:07:29 -08:00
Gregory Schier
c75d6b815e Fix sidebar hidden state being updated too frequently 2026-01-03 14:29:18 -08:00
Gregory Schier
35a57bf7f5 Add plugin API to open URL in external browser (#340)
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-03 13:53:07 -08:00
Gregory Schier
118b2faa76 Update checkout pr command with proper timeout 2026-01-03 13:52:20 -08:00
Gregory Schier
158164089f Update check-out-pr.md 2026-01-03 13:30:43 -08:00
Gregory Schier
4cd4cb5722 Add check-out-pr claude command 2026-01-03 09:41:19 -08:00
Gregory Schier
52f7447f85 Support running multiple Yaak instances via git worktrees (#341) 2026-01-03 09:31:35 -08:00
Gregory Schier
11694921e3 Better plugin error handling 2026-01-02 10:20:44 -08:00
Gregory Schier
0146ee586f Notify of plugin updates and add update UX (#339) 2026-01-02 10:03:08 -08:00
Gregory Schier
e751167dfc Bump mcp server plugin version 2026-01-02 07:32:36 -08:00
Gregory Schier
2ccee0dc70 Better MCP server lifecycle 2026-01-02 07:31:54 -08:00
Gregory Schier
04eec0ee05 Fix weird type errors 2026-01-02 07:10:48 -08:00
Gregory Schier
7e239c0dd1 Fix colon in path name 2026-01-01 16:57:28 -08:00
Gregory Schier
f1783feafc Fix installed and bundled plugin tabs 2026-01-01 16:55:30 -08:00
Gregory Schier
ef187373c5 Fix plugin install 2026-01-01 16:44:00 -08:00
Gregory Schier
8da3659be3 Restructure add plugin 2026-01-01 10:49:35 -08:00
Gregory Schier
4d2bf9304a Fix plugin installation from directory 2026-01-01 10:45:13 -08:00
Gregory Schier
d544899f39 Add body and auth support to MCP HTTP request tools
- Add body, bodyType, authentication, and authenticationType fields to create/update HTTP request MCP tools
- Include comprehensive documentation for body structures and auth types in Zod descriptions
- Fix MCP update_http_request to merge partial updates with existing data to prevent FK constraint violations
- Fix Zod imports from 'zod/v4' to 'zod' to match installed version
- Add uncaughtException handler to plugin runtime to prevent individual plugin crashes from crashing entire runtime
2026-01-01 10:33:28 -08:00
Gregory Schier
92a8da03af (feat) Add ability to disable plugins and show bundled plugins (#337) 2026-01-01 09:32:48 -08:00
Gregory Schier
07ea1ea7dc Fix lint errors 2025-12-31 16:17:24 -08:00
Gregory Schier
e435414c2e Update readme for mcp plugin 2025-12-31 11:15:53 -08:00
Gregory Schier
e4bd30eb01 Fix Nord themes 2025-12-31 11:01:40 -08:00
Gregory Schier
af3e672386 Claude command and add Nord Light 2025-12-31 10:55:28 -08:00
Gregory Schier
45be354625 Fix import 2025-12-31 10:33:19 -08:00
Gregory Schier
cd65ef8dbe Add VSCode themes plugin with 30+ popular themes (#336)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-31 10:05:55 -08:00
Gregory Schier
6b9b207e1c MCP Server Plugin (#335) 2025-12-31 08:41:57 -08:00
Gregory Schier
58eff84f43 Fix TypeScript lint errors in AudioViewer and VideoViewer
- Change from data.buffer to new Uint8Array(data) to fix ArrayBufferLike type compatibility with Blob constructor
- Fixes TS2322 errors about SharedArrayBuffer not being assignable to BlobPart
2025-12-29 11:00:46 -08:00
Gregory Schier
25d51a017e Implement custom cookie handling in HTTP transaction layer (#334) 2025-12-29 09:47:53 -08:00
Gregory Schier
f1a3ef1c11 Fix pdf viewer css 2025-12-28 15:39:52 -08:00
Gregory Schier
3d919591f3 Rename useWebSocketRequestActions to useWebsocketRequestActions 2025-12-28 15:35:42 -08:00
Gregory Schier
75f92bdd29 Merge branch 'main' of github.com:turchinc/yaak into turchinc/main 2025-12-28 15:21:14 -08:00
Gregory Schier
2fc8678183 Fix lint errors 2025-12-28 15:18:01 -08:00
Gregory Schier
1c29f4d4ad Merge branch 'main' into main 2025-12-28 15:09:35 -08:00
Gregory Schier
8e1959b7c3 Use generated types for FolderActionPlugin and WorkspaceActionPlugin 2025-12-28 15:06:18 -08:00
Gregory Schier
cdd5ba3c83 Remove unused 2025-12-28 15:04:19 -08:00
Gregory Schier
3c45464e34 Get everything working 2025-12-28 15:01:15 -08:00
Gregory Schier
7446d62e39 Add test actions to copy-curl plugin and add WebSocket request actions to Sidebar 2025-12-28 14:37:14 -08:00
Gregory Schier
3855058d8f Refactor new actions apis 2025-12-28 14:27:39 -08:00
Gregory Schier
07d743db21 Use workspace from plugin context instead of accepting it as parameter
- Removed workspaceId parameter from ctx.folder.list() and ctx.httpRequest.list()
- Updated event handlers to get workspace from plugin context
- Use proper generated TypeScript types in Context interface
2025-12-28 14:14:09 -08:00
Gregory Schier
6d5ba685f1 Remove unnecessary ctx.file APIs - plugins can use node:fs directly 2025-12-28 14:06:35 -08:00
Gregory Schier
218fdf3715 Merge main into turchinc/main (PR #324) 2025-12-28 13:58:12 -08:00
Alex Coté
7742e7a54c Allow dots in environment variable names (#323) 2025-12-28 13:53:43 -08:00
Gregory Schier
b516ca877b Fix variable matching in twig grammar to ignore ${var} format (#330)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-28 13:25:47 -08:00
Gregory Schier
f3dc71a85c Fix multipart form data parsing from cURL --data-raw (#331)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-28 13:25:35 -08:00
Gregory Schier
394fbbd55d Refactor content viewer components and use for multpart and request body (#333) 2025-12-28 13:25:24 -08:00
Gregory Schier
6869aa49ec Increase max size of multi-part viewer 2025-12-28 08:43:13 -08:00
Gregory Schier
ba00274045 Switch back to unbounded channel 2025-12-28 08:41:56 -08:00
Gregory Schier
e32930034d Merge branch 'multipart-viewer' 2025-12-28 08:09:34 -08:00
Gregory Schier
26a3e88715 Store and show request body in UI (#327) 2025-12-28 08:07:42 -08:00
Gregory Schier
6a0d5d2337 Add Claude Code GitHub Workflow (#332) 2025-12-28 07:07:20 -08:00
gschier
271d8f29ca Deploying to main from @ mountain-loop/yaak@9c5479b206 🚀 2025-12-26 15:37:29 +00:00
Gregory Schier
9c5479b206 Tweak font sizes 2025-12-22 14:40:18 -08:00
Gregory Schier
5f8902e57b Fix cookies not being persisted after HTTP requests (#328) 2025-12-22 10:58:03 -08:00
Gregory Schier
089c7e8dce Http response events (#326) 2025-12-21 14:34:37 -08:00
Gregory Schier
7e0aa919fb Immediate cancellation 2025-12-21 06:28:36 -08:00
Gregory Schier
5776bab288 Tweak response pane and refactor timings 2025-12-21 06:24:01 -08:00
Gregory Schier
6b52a0cbed Try fix tests on Windows 2025-12-20 14:48:23 -08:00
Gregory Schier
46933059f6 Split up HTTP sending logic (#320) 2025-12-20 14:10:55 -08:00
Chris Turchin
e17aae246b collection plugin actions 2025-12-16 00:47:12 +01:00
Gregory Schier
cfbfd66eef Reformat project 2025-12-13 08:10:12 -08:00
Gregory Schier
c20c0eff32 Update entitlements.plist for 1Password shared lib 2025-12-11 09:22:27 -08:00
Gregory Schier
9d40949043 Fix warning: unused variable: window on non-mac OSs 2025-12-11 07:18:31 -08:00
Gregory Schier
d435337f2a Don't strip symbols hotfix 2025-12-11 06:49:06 -08:00
Gregory Schier
a32145c054 Merge branch 'hotfix/2025.9.3' 2025-12-11 06:32:35 -08:00
Gregory Schier
e0f547b93f Update tauri 2025-12-11 06:32:14 -08:00
Gregory Schier
5d4268d6a1 Merge branch 'hotfix/2025.9.3' 2025-12-11 06:00:47 -08:00
Gregory Schier
0a3506f81e Also move defaultValue out 2025-12-11 05:59:40 -08:00
Gregory Schier
375b2287b7 Merge branch 'hotfix/2025.9.3' 2025-12-11 05:54:23 -08:00
Gregory Schier
e72c1e68e5 Unify 1Password field back to static name 2025-12-11 05:48:19 -08:00
Gregory Schier
3484db3371 Default cert to open when just added 2025-12-10 15:08:59 -08:00
Gregory Schier
c4b559f34b Support client certificates (#319) 2025-12-10 13:54:22 -08:00
Mikhail Mamontov
ef1ba9b834 fix(gRPC): Cache descriptor pools to avoid re-reflection; add manual “Refresh Schema” to force re-fetch (#317) 2025-12-09 15:35:35 -08:00
Jake Oliver
846f4d9551 Update 1Password template to support the new Desktop authentication method (#316) 2025-12-09 14:50:08 -08:00
Gregory Schier
4780bfe41f Fix curl import: decode Unicode escape sequences in $'...' strings (#318)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-09 14:15:39 -08:00
Gregory Schier
d0d01b3897 Update license check to use status instead of type 2025-12-09 14:12:13 -08:00
Gregory Schier
fc1e8baa23 Catch any 4XX error on refresh token failure
https://feedback.yaak.app/p/folders-oauth2-refresh-token-issue
2025-12-09 14:08:31 -08:00
Gregory Schier
d35116c494 Add license handling for expired licenses 2025-12-09 13:51:02 -08:00
gschier
1d257b365b Deploying to main from @ mountain-loop/yaak@1076d57e8a 🚀 2025-12-09 18:15:05 +00:00
Gregory Schier
1076d57e8a Remove unused funding model entries from FUNDING.yml 2025-12-09 10:14:19 -08:00
Gregory Schier
01904cd1c9 Oops, forgot to commit this 2025-12-06 06:47:51 -08:00
Gregory Schier
1c93d5775f Shorter titles when using native titlebar 2025-12-06 06:47:34 -08:00
Gregory Schier
113d0dc3c7 Started multi-part response viewer 2025-12-06 06:47:09 -08:00
Gregory Schier
7b78fac24e Fix native titlebar. Get menu ready for native Mac menus 2025-12-05 15:14:13 -08:00
Gregory Schier
6534b3f622 Debug Window 2025-12-05 17:37:54 -08:00
Gregory Schier
daba21fbca Couple small fixes for Windows 2025-12-05 10:18:21 -08:00
Gregory Schier
3b99ea1cad Try fixing titlebar thing for Windows 2025-12-05 10:03:54 -08:00
Gregory Schier
937d7aa72a Fix Git invokation on Windows (#313) 2025-12-05 09:22:34 -08:00
Gregory Schier
5bf7278479 Add setting to use native window titlebar (#312) 2025-12-05 09:15:48 -08:00
Gregory Schier
095af8cf4b Refresh query when plugins reload in useTemplateFunctionConfig hook 2025-12-02 08:07:53 -08:00
Gregory Schier
e1c1ecc34d Try fix quotes for Windows 2025-12-02 05:45:01 -08:00
325 changed files with 17950 additions and 6882 deletions

View File

@@ -0,0 +1,51 @@
---
description: Review a PR in a new worktree
allowed-tools: Bash(git worktree:*), Bash(gh pr:*)
---
Review a GitHub pull request in a new git worktree.
## Usage
```
/review-pr <PR_NUMBER>
```
## What to do
1. List all open pull requests and ask the user to select one
2. Get PR information using `gh pr view <PR_NUMBER> --json number,headRefName`
3. Extract the branch name from the PR
4. Create a new worktree at `../yaak-worktrees/pr-<PR_NUMBER>`
5. Checkout the PR branch in the new worktree using `gh pr checkout <PR_NUMBER>`
6. The post-checkout hook will automatically:
- Create `.env.local` with unique ports
- Copy editor config folders
- Run `npm install && npm run bootstrap`
7. Inform the user:
- Where the worktree was created
- What ports were assigned
- How to access it (cd command)
- How to run the dev server
- How to remove the worktree when done
## Example Output
```
Created worktree for PR #123 at ../yaak-worktrees/pr-123
Branch: feature-auth
Ports: Vite (1421), MCP (64344)
To start working:
cd ../yaak-worktrees/pr-123
npm run app-dev
To remove when done:
git worktree remove ../yaak-worktrees/pr-123
```
## Error Handling
- If the PR doesn't exist, show a helpful error
- If the worktree already exists, inform the user and ask if they want to remove and recreate it
- If `gh` CLI is not available, inform the user to install it

View File

@@ -0,0 +1,39 @@
---
description: Generate formatted release notes for Yaak releases
allowed-tools: Bash(git tag:*)
---
Generate formatted release notes for Yaak releases by analyzing git history and pull request descriptions.
## What to do
1. Identifies the version tag and previous version
2. Retrieves all commits between versions
- If the version is a beta version, it retrieves commits between the beta version and previous beta version
- If the version is a stable version, it retrieves commits between the stable version and the previous stable version
3. Fetches PR descriptions for linked issues to find:
- Feedback URLs (feedback.yaak.app)
- Additional context and descriptions
- Installation links for plugins
4. Formats the release notes using the standard Yaak format:
- Changelog badge at the top
- Bulleted list of changes with PR links
- Feedback links where available
- Full changelog comparison link at the bottom
## Output Format
The skill generates markdown-formatted release notes following this structure:
```markdown
[![Changelog](https://img.shields.io/badge/Changelog-VERSION-blue)](https://yaak.app/changelog/VERSION)
- Feature/fix description in by @username [#123](https://github.com/mountain-loop/yaak/pull/123)
- [Linked feedback item](https://feedback.yaak.app/p/item) by @username in [#456](https://github.com/mountain-loop/yaak/pull/456)
- A simple item that doesn't have a feedback or PR link
**Full Changelog**: https://github.com/mountain-loop/yaak/compare/vPREV...vCURRENT
```
**IMPORTANT**: Always add a blank lines around the markdown code fence and output the markdown code block last
**IMPORTANT**: PRs by `@gschier` should not mention the @username

22
.claude/rules.md Normal file
View File

@@ -0,0 +1,22 @@
# Project Rules
## General Development
- **NEVER** commit or push without explicit confirmation
## Build and Lint
- **ALWAYS** run `npm run lint` after modifying TypeScript or JavaScript files
- Run `npm run bootstrap` after changing plugin runtime or MCP server code
## Plugin System
### Backend Constraints
- Always use `UpdateSource::Plugin` when calling database methods from plugin events
- Never send timestamps (`createdAt`, `updatedAt`) from TypeScript - Rust backend controls these
- Backend uses `NaiveDateTime` (no timezone) so avoid sending ISO timestamp strings
### MCP Server
- MCP server has **no active window context** - cannot call `window.workspaceId()`
- Get workspace ID from `workspaceCtx.yaak.workspace.list()` instead
## Rust Type Generation
- Run `cd src-tauri && cargo test --package yaak-plugins` to regenerate TypeScript bindings after modifying Rust event types

View File

@@ -0,0 +1,35 @@
# Worktree Management Skill
## Creating Worktrees
When creating git worktrees for this project, ALWAYS use the path format:
```
../yaak-worktrees/<NAME>
```
For example:
- `git worktree add ../yaak-worktrees/feature-auth`
- `git worktree add ../yaak-worktrees/bugfix-login`
- `git worktree add ../yaak-worktrees/refactor-api`
## What Happens Automatically
The post-checkout hook will automatically:
1. Create `.env.local` with unique ports (YAAK_DEV_PORT and YAAK_PLUGIN_MCP_SERVER_PORT)
2. Copy gitignored editor config folders (.zed, .idea, etc.)
3. Run `npm install && npm run bootstrap`
## Deleting Worktrees
```bash
git worktree remove ../yaak-worktrees/<NAME>
```
## Port Assignments
- Main worktree: 1420 (Vite), 64343 (MCP)
- First worktree: 1421, 64344
- Second worktree: 1422, 64345
- etc.
Each worktree can run `npm run app-dev` simultaneously without conflicts.

5
.gitattributes vendored
View File

@@ -1,2 +1,7 @@
src-tauri/vendored/**/* linguist-generated=true
src-tauri/gen/schemas/**/* linguist-generated=true
**/bindings/* linguist-generated=true
src-tauri/yaak-templates/pkg/* linguist-generated=true
# Ensure consistent line endings for test files that check exact content
src-tauri/yaak-http/tests/test.txt text eol=lf

12
.github/FUNDING.yml vendored
View File

@@ -1,15 +1,3 @@
# These are supported funding model platforms
github: gschier
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: https://yaak.app/pricing

50
.github/workflows/claude.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
# prompt: 'Update the pull request description to include a summary of changes.'
# Optional: Add claude_args to customize behavior and configuration
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options
# claude_args: '--allowed-tools Bash(gh pr:*)'

4
.gitignore vendored
View File

@@ -25,6 +25,7 @@ dist-ssr
*.sln
*.sw?
.eslintcache
out
*.sqlite
*.sqlite-*
@@ -33,3 +34,6 @@ dist-ssr
.tmp
tmp
.zed
codebook.toml
target

1
.husky/post-checkout Executable file
View File

@@ -0,0 +1 @@
node scripts/git-hooks/post-checkout.mjs "$@"

View File

@@ -19,10 +19,10 @@
<p align="center">
<!-- sponsors-premium --><a href="https://github.com/MVST-Solutions"><img src="https:&#x2F;&#x2F;github.com&#x2F;MVST-Solutions.png" width="80px" alt="User avatar: MVST-Solutions" /></a>&nbsp;&nbsp;<a href="https://github.com/dharsanb"><img src="https:&#x2F;&#x2F;github.com&#x2F;dharsanb.png" width="80px" alt="User avatar: dharsanb" /></a>&nbsp;&nbsp;<a href="https://github.com/railwayapp"><img src="https:&#x2F;&#x2F;github.com&#x2F;railwayapp.png" width="80px" alt="User avatar: railwayapp" /></a>&nbsp;&nbsp;<a href="https://github.com/caseyamcl"><img src="https:&#x2F;&#x2F;github.com&#x2F;caseyamcl.png" width="80px" alt="User avatar: caseyamcl" /></a>&nbsp;&nbsp;<a href="https://github.com/"><img src="https:&#x2F;&#x2F;raw.githubusercontent.com&#x2F;JamesIves&#x2F;github-sponsors-readme-action&#x2F;dev&#x2F;.github&#x2F;assets&#x2F;placeholder.png" width="80px" alt="User avatar: " /></a>&nbsp;&nbsp;<!-- sponsors-premium -->
<!-- sponsors-premium --><a href="https://github.com/MVST-Solutions"><img src="https:&#x2F;&#x2F;github.com&#x2F;MVST-Solutions.png" width="80px" alt="User avatar: MVST-Solutions" /></a>&nbsp;&nbsp;<a href="https://github.com/dharsanb"><img src="https:&#x2F;&#x2F;github.com&#x2F;dharsanb.png" width="80px" alt="User avatar: dharsanb" /></a>&nbsp;&nbsp;<a href="https://github.com/railwayapp"><img src="https:&#x2F;&#x2F;github.com&#x2F;railwayapp.png" width="80px" alt="User avatar: railwayapp" /></a>&nbsp;&nbsp;<a href="https://github.com/caseyamcl"><img src="https:&#x2F;&#x2F;github.com&#x2F;caseyamcl.png" width="80px" alt="User avatar: caseyamcl" /></a>&nbsp;&nbsp;<a href="https://github.com/bytebase"><img src="https:&#x2F;&#x2F;github.com&#x2F;bytebase.png" width="80px" alt="User avatar: bytebase" /></a>&nbsp;&nbsp;<a href="https://github.com/"><img src="https:&#x2F;&#x2F;raw.githubusercontent.com&#x2F;JamesIves&#x2F;github-sponsors-readme-action&#x2F;dev&#x2F;.github&#x2F;assets&#x2F;placeholder.png" width="80px" alt="User avatar: " /></a>&nbsp;&nbsp;<!-- sponsors-premium -->
</p>
<p align="center">
<!-- sponsors-base --><a href="https://github.com/seanwash"><img src="https:&#x2F;&#x2F;github.com&#x2F;seanwash.png" width="50px" alt="User avatar: seanwash" /></a>&nbsp;&nbsp;<a href="https://github.com/jerath"><img src="https:&#x2F;&#x2F;github.com&#x2F;jerath.png" width="50px" alt="User avatar: jerath" /></a>&nbsp;&nbsp;<a href="https://github.com/itsa-sh"><img src="https:&#x2F;&#x2F;github.com&#x2F;itsa-sh.png" width="50px" alt="User avatar: itsa-sh" /></a>&nbsp;&nbsp;<a href="https://github.com/dmmulroy"><img src="https:&#x2F;&#x2F;github.com&#x2F;dmmulroy.png" width="50px" alt="User avatar: dmmulroy" /></a>&nbsp;&nbsp;<a href="https://github.com/timcole"><img src="https:&#x2F;&#x2F;github.com&#x2F;timcole.png" width="50px" alt="User avatar: timcole" /></a>&nbsp;&nbsp;<a href="https://github.com/VLZH"><img src="https:&#x2F;&#x2F;github.com&#x2F;VLZH.png" width="50px" alt="User avatar: VLZH" /></a>&nbsp;&nbsp;<a href="https://github.com/terasaka2k"><img src="https:&#x2F;&#x2F;github.com&#x2F;terasaka2k.png" width="50px" alt="User avatar: terasaka2k" /></a>&nbsp;&nbsp;<a href="https://github.com/andriyor"><img src="https:&#x2F;&#x2F;github.com&#x2F;andriyor.png" width="50px" alt="User avatar: andriyor" /></a>&nbsp;&nbsp;<a href="https://github.com/majudhu"><img src="https:&#x2F;&#x2F;github.com&#x2F;majudhu.png" width="50px" alt="User avatar: majudhu" /></a>&nbsp;&nbsp;<a href="https://github.com/axelrindle"><img src="https:&#x2F;&#x2F;github.com&#x2F;axelrindle.png" width="50px" alt="User avatar: axelrindle" /></a>&nbsp;&nbsp;<a href="https://github.com/jirizverina"><img src="https:&#x2F;&#x2F;github.com&#x2F;jirizverina.png" width="50px" alt="User avatar: jirizverina" /></a>&nbsp;&nbsp;<a href="https://github.com/chip-well"><img src="https:&#x2F;&#x2F;github.com&#x2F;chip-well.png" width="50px" alt="User avatar: chip-well" /></a>&nbsp;&nbsp;<!-- sponsors-base -->
<!-- sponsors-base --><a href="https://github.com/seanwash"><img src="https:&#x2F;&#x2F;github.com&#x2F;seanwash.png" width="50px" alt="User avatar: seanwash" /></a>&nbsp;&nbsp;<a href="https://github.com/jerath"><img src="https:&#x2F;&#x2F;github.com&#x2F;jerath.png" width="50px" alt="User avatar: jerath" /></a>&nbsp;&nbsp;<a href="https://github.com/itsa-sh"><img src="https:&#x2F;&#x2F;github.com&#x2F;itsa-sh.png" width="50px" alt="User avatar: itsa-sh" /></a>&nbsp;&nbsp;<a href="https://github.com/dmmulroy"><img src="https:&#x2F;&#x2F;github.com&#x2F;dmmulroy.png" width="50px" alt="User avatar: dmmulroy" /></a>&nbsp;&nbsp;<a href="https://github.com/timcole"><img src="https:&#x2F;&#x2F;github.com&#x2F;timcole.png" width="50px" alt="User avatar: timcole" /></a>&nbsp;&nbsp;<a href="https://github.com/VLZH"><img src="https:&#x2F;&#x2F;github.com&#x2F;VLZH.png" width="50px" alt="User avatar: VLZH" /></a>&nbsp;&nbsp;<a href="https://github.com/terasaka2k"><img src="https:&#x2F;&#x2F;github.com&#x2F;terasaka2k.png" width="50px" alt="User avatar: terasaka2k" /></a>&nbsp;&nbsp;<a href="https://github.com/andriyor"><img src="https:&#x2F;&#x2F;github.com&#x2F;andriyor.png" width="50px" alt="User avatar: andriyor" /></a>&nbsp;&nbsp;<a href="https://github.com/majudhu"><img src="https:&#x2F;&#x2F;github.com&#x2F;majudhu.png" width="50px" alt="User avatar: majudhu" /></a>&nbsp;&nbsp;<a href="https://github.com/axelrindle"><img src="https:&#x2F;&#x2F;github.com&#x2F;axelrindle.png" width="50px" alt="User avatar: axelrindle" /></a>&nbsp;&nbsp;<a href="https://github.com/jirizverina"><img src="https:&#x2F;&#x2F;github.com&#x2F;jirizverina.png" width="50px" alt="User avatar: jirizverina" /></a>&nbsp;&nbsp;<a href="https://github.com/chip-well"><img src="https:&#x2F;&#x2F;github.com&#x2F;chip-well.png" width="50px" alt="User avatar: chip-well" /></a>&nbsp;&nbsp;<a href="https://github.com/GRAYAH"><img src="https:&#x2F;&#x2F;github.com&#x2F;GRAYAH.png" width="50px" alt="User avatar: GRAYAH" /></a>&nbsp;&nbsp;<!-- sponsors-base -->
</p>
![Yaak API Client](https://yaak.app/static/screenshot.png)

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.7/schema.json",
"$schema": "https://biomejs.dev/schemas/2.3.10/schema.json",
"linter": {
"enabled": true,
"rules": {
@@ -39,13 +39,13 @@
"!**/dist",
"!**/build",
"!scripts",
"!packages/plugin-runtime",
"!packages/plugin-runtime-types",
"!src-tauri",
"!src-web/tailwind.config.cjs",
"!src-web/postcss.config.cjs",
"!src-web/vite.config.ts",
"!src-web/routeTree.gen.ts"
"!src-web/routeTree.gen.ts",
"!packages/plugin-runtime-types/lib",
"!**/bindings"
]
}
}

6484
package-lock.json generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -10,8 +10,11 @@
"packages/common-lib",
"packages/plugin-runtime",
"packages/plugin-runtime-types",
"plugins-external/mcp-server",
"plugins-external/template-function-faker",
"plugins/action-copy-curl",
"plugins/action-copy-grpcurl",
"plugins/action-send-folder",
"plugins/auth-apikey",
"plugins/auth-aws",
"plugins/auth-basic",
@@ -59,9 +62,11 @@
"src-web"
],
"scripts": {
"prepare": "husky",
"init": "npm install && npm run bootstrap",
"start": "npm run app-dev",
"app-build": "tauri build",
"app-dev": "tauri dev --no-watch --config ./src-tauri/tauri.development.conf.json",
"app-dev": "node scripts/run-dev.mjs",
"migration": "node scripts/create-migration.cjs",
"build": "npm run --workspaces --if-present build",
"build-plugins": "npm run --workspaces --if-present build",
@@ -87,9 +92,11 @@
"tauri-before-dev": "workspaces-run --parallel -- npm run --workspaces --if-present dev"
},
"devDependencies": {
"@biomejs/biome": "^2.3.7",
"@tauri-apps/cli": "^2.9.1",
"@biomejs/biome": "^2.3.10",
"@tauri-apps/cli": "^2.9.6",
"@yaakapp/cli": "^0.3.4",
"dotenv-cli": "^11.0.0",
"husky": "^9.1.7",
"nodejs-file-downloader": "^4.13.0",
"npm-run-all": "^4.1.5",
"typescript": "^5.8.3",

View File

@@ -1,47 +0,0 @@
{
"name": "@yaakapp/api",
"version": "0.1.17",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@yaakapp/api",
"version": "0.1.17",
"dependencies": {
"@types/node": "^22.5.4"
},
"devDependencies": {
"typescript": "^5.6.2"
}
},
"node_modules/@types/node": {
"version": "22.5.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.19.2"
}
},
"node_modules/typescript": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"license": "MIT"
}
}
}

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,18 +1,53 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type AnyModel = CookieJar | Environment | Folder | GraphQlIntrospection | GrpcConnection | GrpcEvent | GrpcRequest | HttpRequest | HttpResponse | HttpResponseEvent | KeyValue | Plugin | Settings | SyncState | WebsocketConnection | WebsocketEvent | WebsocketRequest | Workspace | WorkspaceMeta;
export type ClientCertificate = { host: string, port: number | null, crtFile: string | null, keyFile: string | null, pfxFile: string | null, passphrase: string | null, enabled?: boolean, };
export type Cookie = { raw_cookie: string, domain: CookieDomain, expires: CookieExpires, path: [string, boolean], };
export type CookieDomain = { "HostOnly": string } | { "Suffix": string } | "NotPresent" | "Empty";
export type CookieExpires = { "AtUtc": string } | "SessionEnd";
export type CookieJar = { model: "cookie_jar", id: string, createdAt: string, updatedAt: string, workspaceId: string, cookies: Array<Cookie>, name: string, };
export type EditorKeymap = "default" | "vim" | "vscode" | "emacs";
export type EncryptedKey = { encryptedKey: string, };
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, parentModel: string, parentId: string | null, variables: Array<EnvironmentVariable>, color: string | null, sortPriority: number, };
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };
export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, name: string, sortPriority: number, };
export type GraphQlIntrospection = { model: "graphql_introspection", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, content: string | null, };
export type GrpcConnection = { model: "grpc_connection", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, elapsed: number, error: string | null, method: string, service: string, status: number, state: GrpcConnectionState, trailers: { [key in string]?: string }, url: string, };
export type GrpcConnectionState = "initialized" | "connected" | "closed";
export type GrpcEvent = { model: "grpc_event", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, connectionId: string, content: string, error: string | null, eventType: GrpcEventType, metadata: { [key in string]?: string }, status: number | null, };
export type GrpcEventType = "info" | "error" | "client_message" | "server_message" | "connection_start" | "connection_end";
export type GrpcRequest = { model: "grpc_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authenticationType: string | null, authentication: Record<string, any>, description: string, message: string, metadata: Array<HttpRequestHeader>, method: string | null, name: string, service: string | null, sortPriority: number, url: string, };
export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, body: Record<string, any>, bodyType: string | null, description: string, headers: Array<HttpRequestHeader>, method: string, name: string, sortPriority: number, url: string, urlParameters: Array<HttpUrlParameter>, };
export type HttpRequestHeader = { enabled?: boolean, name: string, value: string, id?: string, };
export type HttpResponse = { model: "http_response", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, bodyPath: string | null, contentLength: number | null, elapsed: number, elapsedHeaders: number, error: string | null, headers: Array<HttpResponseHeader>, remoteAddr: string | null, status: number, statusReason: string | null, state: HttpResponseState, url: string, version: string | null, };
export type HttpResponse = { model: "http_response", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, bodyPath: string | null, contentLength: number | null, contentLengthCompressed: number | null, elapsed: number, elapsedHeaders: number, error: string | null, headers: Array<HttpResponseHeader>, remoteAddr: string | null, requestContentLength: number | null, requestHeaders: Array<HttpResponseHeader>, status: number, statusReason: string | null, state: HttpResponseState, url: string, version: string | null, };
export type HttpResponseEvent = { model: "http_response_event", id: string, createdAt: string, updatedAt: string, workspaceId: string, responseId: string, event: HttpResponseEventData, };
/**
* Serializable representation of HTTP response events for DB storage.
* This mirrors `yaak_http::sender::HttpResponseEvent` but with serde support.
* The `From` impl is in yaak-http to avoid circular dependencies.
*/
export type HttpResponseEventData = { "type": "setting", name: string, value: string, } | { "type": "info", message: string, } | { "type": "redirect", url: string, status: number, behavior: string, } | { "type": "send_url", method: string, path: string, } | { "type": "receive_url", version: string, status: string, } | { "type": "header_up", name: string, value: string, } | { "type": "header_down", name: string, value: string, } | { "type": "chunk_sent", bytes: number, } | { "type": "chunk_received", bytes: number, };
export type HttpResponseHeader = { name: string, value: string, };
@@ -20,6 +55,28 @@ export type HttpResponseState = "initialized" | "connected" | "closed";
export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, id?: string, };
export type KeyValue = { model: "key_value", id: string, createdAt: string, updatedAt: string, key: string, namespace: string, value: string, };
export type Plugin = { model: "plugin", id: string, createdAt: string, updatedAt: string, checkedAt: string | null, directory: string, enabled: boolean, url: string | null, };
export type ProxySetting = { "type": "enabled", http: string, https: string, auth: ProxySettingAuth | null, bypass: string, disabled: boolean, } | { "type": "disabled" };
export type ProxySettingAuth = { user: string, password: string, };
export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, clientCertificates: Array<ClientCertificate>, coloredMethods: boolean, editorFont: string | null, editorFontSize: number, editorKeymap: EditorKeymap, editorSoftWrap: boolean, hideWindowControls: boolean, useNativeTitlebar: boolean, interfaceFont: string | null, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, hideLicenseBadge: boolean, autoupdate: boolean, autoDownloadUpdates: boolean, checkNotifications: boolean, };
export type SyncState = { model: "sync_state", id: string, workspaceId: string, createdAt: string, updatedAt: string, flushedAt: string, modelId: string, checksum: string, relPath: string, syncDir: string, };
export type WebsocketConnection = { model: "websocket_connection", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, elapsed: number, error: string | null, headers: Array<HttpResponseHeader>, state: WebsocketConnectionState, status: number, url: string, };
export type WebsocketConnectionState = "initialized" | "connected" | "closing" | "closed";
export type WebsocketEvent = { model: "websocket_event", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, connectionId: string, isServer: boolean, message: Array<number>, messageType: WebsocketEventType, };
export type WebsocketEventType = "binary" | "close" | "frame" | "open" | "ping" | "pong" | "text";
export type WebsocketRequest = { model: "websocket_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, message: string, name: string, sortPriority: number, url: string, urlParameters: Array<HttpUrlParameter>, };
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, name: string, encryptionKeyChallenge: string | null, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, };
export type WorkspaceMeta = { model: "workspace_meta", id: string, workspaceId: string, createdAt: string, updatedAt: string, encryptionKey: EncryptedKey | null, settingSyncDir: string | null, };

View File

@@ -1,4 +1,4 @@
import {
import type {
CallHttpAuthenticationActionArgs,
CallHttpAuthenticationRequest,
CallHttpAuthenticationResponse,
@@ -6,8 +6,8 @@ import {
GetHttpAuthenticationSummaryResponse,
HttpAuthenticationAction,
} from '../bindings/gen_events';
import { MaybePromise } from '../helpers';
import { Context } from './Context';
import type { MaybePromise } from '../helpers';
import type { Context } from './Context';
type AddDynamicMethod<T> = {
dynamic?: (
@@ -16,6 +16,7 @@ type AddDynamicMethod<T> = {
) => MaybePromise<Partial<T> | null | undefined>;
};
// biome-ignore lint/suspicious/noExplicitAny: distributive conditional type pattern
type AddDynamic<T> = T extends any
? T extends { inputs?: FormInput[] }
? Omit<T, 'inputs'> & {

View File

@@ -1,25 +1,33 @@
import type {
FindHttpResponsesRequest,
FindHttpResponsesResponse,
GetCookieValueRequest,
GetCookieValueResponse,
GetHttpRequestByIdRequest,
GetHttpRequestByIdResponse,
ListCookieNamesResponse,
OpenWindowRequest,
PromptTextRequest,
PromptTextResponse,
RenderGrpcRequestRequest,
RenderGrpcRequestResponse,
RenderHttpRequestRequest,
RenderHttpRequestResponse,
SendHttpRequestRequest,
SendHttpRequestResponse,
ShowToastRequest,
TemplateRenderRequest,
FindHttpResponsesRequest,
FindHttpResponsesResponse,
GetCookieValueRequest,
GetCookieValueResponse,
GetHttpRequestByIdRequest,
GetHttpRequestByIdResponse,
ListCookieNamesResponse,
ListFoldersRequest,
ListFoldersResponse,
ListHttpRequestsRequest,
ListHttpRequestsResponse,
OpenWindowRequest,
PromptTextRequest,
PromptTextResponse,
RenderGrpcRequestRequest,
RenderGrpcRequestResponse,
RenderHttpRequestRequest,
RenderHttpRequestResponse,
SendHttpRequestRequest,
SendHttpRequestResponse,
ShowToastRequest,
TemplateRenderRequest,
WorkspaceInfo,
} from '../bindings/gen_events.ts';
import type { HttpRequest } from '../bindings/gen_models.ts';
import type { JsonValue } from '../bindings/serde_json/JsonValue';
export type WorkspaceHandle = Pick<WorkspaceInfo, 'id' | 'name'>;
export interface Context {
clipboard: {
copyText(text: string): Promise<void>;
@@ -45,6 +53,7 @@ export interface Context {
onClose?: () => void;
},
): Promise<{ close: () => void }>;
openExternalUrl(url: string): Promise<void>;
};
cookies: {
listNames(): Promise<ListCookieNamesResponse['names']>;
@@ -57,6 +66,19 @@ export interface Context {
send(args: SendHttpRequestRequest): Promise<SendHttpRequestResponse['httpResponse']>;
getById(args: GetHttpRequestByIdRequest): Promise<GetHttpRequestByIdResponse['httpRequest']>;
render(args: RenderHttpRequestRequest): Promise<RenderHttpRequestResponse['httpRequest']>;
list(args?: ListHttpRequestsRequest): Promise<ListHttpRequestsResponse['httpRequests']>;
create(
args: Omit<Partial<HttpRequest>, 'id' | 'model' | 'createdAt' | 'updatedAt'> &
Pick<HttpRequest, 'workspaceId' | 'url'>,
): Promise<HttpRequest>;
update(
args: Omit<Partial<HttpRequest>, 'model' | 'createdAt' | 'updatedAt'> &
Pick<HttpRequest, 'id'>,
): Promise<HttpRequest>;
delete(args: { id: string }): Promise<HttpRequest>;
};
folder: {
list(args?: ListFoldersRequest): Promise<ListFoldersResponse['folders']>;
};
httpResponse: {
find(args: FindHttpResponsesRequest): Promise<FindHttpResponsesResponse['httpResponses']>;
@@ -67,4 +89,8 @@ export interface Context {
plugin: {
reload(): void;
};
workspace: {
list(): Promise<WorkspaceHandle[]>;
withContext(handle: WorkspaceHandle): Context;
};
}

View File

@@ -1,4 +1,4 @@
import { FilterResponse } from '../bindings/gen_events';
import type { FilterResponse } from '../bindings/gen_events';
import type { Context } from './Context';
export type FilterPlugin = {

View File

@@ -0,0 +1,6 @@
import type { CallFolderActionArgs, FolderAction } from '../bindings/gen_events';
import type { Context } from './Context';
export type FolderActionPlugin = FolderAction & {
onSelect(ctx: Context, args: CallFolderActionArgs): Promise<void> | void;
};

View File

@@ -1,4 +1,4 @@
import { CallGrpcRequestActionArgs, GrpcRequestAction } from '../bindings/gen_events';
import type { CallGrpcRequestActionArgs, GrpcRequestAction } from '../bindings/gen_events';
import type { Context } from './Context';
export type GrpcRequestActionPlugin = GrpcRequestAction & {

View File

@@ -1,5 +1,5 @@
import { ImportResources } from '../bindings/gen_events';
import { AtLeast, MaybePromise } from '../helpers';
import type { ImportResources } from '../bindings/gen_events';
import type { AtLeast, MaybePromise } from '../helpers';
import type { Context } from './Context';
type RootFields = 'name' | 'id' | 'model';

View File

@@ -1,6 +1,6 @@
import { CallTemplateFunctionArgs, FormInput, TemplateFunction } from '../bindings/gen_events';
import { MaybePromise } from '../helpers';
import { Context } from './Context';
import type { CallTemplateFunctionArgs, FormInput, TemplateFunction } from '../bindings/gen_events';
import type { MaybePromise } from '../helpers';
import type { Context } from './Context';
type AddDynamicMethod<T> = {
dynamic?: (
@@ -9,6 +9,7 @@ type AddDynamicMethod<T> = {
) => MaybePromise<Partial<T> | null | undefined>;
};
// biome-ignore lint/suspicious/noExplicitAny: distributive conditional type pattern
type AddDynamic<T> = T extends any
? T extends { inputs?: FormInput[] }
? Omit<T, 'inputs'> & {

View File

@@ -1,3 +1,3 @@
import { Theme } from '../bindings/gen_events';
import type { Theme } from '../bindings/gen_events';
export type ThemePlugin = Theme;

View File

@@ -0,0 +1,6 @@
import type { CallWebsocketRequestActionArgs, WebsocketRequestAction } from '../bindings/gen_events';
import type { Context } from './Context';
export type WebsocketRequestActionPlugin = WebsocketRequestAction & {
onSelect(ctx: Context, args: CallWebsocketRequestActionArgs): Promise<void> | void;
};

View File

@@ -0,0 +1,6 @@
import type { CallWorkspaceActionArgs, WorkspaceAction } from '../bindings/gen_events';
import type { Context } from './Context';
export type WorkspaceActionPlugin = WorkspaceAction & {
onSelect(ctx: Context, args: CallWorkspaceActionArgs): Promise<void> | void;
};

View File

@@ -1,9 +1,12 @@
import { AuthenticationPlugin } from './AuthenticationPlugin';
import type { AuthenticationPlugin } from './AuthenticationPlugin';
import type { Context } from './Context';
import type { FilterPlugin } from './FilterPlugin';
import { GrpcRequestActionPlugin } from './GrpcRequestActionPlugin';
import type { GrpcRequestActionPlugin } from './GrpcRequestActionPlugin';
import type { HttpRequestActionPlugin } from './HttpRequestActionPlugin';
import type { WebsocketRequestActionPlugin } from './WebsocketRequestActionPlugin';
import type { WorkspaceActionPlugin } from './WorkspaceActionPlugin';
import type { FolderActionPlugin } from './FolderActionPlugin';
import type { ImporterPlugin } from './ImporterPlugin';
import type { TemplateFunctionPlugin } from './TemplateFunctionPlugin';
import type { ThemePlugin } from './ThemePlugin';
@@ -12,6 +15,8 @@ export type { Context };
export type { DynamicTemplateFunctionArg } from './TemplateFunctionPlugin';
export type { DynamicAuthenticationArg } from './AuthenticationPlugin';
export type { TemplateFunctionPlugin };
export type { WorkspaceActionPlugin } from './WorkspaceActionPlugin';
export type { FolderActionPlugin } from './FolderActionPlugin';
/**
* The global structure of a Yaak plugin
@@ -24,6 +29,9 @@ export type PluginDefinition = {
filter?: FilterPlugin;
authentication?: AuthenticationPlugin;
httpRequestActions?: HttpRequestActionPlugin[];
websocketRequestActions?: WebsocketRequestActionPlugin[];
workspaceActions?: WorkspaceActionPlugin[];
folderActions?: FolderActionPlugin[];
grpcRequestActions?: GrpcRequestActionPlugin[];
templateFunctions?: TemplateFunctionPlugin[];
};

View File

@@ -1,10 +0,0 @@
{
"name": "@yaakapp-internal/plugin-runtime",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@yaakapp-internal/plugin-runtime"
}
}
}

View File

@@ -1,7 +1,7 @@
import { PluginContext } from '@yaakapp-internal/plugins';
import type { BootRequest, InternalEvent } from '@yaakapp/api';
import type { PluginContext } from '@yaakapp-internal/plugins';
import type { EventChannel } from './EventChannel';
import { PluginInstance, PluginWorkerData } from './PluginInstance';
import { PluginInstance, type PluginWorkerData } from './PluginInstance';
export class PluginHandle {
#instance: PluginInstance;

View File

@@ -1,7 +1,15 @@
import { applyFormInputDefaults, validateTemplateFunctionArgs } from '@yaakapp-internal/lib/templateFunction';
import console from 'node:console';
import { type Stats, statSync, watch } from 'node:fs';
import path from 'node:path';
import type { Context, PluginDefinition } from '@yaakapp/api';
import {
applyFormInputDefaults,
validateTemplateFunctionArgs,
} from '@yaakapp-internal/lib/templateFunction';
import type {
BootRequest,
DeleteKeyValueResponse,
DeleteModelResponse,
FindHttpResponsesResponse,
GetCookieValueRequest,
GetCookieValueResponse,
@@ -9,23 +17,27 @@ import {
GetKeyValueResponse,
GrpcRequestAction,
HttpAuthenticationAction,
HttpRequest,
HttpRequestAction,
ImportResources,
InternalEvent,
InternalEventPayload,
ListCookieNamesResponse,
ListFoldersResponse,
ListHttpRequestsRequest,
ListHttpRequestsResponse,
ListWorkspacesResponse,
PluginContext,
PromptTextResponse,
RenderGrpcRequestResponse,
RenderHttpRequestResponse,
SendHttpRequestResponse,
TemplateFunction,
TemplateRenderRequest,
TemplateRenderResponse,
UpsertModelResponse,
WindowInfoResponse,
} from '@yaakapp-internal/plugins';
import { Context, PluginDefinition } from '@yaakapp/api';
import console from 'node:console';
import { type Stats, statSync, watch } from 'node:fs';
import path from 'node:path';
import { applyDynamicFormInput } from './common';
import { EventChannel } from './EventChannel';
import { migrateTemplateFunctionSelectOptions } from './migrations';
@@ -52,20 +64,30 @@ export class PluginInstance {
await this.#onMessage(event);
});
this.#mod = {} as any;
this.#mod = {};
const fileChangeCallback = async () => {
await this.#mod?.dispose?.();
this.#importModule();
await this.#mod?.init?.(this.#newCtx(workerData.context));
return this.#sendPayload(
workerData.context,
{
type: 'reload_response',
silent: false,
},
null,
);
const ctx = this.#newCtx(workerData.context);
try {
await this.#mod?.init?.(ctx);
this.#sendPayload(
workerData.context,
{
type: 'reload_response',
silent: false,
},
null,
);
} catch (err: unknown) {
ctx.toast.show({
message: `Failed to initialize plugin ${this.#workerData.bootRequest.dir.split('/').pop()}: ${err}`,
color: 'notice',
icon: 'alert_triangle',
timeout: 30000,
});
}
};
if (this.#workerData.bootRequest.watch) {
@@ -116,8 +138,7 @@ export class PluginInstance {
if (reply != null) {
const replyPayload: InternalEventPayload = {
type: 'import_response',
// deno-lint-ignore no-explicit-any
resources: reply.resources as any,
resources: reply.resources as ImportResources,
};
this.#sendPayload(context, replyPayload, replyId);
return;
@@ -172,6 +193,57 @@ export class PluginInstance {
return;
}
if (
payload.type === 'get_websocket_request_actions_request' &&
Array.isArray(this.#mod?.websocketRequestActions)
) {
const reply = this.#mod.websocketRequestActions.map((a) => ({
...a,
onSelect: undefined,
}));
const replyPayload: InternalEventPayload = {
type: 'get_websocket_request_actions_response',
pluginRefId: this.#workerData.pluginRefId,
actions: reply,
};
this.#sendPayload(context, replyPayload, replyId);
return;
}
if (
payload.type === 'get_workspace_actions_request' &&
Array.isArray(this.#mod?.workspaceActions)
) {
const reply = this.#mod.workspaceActions.map((a) => ({
...a,
onSelect: undefined,
}));
const replyPayload: InternalEventPayload = {
type: 'get_workspace_actions_response',
pluginRefId: this.#workerData.pluginRefId,
actions: reply,
};
this.#sendPayload(context, replyPayload, replyId);
return;
}
if (
payload.type === 'get_folder_actions_request' &&
Array.isArray(this.#mod?.folderActions)
) {
const reply = this.#mod.folderActions.map((a) => ({
...a,
onSelect: undefined,
}));
const replyPayload: InternalEventPayload = {
type: 'get_folder_actions_response',
pluginRefId: this.#workerData.pluginRefId,
actions: reply,
};
this.#sendPayload(context, replyPayload, replyId);
return;
}
if (payload.type === 'get_themes_request' && Array.isArray(this.#mod?.themes)) {
const replyPayload: InternalEventPayload = {
type: 'get_themes_response',
@@ -207,7 +279,7 @@ export class PluginInstance {
payload.type === 'get_template_function_config_request' &&
Array.isArray(this.#mod?.templateFunctions)
) {
let templateFunction = this.#mod.templateFunctions.find((f) => f.name === payload.name);
const templateFunction = this.#mod.templateFunctions.find((f) => f.name === payload.name);
if (templateFunction == null) {
this.#sendEmpty(context, replyId);
return;
@@ -302,6 +374,39 @@ export class PluginInstance {
}
}
if (
payload.type === 'call_websocket_request_action_request' &&
Array.isArray(this.#mod.websocketRequestActions)
) {
const action = this.#mod.websocketRequestActions[payload.index];
if (typeof action?.onSelect === 'function') {
await action.onSelect(ctx, payload.args);
this.#sendEmpty(context, replyId);
return;
}
}
if (
payload.type === 'call_workspace_action_request' &&
Array.isArray(this.#mod.workspaceActions)
) {
const action = this.#mod.workspaceActions[payload.index];
if (typeof action?.onSelect === 'function') {
await action.onSelect(ctx, payload.args);
this.#sendEmpty(context, replyId);
return;
}
}
if (payload.type === 'call_folder_action_request' && Array.isArray(this.#mod.folderActions)) {
const action = this.#mod.folderActions[payload.index];
if (typeof action?.onSelect === 'function') {
await action.onSelect(ctx, payload.args);
this.#sendEmpty(context, replyId);
return;
}
}
if (
payload.type === 'call_grpc_request_action_request' &&
Array.isArray(this.#mod.grpcRequestActions)
@@ -541,6 +646,12 @@ export class PluginInstance {
},
};
},
openExternalUrl: async (url) => {
await this.#sendForReply(context, {
type: 'open_external_url_request',
url,
});
},
},
prompt: {
text: async (args) => {
@@ -611,6 +722,58 @@ export class PluginInstance {
);
return httpRequest;
},
list: async (args?: { folderId?: string }) => {
const payload: InternalEventPayload = {
type: 'list_http_requests_request',
folderId: args?.folderId,
} satisfies ListHttpRequestsRequest & { type: 'list_http_requests_request' };
const { httpRequests } = await this.#sendForReply<ListHttpRequestsResponse>(
context,
payload,
);
return httpRequests;
},
create: async (args) => {
const payload = {
type: 'upsert_model_request',
model: {
name: '',
method: 'GET',
...args,
id: '',
model: 'http_request',
},
} as InternalEventPayload;
const response = await this.#sendForReply<UpsertModelResponse>(context, payload);
return response.model as HttpRequest;
},
update: async (args) => {
const payload = {
type: 'upsert_model_request',
model: {
model: 'http_request',
...args,
},
} as InternalEventPayload;
const response = await this.#sendForReply<UpsertModelResponse>(context, payload);
return response.model as HttpRequest;
},
delete: async (args) => {
const payload = {
type: 'delete_model_request',
model: 'http_request',
id: args.id,
} as InternalEventPayload;
const response = await this.#sendForReply<DeleteModelResponse>(context, payload);
return response.model as HttpRequest;
},
},
folder: {
list: async () => {
const payload = { type: 'list_folders_request' } as const;
const { folders } = await this.#sendForReply<ListFoldersResponse>(context, payload);
return folders;
},
},
cookies: {
getValue: async (args: GetCookieValueRequest) => {
@@ -632,9 +795,10 @@ export class PluginInstance {
* Invoke Yaak's template engine to render a value. If the value is a nested type
* (eg. object), it will be recursively rendered.
*/
render: async (args) => {
render: async (args: TemplateRenderRequest) => {
const payload = { type: 'template_render_request', ...args } as const;
const result = await this.#sendForReply<TemplateRenderResponse>(context, payload);
// biome-ignore lint/suspicious/noExplicitAny: That's okay
return result.data as any;
},
},
@@ -664,6 +828,33 @@ export class PluginInstance {
this.#sendPayload(context, { type: 'reload_response', silent: true }, null);
},
},
workspace: {
list: async () => {
const payload = {
type: 'list_workspaces_request',
} as InternalEventPayload;
const response = await this.#sendForReply<ListWorkspacesResponse>(context, payload);
return response.workspaces.map((w) => {
// Internal workspace info includes label field not in public API
type WorkspaceInfoInternal = typeof w & { label?: string };
return {
id: w.id,
name: w.name,
// Hide label from plugin authors, but keep it for internal routing
_label: (w as WorkspaceInfoInternal).label as string,
};
});
},
withContext: (workspaceHandle: { id: string; name: string; _label?: string }) => {
// Create a new context with the workspace's window label
const newContext: PluginContext = {
...context,
label: workspaceHandle._label || null,
workspaceId: workspaceHandle.id,
};
return this.#newCtx(newContext);
},
},
};
}
}

View File

@@ -1,5 +1,8 @@
import { CallHttpAuthenticationActionArgs, CallTemplateFunctionArgs } from '@yaakapp-internal/plugins';
import { Context, DynamicAuthenticationArg, DynamicTemplateFunctionArg } from '@yaakapp/api';
import type { Context, DynamicAuthenticationArg, DynamicTemplateFunctionArg } from '@yaakapp/api';
import type {
CallHttpAuthenticationActionArgs,
CallTemplateFunctionArgs,
} from '@yaakapp-internal/plugins';
export async function applyDynamicFormInput(
ctx: Context,
@@ -18,15 +21,28 @@ export async function applyDynamicFormInput(
args: (DynamicTemplateFunctionArg | DynamicAuthenticationArg)[],
callArgs: CallTemplateFunctionArgs | CallHttpAuthenticationActionArgs,
): Promise<(DynamicTemplateFunctionArg | DynamicAuthenticationArg)[]> {
const resolvedArgs: any[] = [];
const resolvedArgs: (DynamicTemplateFunctionArg | DynamicAuthenticationArg)[] = [];
for (const { dynamic, ...arg } of args) {
const newArg: any = {
const dynamicResult =
typeof dynamic === 'function'
? await dynamic(
ctx,
callArgs as CallTemplateFunctionArgs & CallHttpAuthenticationActionArgs,
)
: undefined;
const newArg = {
...arg,
...(typeof dynamic === 'function' ? await dynamic(ctx, callArgs as any) : undefined),
};
...dynamicResult,
} as DynamicTemplateFunctionArg | DynamicAuthenticationArg;
if ('inputs' in newArg && Array.isArray(newArg.inputs)) {
try {
newArg.inputs = await applyDynamicFormInput(ctx, newArg.inputs, callArgs as any);
newArg.inputs = await applyDynamicFormInput(
ctx,
newArg.inputs as DynamicTemplateFunctionArg[],
callArgs as CallTemplateFunctionArgs & CallHttpAuthenticationActionArgs,
);
} catch (e) {
console.error('Failed to apply dynamic form input', e);
}

View File

@@ -1,16 +1,16 @@
import type { InternalEvent } from '@yaakapp/api';
import WebSocket from 'ws';
import { EventChannel } from './EventChannel';
import { PluginHandle } from './PluginHandle';
import WebSocket from 'ws';
const port = process.env.PORT;
if (!port) {
throw new Error('Plugin runtime missing PORT')
throw new Error('Plugin runtime missing PORT');
}
const host = process.env.HOST;
if (!host) {
throw new Error('Plugin runtime missing HOST')
throw new Error('Plugin runtime missing HOST');
}
const pluginToAppEvents = new EventChannel();
@@ -26,7 +26,7 @@ ws.on('message', async (e: Buffer) => {
}
});
ws.on('open', () => console.log('Plugin runtime connected to websocket'));
ws.on('error', (err: any) => console.error('Plugin runtime websocket error', err));
ws.on('error', (err: unknown) => console.error('Plugin runtime websocket error', err));
ws.on('close', (code: number) => console.log('Plugin runtime websocket closed', code));
// Listen for incoming events from plugins
@@ -39,7 +39,12 @@ async function handleIncoming(msg: string) {
const pluginEvent: InternalEvent = JSON.parse(msg);
// Handle special event to bootstrap plugin
if (pluginEvent.payload.type === 'boot_request') {
const plugin = new PluginHandle(pluginEvent.pluginRefId, pluginEvent.context, pluginEvent.payload, pluginToAppEvents);
const plugin = new PluginHandle(
pluginEvent.pluginRefId,
pluginEvent.context,
pluginEvent.payload,
pluginToAppEvents,
);
plugins[pluginEvent.pluginRefId] = plugin;
}
@@ -62,3 +67,7 @@ async function handleIncoming(msg: string) {
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
});

View File

@@ -1,28 +1,20 @@
import process from "node:process";
import process from 'node:process';
export function interceptStdout(
intercept: (text: string) => string,
) {
export function interceptStdout(intercept: (text: string) => string) {
const old_stdout_write = process.stdout.write;
const old_stderr_write = process.stderr.write;
process.stdout.write = (function (write) {
return function (text: string) {
arguments[0] = interceptor(text, intercept);
// deno-lint-ignore no-explicit-any
write.apply(process.stdout, arguments as any);
process.stdout.write = ((write) =>
((text: string, ...args: never[]) => {
write.call(process.stdout, interceptor(text, intercept), ...args);
return true;
};
})(process.stdout.write);
}) as typeof process.stdout.write)(process.stdout.write);
process.stderr.write = (function (write) {
return function (text: string) {
arguments[0] = interceptor(text, intercept);
// deno-lint-ignore no-explicit-any
write.apply(process.stderr, arguments as any);
process.stderr.write = ((write) =>
((text: string, ...args: never[]) => {
write.call(process.stderr, interceptor(text, intercept), ...args);
return true;
};
})(process.stderr.write);
}) as typeof process.stderr.write)(process.stderr.write);
// puts back to original
return function unhook() {
@@ -32,6 +24,5 @@ export function interceptStdout(
}
function interceptor(text: string, fn: (text: string) => string) {
return fn(text).replace(/\n$/, "") +
(fn(text) && /\n$/.test(text) ? "\n" : "");
return fn(text).replace(/\n$/, '') + (fn(text) && /\n$/.test(text) ? '\n' : '');
}

View File

@@ -5,10 +5,15 @@ export function migrateTemplateFunctionSelectOptions(
): TemplateFunctionPlugin {
const migratedArgs = f.args.map((a) => {
if (a.type === 'select') {
a.options = a.options.map((o) => ({
...o,
label: o.label || (o as any).name,
}));
// Migrate old options that had 'name' instead of 'label'
type LegacyOption = { label?: string; value: string; name?: string };
a.options = a.options.map((o) => {
const legacy = o as LegacyOption;
return {
label: legacy.label ?? legacy.name ?? '',
value: legacy.value,
};
});
}
return a;
});

View File

@@ -1,7 +1,8 @@
import { CallTemplateFunctionArgs } from '@yaakapp-internal/plugins';
import { Context, DynamicTemplateFunctionArg } from '@yaakapp/api';
import { applyFormInputDefaults } from '@yaakapp-internal/lib/templateFunction';
import type { CallTemplateFunctionArgs } from '@yaakapp-internal/plugins';
import type { Context, DynamicTemplateFunctionArg } from '@yaakapp/api';
import { describe, expect, test } from 'vitest';
import { applyDynamicFormInput, applyFormInputDefaults } from '../src/common';
import { applyDynamicFormInput } from '../src/common';
describe('applyFormInputDefaults', () => {
test('Works with top-level select', () => {

1
plugins-external/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*/build

View File

@@ -0,0 +1,76 @@
# Yaak Faker Plugin
This is a template function that generates realistic fake data
for testing and development using [FakerJS](https://fakerjs.dev).
![CleanShot 2024-09-19 at 13 56 33@2x](https://github.com/user-attachments/assets/2f935110-4af2-4236-a50d-18db5454176d)
## Example JSON Body
Here's an example JSON body that uses fake data:
```json
{
"id": "${[ faker.string.uuid() ]}",
"name": "${[ faker.person.fullName() ]}",
"email": "${[ faker.internet.email() ]}",
"phone": "${[ faker.phone.number() ]}",
"address": {
"street": "${[ faker.location.streetAddress() ]}",
"city": "${[ faker.location.city() ]}",
"country": "${[ faker.location.country() ]}",
"zipCode": "${[ faker.location.zipCode() ]}"
},
"company": "${[ faker.company.name() ]}",
"website": "${[ faker.internet.url() ]}"
}
```
This will generate a random JSON body on every request:
```json
{
"id": "589f0aec-7310-4bf2-81c4-0b1bb7f1c3c1",
"name": "Lucy Gottlieb-Weissnat",
"email": "Destiny_Herzog@gmail.com",
"phone": "411.805.2871 x699",
"address": {
"street": "846 Christ Mills",
"city": "Spencerfurt",
"country": "United Kingdom",
"zipCode": "20354"
},
"company": "Emard, Kohler and Rutherford",
"website": "https://watery-detective.org"
}
```
## Available Categories
The plugin provides access to all FakerJS modules and their methods:
| Category | Description | Example Methods |
|------------|---------------------------|--------------------------------------------|
| `airline` | Airline-related data | `aircraftType`, `airline`, `airplane` |
| `animal` | Animal names and types | `bear`, `bird`, `cat`, `dog`, `fish` |
| `color` | Colors in various formats | `human`, `rgb`, `hex`, `hsl` |
| `commerce` | E-commerce data | `department`, `product`, `price` |
| `company` | Company information | `name`, `catchPhrase`, `bs` |
| `database` | Database-related data | `column`, `type`, `collation` |
| `date` | Date and time values | `recent`, `future`, `past`, `between` |
| `finance` | Financial data | `account`, `amount`, `currency` |
| `git` | Git-related data | `branch`, `commitEntry`, `commitSha` |
| `hacker` | Tech/hacker terminology | `abbreviation`, `noun`, `phrase` |
| `image` | Image URLs and data | `avatar`, `url`, `dataUri` |
| `internet` | Internet-related data | `email`, `url`, `ip`, `userAgent` |
| `location` | Geographic data | `city`, `country`, `latitude`, `longitude` |
| `lorem` | Lorem ipsum text | `word`, `sentence`, `paragraph` |
| `person` | Personal information | `firstName`, `lastName`, `fullName` |
| `music` | Music-related data | `genre`, `songName`, `artist` |
| `number` | Numeric data | `int`, `float`, `binary`, `hex` |
| `phone` | Phone numbers | `number`, `imei` |
| `science` | Scientific data | `chemicalElement`, `unit` |
| `string` | String utilities | `uuid`, `alpha`, `alphanumeric` |
| `system` | System-related data | `fileName`, `mimeType`, `fileExt` |
| `vehicle` | Vehicle information | `vehicle`, `manufacturer`, `model` |
| `word` | Word generation | `adjective`, `adverb`, `conjunction` |

View File

@@ -0,0 +1,24 @@
{
"name": "@yaak/faker",
"private": true,
"version": "1.1.1",
"displayName": "Faker",
"description": "Template functions for generating fake data using FakerJS",
"repository": {
"type": "git",
"url": "https://github.com/mountain-loop/yaak.git",
"directory": "plugins-external/faker"
},
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev",
"test": "vitest --run tests"
},
"dependencies": {
"@faker-js/faker": "^10.1.0"
},
"devDependencies": {
"@types/node": "^25.0.3",
"typescript": "^5.9.3"
}
}

View File

@@ -0,0 +1,105 @@
import { faker } from '@faker-js/faker';
import type { DynamicTemplateFunctionArg, PluginDefinition } from '@yaakapp/api';
const modules = [
'airline',
'animal',
'color',
'commerce',
'company',
'database',
'date',
'finance',
'git',
'hacker',
'image',
'internet',
'location',
'lorem',
'person',
'music',
'number',
'phone',
'science',
'string',
'system',
'vehicle',
'word',
];
function normalizeResult(result: unknown): string {
if (typeof result === 'string') return result;
return JSON.stringify(result);
}
// Whatever Yaaks arg type shape is rough example
function args(modName: string, fnName: string): DynamicTemplateFunctionArg[] {
return [
{
type: 'banner',
color: 'info',
inputs: [
{
type: 'markdown',
content: `Need help? View documentation for [\`${modName}.${fnName}(…)\`](https://fakerjs.dev/api/${encodeURIComponent(modName)}.html#${encodeURIComponent(fnName)})`,
},
],
},
{
name: 'options',
label: 'Arguments',
type: 'editor',
language: 'json',
optional: true,
placeholder: 'e.g. { "min": 1, "max": 10 } or 10 or ["en","US"]',
},
];
}
export const plugin: PluginDefinition = {
templateFunctions: modules.flatMap((modName) => {
const mod = faker[modName as keyof typeof faker];
return Object.keys(mod)
.filter((n) => n !== 'faker')
.map((fnName) => ({
name: ['faker', modName, fnName].join('.'),
args: args(modName, fnName),
async onRender(_ctx, args) {
const fn = mod[fnName as keyof typeof mod] as (...a: unknown[]) => unknown;
const options = args.values.options;
// No options supplied
if (options == null || options === '') {
return normalizeResult(fn());
}
// Try JSON first
let parsed: unknown = options;
if (typeof options === 'string') {
try {
parsed = JSON.parse(options);
} catch {
// Not valid JSON maybe just a scalar
const n = Number(options);
if (!Number.isNaN(n)) {
parsed = n;
} else {
parsed = options;
}
}
}
let result: unknown;
if (Array.isArray(parsed)) {
// Treat as positional arguments
result = fn(...parsed);
} else {
// Treat as a single argument (option object or scalar)
result = fn(parsed);
}
return normalizeResult(result);
},
}));
}),
};

View File

@@ -0,0 +1,9 @@
import { describe, expect, it } from 'vitest';
describe('formatDatetime', () => {
it('returns formatted current date', async () => {
// Ensure the plugin imports properly
const faker = await import('../src/index');
expect(faker.plugin.templateFunctions?.length).toBe(226);
});
});

View File

@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}

View File

@@ -0,0 +1,35 @@
# Yaak MCP Server Plugin
Exposes Yaak's functionality via the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/).
## Setup
Add this to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
```json
{
"mcpServers": {
"yaak": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://127.0.0.1:64343/mcp"]
}
}
}
```
Restart Claude Desktop and make sure Yaak is running.
## Available Tools
- `list_http_requests` - List all HTTP requests in a workspace
- `get_http_request` - Get details of a specific HTTP request
- `send_http_request` - Send an HTTP request and get the response
- `create_http_request` - Create a new HTTP request
- `update_http_request` - Update an existing HTTP request
- `delete_http_request` - Delete an HTTP request
- `list_folders` - List all folders in a workspace
- `list_workspaces` - List all open workspaces
- `get_workspace_id` - Get the current workspace ID
- `get_environment_id` - Get the current environment ID
- `copy_to_clipboard` - Copy text to the system clipboard
- `show_toast` - Show a toast notification in Yaak

View File

@@ -0,0 +1,28 @@
{
"name": "@yaak/mcp-server",
"private": true,
"version": "0.1.7",
"displayName": "MCP Server",
"description": "Expose Yaak functionality via Model Context Protocol",
"minYaakVersion": "2025.10.0-beta.6",
"repository": {
"type": "git",
"url": "https://github.com/mountain-loop/yaak.git",
"directory": "plugins-external/mcp-server"
},
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev"
},
"dependencies": {
"@hono/mcp": "^0.2.3",
"@hono/node-server": "^1.19.7",
"@modelcontextprotocol/sdk": "^1.25.1",
"hono": "^4.11.3",
"zod": "^3.25.76"
},
"devDependencies": {
"@types/node": "^25.0.3",
"typescript": "^5.9.3"
}
}

View File

@@ -0,0 +1,36 @@
import type { Context, PluginDefinition } from '@yaakapp/api';
import { createMcpServer } from './server.js';
const serverPort = parseInt(process.env.YAAK_PLUGIN_MCP_SERVER_PORT ?? '64343', 10);
let mcpServer: Awaited<ReturnType<typeof createMcpServer>> | null = null;
export const plugin: PluginDefinition = {
async init(ctx: Context) {
// Start the server after waiting, so there's an active window open to do things
// like show the startup toast.
console.log('Initializing MCP Server plugin');
setTimeout(async () => {
try {
mcpServer = createMcpServer({ yaak: ctx }, serverPort);
} catch (err) {
console.error('Failed to start MCP server:', err);
ctx.toast.show({
message: `Failed to start MCP Server: ${err instanceof Error ? err.message : String(err)}`,
icon: 'alert_triangle',
color: 'danger',
timeout: 10000,
});
}
}, 5000);
},
async dispose() {
console.log('Disposing MCP Server plugin');
if (mcpServer) {
await mcpServer.close();
mcpServer = null;
}
},
};

View File

@@ -0,0 +1,72 @@
import { StreamableHTTPTransport } from '@hono/mcp';
import { serve } from '@hono/node-server';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { Hono } from 'hono';
import { registerFolderTools } from './tools/folder.js';
import { registerHttpRequestTools } from './tools/httpRequest.js';
import { registerToastTools } from './tools/toast.js';
import { registerWindowTools } from './tools/window.js';
import { registerWorkspaceTools } from './tools/workspace.js';
import type { McpServerContext } from './types.js';
export function createMcpServer(ctx: McpServerContext, port: number) {
console.log('Creating MCP server on port', port);
const mcpServer = new McpServer({
name: 'yaak-mcp-server',
version: '0.1.0',
});
// Register all tools
registerToastTools(mcpServer, ctx);
registerHttpRequestTools(mcpServer, ctx);
registerFolderTools(mcpServer, ctx);
registerWindowTools(mcpServer, ctx);
registerWorkspaceTools(mcpServer, ctx);
const app = new Hono();
const transport = new StreamableHTTPTransport();
app.all('/mcp', async (c) => {
if (!mcpServer.isConnected()) {
// Connect the mcp with the transport
await mcpServer.connect(transport);
ctx.yaak.toast.show({
message: `MCP Server connected`,
icon: 'info',
color: 'info',
timeout: 5000,
});
}
return transport.handleRequest(c);
});
const honoServer = serve(
{
port,
hostname: '127.0.0.1',
fetch: app.fetch,
},
(info) => {
console.log('Started MCP server on ', info.address);
ctx.yaak.toast.show({
message: `MCP Server running on http://127.0.0.1:${info.port}`,
icon: 'info',
color: 'secondary',
timeout: 10000,
});
},
);
return {
server: mcpServer,
close: async () => {
await new Promise<void>((resolve, reject) => {
honoServer.close((err) => {
if (err) reject(err);
else resolve();
});
});
await mcpServer.close();
},
};
}

View File

@@ -0,0 +1,33 @@
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import * as z from 'zod';
import type { McpServerContext } from '../types.js';
import { getWorkspaceContext } from './helpers.js';
export function registerFolderTools(server: McpServer, ctx: McpServerContext) {
server.registerTool(
'list_folders',
{
title: 'List Folders',
description: 'List all folders in a workspace',
inputSchema: {
workspaceId: z
.string()
.optional()
.describe('Workspace ID (required if multiple workspaces are open)'),
},
},
async ({ workspaceId }) => {
const workspaceCtx = await getWorkspaceContext(ctx, workspaceId);
const folders = await workspaceCtx.yaak.folder.list();
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(folders, null, 2),
},
],
};
},
);
}

View File

@@ -0,0 +1,32 @@
import type { McpServerContext } from '../types.js';
export async function getWorkspaceContext(
ctx: McpServerContext,
workspaceId?: string,
): Promise<McpServerContext> {
const workspaces = await ctx.yaak.workspace.list();
if (!workspaceId && workspaces.length > 1) {
const workspaceList = workspaces.map((w, i) => `${i + 1}. ${w.name} (ID: ${w.id})`).join('\n');
throw new Error(
`Multiple workspaces are open. Please specify which workspace to use.\n\n` +
`Currently open workspaces:\n${workspaceList}\n\n` +
`You can use the list_workspaces tool to get workspace IDs, then use other tools ` +
`with the workspace context. For example, ask the user which workspace they want ` +
`to work with by presenting them with the numbered list above.`,
);
}
const workspace = workspaceId ? workspaces.find((w) => w.id === workspaceId) : workspaces[0];
if (!workspace) {
const workspaceList = workspaces.map((w) => `- ${w.name} (ID: ${w.id})`).join('\n');
throw new Error(
`Workspace with ID "${workspaceId}" not found.\n\n` +
`Available workspaces:\n${workspaceList}`,
);
}
return {
yaak: ctx.yaak.workspace.withContext(workspace),
};
}

View File

@@ -0,0 +1,298 @@
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import * as z from 'zod';
import type { McpServerContext } from '../types.js';
import { getWorkspaceContext } from './helpers.js';
export function registerHttpRequestTools(server: McpServer, ctx: McpServerContext) {
server.registerTool(
'list_http_requests',
{
title: 'List HTTP Requests',
description: 'List all HTTP requests in a workspace',
inputSchema: {
workspaceId: z
.string()
.optional()
.describe('Workspace ID (required if multiple workspaces are open)'),
},
},
async ({ workspaceId }) => {
const workspaceCtx = await getWorkspaceContext(ctx, workspaceId);
const requests = await workspaceCtx.yaak.httpRequest.list();
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(requests, null, 2),
},
],
};
},
);
server.registerTool(
'get_http_request',
{
title: 'Get HTTP Request',
description: 'Get details of a specific HTTP request by ID',
inputSchema: {
id: z.string().describe('The HTTP request ID'),
workspaceId: z
.string()
.optional()
.describe('Workspace ID (required if multiple workspaces are open)'),
},
},
async ({ id, workspaceId }) => {
const workspaceCtx = await getWorkspaceContext(ctx, workspaceId);
const request = await workspaceCtx.yaak.httpRequest.getById({ id });
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(request, null, 2),
},
],
};
},
);
server.registerTool(
'send_http_request',
{
title: 'Send HTTP Request',
description: 'Send an HTTP request and get the response',
inputSchema: {
id: z.string().describe('The HTTP request ID to send'),
environmentId: z.string().optional().describe('Optional environment ID to use'),
workspaceId: z
.string()
.optional()
.describe('Workspace ID (required if multiple workspaces are open)'),
},
},
async ({ id, workspaceId }) => {
const workspaceCtx = await getWorkspaceContext(ctx, workspaceId);
const httpRequest = await workspaceCtx.yaak.httpRequest.getById({ id });
if (httpRequest == null) {
throw new Error(`HTTP request with ID ${id} not found`);
}
const response = await workspaceCtx.yaak.httpRequest.send({ httpRequest });
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(response, null, 2),
},
],
};
},
);
server.registerTool(
'create_http_request',
{
title: 'Create HTTP Request',
description: 'Create a new HTTP request',
inputSchema: {
workspaceId: z
.string()
.optional()
.describe('Workspace ID (required if multiple workspaces are open)'),
name: z
.string()
.optional()
.describe('Request name (empty string to auto-generate from URL)'),
url: z.string().describe('Request URL'),
method: z.string().optional().describe('HTTP method (defaults to GET)'),
folderId: z.string().optional().describe('Parent folder ID'),
description: z.string().optional().describe('Request description'),
headers: z
.array(
z.object({
name: z.string(),
value: z.string(),
enabled: z.boolean().default(true),
}),
)
.optional()
.describe('Request headers'),
urlParameters: z
.array(
z.object({
name: z.string(),
value: z.string(),
enabled: z.boolean().default(true),
}),
)
.optional()
.describe('URL query parameters'),
bodyType: z
.string()
.optional()
.describe(
'Body type. Supported values: "binary", "graphql", "application/x-www-form-urlencoded", "multipart/form-data", or any text-based type (e.g., "application/json", "text/plain")',
),
body: z
.record(z.string(), z.any())
.optional()
.describe(
'Body content object. Structure varies by bodyType:\n' +
'- "binary": { filePath: "/path/to/file" }\n' +
'- "graphql": { query: "{ users { name } }", variables: "{\\"id\\": \\"123\\"}" }\n' +
'- "application/x-www-form-urlencoded": { form: [{ name: "key", value: "val", enabled: true }] }\n' +
'- "multipart/form-data": { form: [{ name: "field", value: "text", file: "/path/to/file", enabled: true }] }\n' +
'- text-based (application/json, etc.): { text: "raw body content" }',
),
authenticationType: z
.string()
.optional()
.describe(
'Authentication type. Common values: "basic", "bearer", "oauth2", "apikey", "jwt", "awsv4", "oauth1", "ntlm", "none". Use null to inherit from parent folder/workspace.',
),
authentication: z
.record(z.string(), z.any())
.optional()
.describe(
'Authentication configuration object. Structure varies by authenticationType:\n' +
'- "basic": { username: "user", password: "pass" }\n' +
'- "bearer": { token: "abc123", prefix: "Bearer" }\n' +
'- "oauth2": { clientId: "...", clientSecret: "...", grantType: "authorization_code", authorizationUrl: "...", accessTokenUrl: "...", scope: "...", ... }\n' +
'- "apikey": { location: "header" | "query", key: "X-API-Key", value: "..." }\n' +
'- "jwt": { algorithm: "HS256", secret: "...", payload: "{ ... }" }\n' +
'- "awsv4": { accessKeyId: "...", secretAccessKey: "...", service: "sts", region: "us-east-1", sessionToken: "..." }\n' +
'- "none": {}',
),
},
},
async ({ workspaceId: ogWorkspaceId, ...args }) => {
const workspaceCtx = await getWorkspaceContext(ctx, ogWorkspaceId);
const workspaceId = await workspaceCtx.yaak.window.workspaceId();
if (!workspaceId) {
throw new Error('No workspace is open');
}
const httpRequest = await workspaceCtx.yaak.httpRequest.create({
workspaceId: workspaceId,
...args,
});
return {
content: [{ type: 'text' as const, text: JSON.stringify(httpRequest, null, 2) }],
};
},
);
server.registerTool(
'update_http_request',
{
title: 'Update HTTP Request',
description: 'Update an existing HTTP request',
inputSchema: {
id: z.string().describe('HTTP request ID to update'),
workspaceId: z.string().describe('Workspace ID'),
name: z.string().optional().describe('Request name'),
url: z.string().optional().describe('Request URL'),
method: z.string().optional().describe('HTTP method'),
folderId: z.string().optional().describe('Parent folder ID'),
description: z.string().optional().describe('Request description'),
headers: z
.array(
z.object({
name: z.string(),
value: z.string(),
enabled: z.boolean().default(true),
}),
)
.optional()
.describe('Request headers'),
urlParameters: z
.array(
z.object({
name: z.string(),
value: z.string(),
enabled: z.boolean().default(true),
}),
)
.optional()
.describe('URL query parameters'),
bodyType: z
.string()
.optional()
.describe(
'Body type. Supported values: "binary", "graphql", "application/x-www-form-urlencoded", "multipart/form-data", or any text-based type (e.g., "application/json", "text/plain")',
),
body: z
.record(z.string(), z.any())
.optional()
.describe(
'Body content object. Structure varies by bodyType:\n' +
'- "binary": { filePath: "/path/to/file" }\n' +
'- "graphql": { query: "{ users { name } }", variables: "{\\"id\\": \\"123\\"}" }\n' +
'- "application/x-www-form-urlencoded": { form: [{ name: "key", value: "val", enabled: true }] }\n' +
'- "multipart/form-data": { form: [{ name: "field", value: "text", file: "/path/to/file", enabled: true }] }\n' +
'- text-based (application/json, etc.): { text: "raw body content" }',
),
authenticationType: z
.string()
.optional()
.describe(
'Authentication type. Common values: "basic", "bearer", "oauth2", "apikey", "jwt", "awsv4", "oauth1", "ntlm", "none". Use null to inherit from parent folder/workspace.',
),
authentication: z
.record(z.string(), z.any())
.optional()
.describe(
'Authentication configuration object. Structure varies by authenticationType:\n' +
'- "basic": { username: "user", password: "pass" }\n' +
'- "bearer": { token: "abc123", prefix: "Bearer" }\n' +
'- "oauth2": { clientId: "...", clientSecret: "...", grantType: "authorization_code", authorizationUrl: "...", accessTokenUrl: "...", scope: "...", ... }\n' +
'- "apikey": { location: "header" | "query", key: "X-API-Key", value: "..." }\n' +
'- "jwt": { algorithm: "HS256", secret: "...", payload: "{ ... }" }\n' +
'- "awsv4": { accessKeyId: "...", secretAccessKey: "...", service: "sts", region: "us-east-1", sessionToken: "..." }\n' +
'- "none": {}',
),
},
},
async ({ id, workspaceId, ...updates }) => {
const workspaceCtx = await getWorkspaceContext(ctx, workspaceId);
// Fetch existing request to merge with updates
const existing = await workspaceCtx.yaak.httpRequest.getById({ id });
if (!existing) {
throw new Error(`HTTP request with ID ${id} not found`);
}
// Merge existing fields with updates
const httpRequest = await workspaceCtx.yaak.httpRequest.update({
...existing,
...updates,
id,
});
return {
content: [{ type: 'text' as const, text: JSON.stringify(httpRequest, null, 2) }],
};
},
);
server.registerTool(
'delete_http_request',
{
title: 'Delete HTTP Request',
description: 'Delete an HTTP request by ID',
inputSchema: {
id: z.string().describe('HTTP request ID to delete'),
},
},
async ({ id }) => {
const httpRequest = await ctx.yaak.httpRequest.delete({ id });
return {
content: [
{ type: 'text' as const, text: `Deleted: ${httpRequest.name} (${httpRequest.id})` },
],
};
},
);
}

View File

@@ -0,0 +1,59 @@
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { Color, Icon } from '@yaakapp/api';
import * as z from 'zod';
import type { McpServerContext } from '../types.js';
const ICON_VALUES = [
'alert_triangle',
'check',
'check_circle',
'chevron_down',
'copy',
'info',
'pin',
'search',
'trash',
] as const satisfies readonly Icon[];
const COLOR_VALUES = [
'primary',
'secondary',
'info',
'success',
'notice',
'warning',
'danger',
] as const satisfies readonly Color[];
export function registerToastTools(server: McpServer, ctx: McpServerContext) {
server.registerTool(
'show_toast',
{
title: 'Show Toast',
description: 'Show a toast notification in Yaak',
inputSchema: {
message: z.string().describe('The message to display'),
icon: z.enum(ICON_VALUES).optional().describe('Icon name'),
color: z.enum(COLOR_VALUES).optional().describe('Toast color'),
timeout: z.number().optional().describe('Timeout in milliseconds'),
},
},
async ({ message, icon, color, timeout }) => {
await ctx.yaak.toast.show({
message,
icon,
color,
timeout,
});
return {
content: [
{
type: 'text' as const,
text: `✓ Toast shown: "${message}"`,
},
],
};
},
);
}

View File

@@ -0,0 +1,47 @@
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { McpServerContext } from '../types.js';
import { getWorkspaceContext } from './helpers.js';
export function registerWindowTools(server: McpServer, ctx: McpServerContext) {
server.registerTool(
'get_workspace_id',
{
title: 'Get Workspace ID',
description: 'Get the current workspace ID',
},
async () => {
const workspaceCtx = await getWorkspaceContext(ctx);
const workspaceId = await workspaceCtx.yaak.window.workspaceId();
return {
content: [
{
type: 'text' as const,
text: workspaceId || 'No workspace open',
},
],
};
},
);
server.registerTool(
'get_environment_id',
{
title: 'Get Environment ID',
description: 'Get the current environment ID',
},
async () => {
const workspaceCtx = await getWorkspaceContext(ctx);
const environmentId = await workspaceCtx.yaak.window.environmentId();
return {
content: [
{
type: 'text' as const,
text: environmentId || 'No environment selected',
},
],
};
},
);
}

View File

@@ -0,0 +1,24 @@
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { McpServerContext } from '../types.js';
export function registerWorkspaceTools(server: McpServer, ctx: McpServerContext) {
server.registerTool(
'list_workspaces',
{
title: 'List Workspaces',
description: 'List all open workspaces in Yaak',
},
async () => {
const workspaces = await ctx.yaak.workspace.list();
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(workspaces, null, 2),
},
],
};
},
);
}

View File

@@ -0,0 +1,5 @@
import type { Context } from '@yaakapp/api';
export interface McpServerContext {
yaak: Context;
}

View File

@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"skipLibCheck": true,
"moduleResolution": "Bundler"
}
}

View File

@@ -0,0 +1,16 @@
{
"name": "@yaak/action-send-folder",
"displayName": "Send All",
"description": "Send all HTTP requests in a folder sequentially",
"repository": {
"type": "git",
"url": "https://github.com/mountain-loop/yaak.git",
"directory": "plugins/action-send-folder"
},
"private": true,
"version": "0.1.0",
"scripts": {
"build": "yaakcli build",
"dev": "yaakcli dev"
}
}

View File

@@ -0,0 +1,74 @@
import type { PluginDefinition } from '@yaakapp/api';
export const plugin: PluginDefinition = {
folderActions: [
{
label: 'Send All',
icon: 'send_horizontal',
async onSelect(ctx, args) {
const targetFolder = args.folder;
// Get all folders and HTTP requests
const [allFolders, allRequests] = await Promise.all([
ctx.folder.list(),
ctx.httpRequest.list(),
]);
// Build a set of all folder IDs that are descendants of the target folder
const folderIds = new Set<string>([targetFolder.id]);
const addDescendants = (parentId: string) => {
for (const folder of allFolders) {
if (folder.folderId === parentId && !folderIds.has(folder.id)) {
folderIds.add(folder.id);
addDescendants(folder.id);
}
}
};
addDescendants(targetFolder.id);
// Filter HTTP requests to those in the target folder or its descendants
const requestsToSend = allRequests.filter(
(req) => req.folderId != null && folderIds.has(req.folderId),
);
if (requestsToSend.length === 0) {
await ctx.toast.show({
message: 'No requests in folder',
icon: 'info',
color: 'info',
});
return;
}
// Send each request sequentially
let successCount = 0;
let errorCount = 0;
for (const request of requestsToSend) {
try {
await ctx.httpRequest.send({ httpRequest: request });
successCount++;
} catch (error) {
errorCount++;
console.error(`Failed to send request ${request.id}:`, error);
}
}
// Show summary toast
if (errorCount === 0) {
await ctx.toast.show({
message: `Sent ${successCount} request${successCount !== 1 ? 's' : ''}`,
icon: 'send_horizontal',
color: 'success',
});
} else {
await ctx.toast.show({
message: `Sent ${successCount}, failed ${errorCount}`,
icon: 'alert_triangle',
color: 'warning',
});
}
},
},
],
};

View File

@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}

View File

@@ -71,10 +71,10 @@ export async function getOrRefreshAccessToken(
httpRequest.authenticationType = 'none'; // Don't inherit workspace auth
const resp = await ctx.httpRequest.send({ httpRequest });
if (resp.status === 401) {
// Bad refresh token, so we'll force it to fetch a fresh access token by deleting
// and returning null;
console.log('[oauth2] Unauthorized refresh_token request');
if (resp.status >= 400 && resp.status < 500) {
// Client errors (4xx) indicate the refresh token is invalid, expired, or revoked
// Delete the token and return null to trigger a fresh authorization flow
console.log('[oauth2] Refresh token request failed with client error, deleting token');
await deleteToken(ctx, tokenArgs);
return null;
}

View File

@@ -55,6 +55,34 @@ export const plugin: PluginDefinition = {
},
};
/**
* Decodes escape sequences in shell $'...' strings
* Handles Unicode escape sequences (\uXXXX) and common escape codes
*/
function decodeShellString(str: string): string {
return str
.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
.replace(/\\x([0-9a-fA-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
.replace(/\\n/g, '\n')
.replace(/\\r/g, '\r')
.replace(/\\t/g, '\t')
.replace(/\\'/g, "'")
.replace(/\\"/g, '"')
.replace(/\\\\/g, '\\');
}
/**
* Checks if a string might contain escape sequences that need decoding
* If so, decodes them; otherwise returns the string as-is
*/
function maybeDecodeEscapeSequences(str: string): string {
// Check if the string contains escape sequences that shell-quote might not handle
if (str.includes('\\u') || str.includes('\\x')) {
return decodeShellString(str);
}
return str;
}
export function convertCurl(rawData: string) {
if (!rawData.match(/^\s*curl /)) {
return null;
@@ -86,9 +114,11 @@ export function convertCurl(rawData: string) {
for (const parseEntry of normalizedParseEntries) {
if (typeof parseEntry === 'string') {
if (parseEntry.startsWith('$')) {
currentCommand.push(parseEntry.slice(1));
// Handle $'...' strings from shell-quote - decode escape sequences
currentCommand.push(decodeShellString(parseEntry.slice(1)));
} else {
currentCommand.push(parseEntry);
// Decode escape sequences that shell-quote might not handle
currentCommand.push(maybeDecodeEscapeSequences(parseEntry));
}
continue;
}
@@ -108,7 +138,7 @@ export function convertCurl(rawData: string) {
if (op?.startsWith('$')) {
// Handle the case where literal like -H $'Header: \'Some Quoted Thing\''
const str = op.slice(2, op.length - 1).replace(/\\'/g, "'");
const str = decodeShellString(op.slice(2, op.length - 1));
currentCommand.push(str);
continue;
@@ -164,11 +194,18 @@ function importCommand(parseEntries: ParseEntry[], workspaceId: string) {
let value: string | boolean;
const nextEntry = parseEntries[i + 1];
const hasValue = !BOOLEAN_FLAGS.includes(name);
// Check if nextEntry looks like a flag:
// - Single dash followed by a letter: -X, -H, -d
// - Double dash followed by a letter: --data-raw, --header
// This prevents mistaking data that starts with dashes (like multipart boundaries ------) as flags
const nextEntryIsFlag =
typeof nextEntry === 'string' &&
(nextEntry.match(/^-[a-zA-Z]/) || nextEntry.match(/^--[a-zA-Z]/));
if (isSingleDash && name.length > 1) {
// Handle squished arguments like -XPOST
value = name.slice(1);
name = name.slice(0, 1);
} else if (typeof nextEntry === 'string' && hasValue && !nextEntry.startsWith('-')) {
} else if (typeof nextEntry === 'string' && hasValue && !nextEntryIsFlag) {
// Next arg is not a flag, so assign it as the value
value = nextEntry;
i++; // Skip next one
@@ -275,11 +312,34 @@ function importCommand(parseEntries: ParseEntry[], workspaceId: string) {
}
// Body (Text or Blob)
const dataParameters = pairsToDataParameters(flagsByName);
const contentTypeHeader = headers.find((header) => header.name.toLowerCase() === 'content-type');
const mimeType = contentTypeHeader ? contentTypeHeader.value.split(';')[0] : null;
const mimeType = contentTypeHeader ? contentTypeHeader.value.split(';')[0]?.trim() : null;
// Body (Multipart Form Data)
// Extract boundary from Content-Type header for multipart parsing
const boundaryMatch = contentTypeHeader?.value.match(/boundary=([^\s;]+)/i);
const boundary = boundaryMatch?.[1];
// Get raw data from --data-raw flags (before splitting by &)
const rawDataValues = [
...((flagsByName['data-raw'] as string[] | undefined) || []),
...((flagsByName.d as string[] | undefined) || []),
...((flagsByName.data as string[] | undefined) || []),
...((flagsByName['data-binary'] as string[] | undefined) || []),
...((flagsByName['data-ascii'] as string[] | undefined) || []),
];
// Check if this is multipart form data in --data-raw (Chrome DevTools format)
let multipartFormDataFromRaw:
| { name: string; value?: string; file?: string; enabled: boolean }[]
| null = null;
if (mimeType === 'multipart/form-data' && boundary && rawDataValues.length > 0) {
const rawBody = rawDataValues.join('');
multipartFormDataFromRaw = parseMultipartFormData(rawBody, boundary);
}
const dataParameters = pairsToDataParameters(flagsByName);
// Body (Multipart Form Data from -F flags)
const formDataParams = [
...((flagsByName.form as string[] | undefined) || []),
...((flagsByName.F as string[] | undefined) || []),
@@ -306,7 +366,13 @@ function importCommand(parseEntries: ParseEntry[], workspaceId: string) {
let bodyType: string | null = null;
const bodyAsGET = getPairValue(flagsByName, false, ['G', 'get']);
if (dataParameters.length > 0 && bodyAsGET) {
if (multipartFormDataFromRaw) {
// Handle multipart form data parsed from --data-raw (Chrome DevTools format)
bodyType = 'multipart/form-data';
body = {
form: multipartFormDataFromRaw,
};
} else if (dataParameters.length > 0 && bodyAsGET) {
urlParameters.push(...dataParameters);
} else if (
dataParameters.length > 0 &&
@@ -443,6 +509,71 @@ function splitOnce(str: string, sep: string): string[] {
return [str];
}
/**
* Parses multipart form data from a raw body string
* Used when Chrome DevTools exports a cURL with --data-raw containing multipart data
*/
function parseMultipartFormData(
rawBody: string,
boundary: string,
): { name: string; value?: string; file?: string; enabled: boolean }[] | null {
const results: { name: string; value?: string; file?: string; enabled: boolean }[] = [];
// The boundary in the body typically has -- prefix
const boundaryMarker = `--${boundary}`;
const parts = rawBody.split(boundaryMarker);
for (const part of parts) {
// Skip empty parts and the closing boundary marker
if (!part || part.trim() === '--' || part.trim() === '--\r\n') {
continue;
}
// Each part has headers and content separated by \r\n\r\n
const headerContentSplit = part.indexOf('\r\n\r\n');
if (headerContentSplit === -1) {
continue;
}
const headerSection = part.slice(0, headerContentSplit);
let content = part.slice(headerContentSplit + 4); // Skip \r\n\r\n
// Remove trailing \r\n from content
if (content.endsWith('\r\n')) {
content = content.slice(0, -2);
}
// Parse Content-Disposition header to get name and filename
const contentDispositionMatch = headerSection.match(
/Content-Disposition:\s*form-data;\s*name="([^"]+)"(?:;\s*filename="([^"]+)")?/i,
);
if (!contentDispositionMatch) {
continue;
}
const name = contentDispositionMatch[1] ?? '';
const filename = contentDispositionMatch[2];
const item: { name: string; value?: string; file?: string; enabled: boolean } = {
name,
enabled: true,
};
if (filename) {
// This is a file upload field
item.file = filename;
} else {
// This is a regular text field
item.value = content;
}
results.push(item);
}
return results.length > 0 ? results : null;
}
const idCount: Partial<Record<string, number>> = {};
function generateId(model: string): string {

View File

@@ -391,6 +391,122 @@ describe('importer-curl', () => {
},
});
});
test('Imports data with Unicode escape sequences', () => {
expect(
convertCurl(
`curl 'https://yaak.app' -H 'Content-Type: application/json' --data-raw $'{"query":"SearchQueryInput\\u0021"}' -X POST`,
),
).toEqual({
resources: {
workspaces: [baseWorkspace()],
httpRequests: [
baseRequest({
url: 'https://yaak.app',
method: 'POST',
headers: [{ name: 'Content-Type', value: 'application/json', enabled: true }],
bodyType: 'application/json',
body: { text: '{"query":"SearchQueryInput!"}' },
}),
],
},
});
});
test('Imports data with multiple escape sequences', () => {
expect(
convertCurl(
`curl 'https://yaak.app' --data-raw $'Line1\\nLine2\\tTab\\u0021Exclamation' -X POST`,
),
).toEqual({
resources: {
workspaces: [baseWorkspace()],
httpRequests: [
baseRequest({
url: 'https://yaak.app',
method: 'POST',
bodyType: 'application/x-www-form-urlencoded',
body: {
form: [{ name: 'Line1\nLine2\tTab!Exclamation', value: '', enabled: true }],
},
headers: [
{
enabled: true,
name: 'Content-Type',
value: 'application/x-www-form-urlencoded',
},
],
}),
],
},
});
});
test('Imports multipart form data from --data-raw (Chrome DevTools format)', () => {
// This is the format Chrome DevTools uses when copying a multipart form submission as cURL
const curlCommand = `curl 'http://localhost:8080/system' \
-H 'Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryHwsXKi4rKA6P5VBd' \
--data-raw $'------WebKitFormBoundaryHwsXKi4rKA6P5VBd\r\nContent-Disposition: form-data; name="username"\r\n\r\njsgj\r\n------WebKitFormBoundaryHwsXKi4rKA6P5VBd\r\nContent-Disposition: form-data; name="password"\r\n\r\n654321\r\n------WebKitFormBoundaryHwsXKi4rKA6P5VBd\r\nContent-Disposition: form-data; name="captcha"; filename="test.xlsx"\r\nContent-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\r\n\r\n\r\n------WebKitFormBoundaryHwsXKi4rKA6P5VBd--\r\n'`;
expect(convertCurl(curlCommand)).toEqual({
resources: {
workspaces: [baseWorkspace()],
httpRequests: [
baseRequest({
url: 'http://localhost:8080/system',
method: 'POST',
headers: [
{
name: 'Content-Type',
value: 'multipart/form-data; boundary=----WebKitFormBoundaryHwsXKi4rKA6P5VBd',
enabled: true,
},
],
bodyType: 'multipart/form-data',
body: {
form: [
{ name: 'username', value: 'jsgj', enabled: true },
{ name: 'password', value: '654321', enabled: true },
{ name: 'captcha', file: 'test.xlsx', enabled: true },
],
},
}),
],
},
});
});
test('Imports multipart form data with text-only fields from --data-raw', () => {
const curlCommand = `curl 'http://example.com/api' \
-H 'Content-Type: multipart/form-data; boundary=----FormBoundary123' \
--data-raw $'------FormBoundary123\r\nContent-Disposition: form-data; name="field1"\r\n\r\nvalue1\r\n------FormBoundary123\r\nContent-Disposition: form-data; name="field2"\r\n\r\nvalue2\r\n------FormBoundary123--\r\n'`;
expect(convertCurl(curlCommand)).toEqual({
resources: {
workspaces: [baseWorkspace()],
httpRequests: [
baseRequest({
url: 'http://example.com/api',
method: 'POST',
headers: [
{
name: 'Content-Type',
value: 'multipart/form-data; boundary=----FormBoundary123',
enabled: true,
},
],
bodyType: 'multipart/form-data',
body: {
form: [
{ name: 'field1', value: 'value1', enabled: true },
{ name: 'field2', value: 'value2', enabled: true },
],
},
}),
],
},
});
});
});
const idCount: Partial<Record<string, number>> = {};

View File

@@ -5,9 +5,9 @@
"private": true,
"version": "0.1.0",
"scripts": {
"build": "run-p build:*",
"build": "run-s build:*",
"build:1-build": "yaakcli build",
"build:2-cpywasm": "cpx '../../node_modules/@1password/sdk-core/nodejs/core_bg.*' build/",
"build:2-cpywasm": "cpx \"../../node_modules/@1password/sdk-core/nodejs/core_bg.wasm\" build/",
"dev": "yaakcli dev"
},
"dependencies": {

View File

@@ -1,26 +1,86 @@
import crypto from 'node:crypto';
import type { Client } from '@1password/sdk';
import { createClient } from '@1password/sdk';
import type { PluginDefinition } from '@yaakapp/api';
import { createClient, DesktopAuth } from '@1password/sdk';
import type { JsonPrimitive, PluginDefinition } from '@yaakapp/api';
import type { CallTemplateFunctionArgs } from '@yaakapp-internal/plugins';
const _clients: Record<string, Client> = {};
async function op(args: CallTemplateFunctionArgs): Promise<Client | null> {
const token = args.values.token;
if (typeof token !== 'string') return null;
async function op(args: CallTemplateFunctionArgs): Promise<{ client?: Client; error?: unknown }> {
let authMethod: string | DesktopAuth | null = null;
let hash: string | null = null;
switch (args.values.authMethod) {
case 'desktop': {
const account = args.values.token;
if (typeof account !== 'string' || !account) return { error: 'Missing account name' };
hash = crypto.createHash('sha256').update(`desktop:${account}`).digest('hex');
authMethod = new DesktopAuth(account);
break;
}
case 'token': {
const token = args.values.token;
if (typeof token !== 'string' || !token) return { error: 'Missing service token' };
hash = crypto.createHash('sha256').update(`token:${token}`).digest('hex');
authMethod = token;
break;
}
}
if (hash == null || authMethod == null) return { error: 'Invalid authentication method' };
const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
try {
_clients[tokenHash] ??= await createClient({
auth: token,
_clients[hash] ??= await createClient({
auth: authMethod,
integrationName: 'Yaak 1Password Plugin',
integrationVersion: 'v1.0.0',
});
} catch {
return null;
} catch (e) {
return { error: e };
}
return _clients[tokenHash];
return { client: _clients[hash] };
}
async function getValue(
args: CallTemplateFunctionArgs,
vaultId?: JsonPrimitive,
itemId?: JsonPrimitive,
fieldId?: JsonPrimitive,
): Promise<{ value?: string; error?: unknown }> {
const { client, error } = await op(args);
if (!client) return { error };
if (vaultId && typeof vaultId === 'string') {
try {
await client.vaults.getOverview(vaultId);
} catch {
return { error: `Vault ${vaultId} not found` };
}
} else {
return { error: 'No vault specified' };
}
if (itemId && typeof itemId === 'string') {
try {
const item = await client.items.get(vaultId, itemId);
if (fieldId && typeof fieldId === 'string') {
const field = item.fields.find((f) => f.id === fieldId);
if (field) {
return { value: field.value };
} else {
return { error: `Field ${fieldId} not found in item ${itemId} in vault ${vaultId}` };
}
}
} catch {
return { error: `Item ${itemId} not found in vault ${vaultId}` };
}
} else {
return { error: 'No item specified' };
}
return {};
}
export const plugin: PluginDefinition = {
@@ -31,14 +91,50 @@ export const plugin: PluginDefinition = {
previewArgs: ['field'],
args: [
{
name: 'token',
type: 'text',
label: '1Password Service Account Token',
description:
'Token can be generated from the 1Password website by visiting Developer > Service Accounts',
// biome-ignore lint/suspicious/noTemplateCurlyInString: Yaak template syntax
defaultValue: '${[1PASSWORD_TOKEN]}',
password: true,
type: 'h_stack',
inputs: [
{
name: 'authMethod',
type: 'select',
label: 'Authentication Method',
defaultValue: 'token',
options: [
{
label: 'Service Account',
value: 'token',
},
{
label: 'Desktop App',
value: 'desktop',
},
],
},
{
name: 'token',
type: 'text',
// biome-ignore lint/suspicious/noTemplateCurlyInString: Yaak template syntax
defaultValue: '${[1PASSWORD_TOKEN]}',
dynamic(_ctx, args) {
switch (args.values.authMethod) {
case 'desktop':
return {
label: 'Account Name',
description:
'Account name can be taken from the sidebar of the 1Password App. Make sure you\'re on the BETA version of the 1Password app and have "Integrate with other apps" enabled in Settings > Developer.',
};
case 'token':
return {
label: 'Token',
description:
'Token can be generated from the 1Password website by visiting Developer > Service Accounts',
password: true,
};
}
return { hidden: true };
},
},
],
},
{
name: 'vault',
@@ -46,7 +142,7 @@ export const plugin: PluginDefinition = {
type: 'select',
options: [],
async dynamic(_ctx, args) {
const client = await op(args);
const { client } = await op(args);
if (client == null) return { hidden: true };
// Fetches a secret.
const vaults = await client.vaults.list({ decryptDetails: true });
@@ -64,18 +160,23 @@ export const plugin: PluginDefinition = {
type: 'select',
options: [],
async dynamic(_ctx, args) {
const client = await op(args);
const { client } = await op(args);
if (client == null) return { hidden: true };
const vaultId = args.values.vault;
if (typeof vaultId !== 'string') return { hidden: true };
const items = await client.items.list(vaultId);
return {
options: items.map((item) => ({
label: `${item.title} ${item.category}`,
value: item.id,
})),
};
try {
const items = await client.items.list(vaultId);
return {
options: items.map((item) => ({
label: `${item.title} ${item.category}`,
value: item.id,
})),
};
} catch {
// Hide as we can't list the items for this vault
return { hidden: true };
}
},
},
{
@@ -84,7 +185,7 @@ export const plugin: PluginDefinition = {
type: 'select',
options: [],
async dynamic(_ctx, args) {
const client = await op(args);
const { client } = await op(args);
if (client == null) return { hidden: true };
const vaultId = args.values.vault;
const itemId = args.values.item;
@@ -92,34 +193,28 @@ export const plugin: PluginDefinition = {
return { hidden: true };
}
const item = await client.items.get(vaultId, itemId);
return {
options: item.fields.map((field) => ({ label: field.title, value: field.id })),
};
try {
const item = await client.items.get(vaultId, itemId);
return {
options: item.fields.map((field) => ({ label: field.title, value: field.id })),
};
} catch {
// Hide as we can't find the item within this vault
return { hidden: true };
}
},
},
],
async onRender(_ctx, args) {
const client = await op(args);
if (client == null) throw new Error('Invalid token');
const vaultId = args.values.vault;
const itemId = args.values.item;
const fieldId = args.values.field;
if (
typeof vaultId !== 'string' ||
typeof itemId !== 'string' ||
typeof fieldId !== 'string'
) {
return null;
const { value, error } = await getValue(args, vaultId, itemId, fieldId);
if (error) {
throw error;
}
const item = await client.items.get(vaultId, itemId);
const field = item.fields.find((f) => f.id === fieldId);
if (field == null) {
throw new Error(`Field not found: ${fieldId}`);
}
return field.value ?? '';
return value ?? '';
},
},
],

View File

@@ -1,791 +1,116 @@
import type { PluginDefinition } from '@yaakapp/api';
import { andromeda } from './themes/andromeda';
import { atomOneDark } from './themes/atom-one-dark';
import { ayuDark, ayuLight, ayuMirage } from './themes/ayu';
import { blulocoDark, blulocoLight } from './themes/bluloco';
import {
catppuccinFrappe,
catppuccinLatte,
catppuccinMacchiato,
catppuccinMocha,
} from './themes/catppuccin';
import { cobalt2 } from './themes/cobalt2';
import { dracula } from './themes/dracula';
import { everforestDark, everforestLight } from './themes/everforest';
import { fleetDark, fleetDarkPurple, fleetLight } from './themes/fleet';
import { githubDark, githubLight } from './themes/github';
import { githubDarkDimmed } from './themes/github-dimmed';
import { gruvbox } from './themes/gruvbox';
// Yaak themes
import { highContrast, highContrastDark } from './themes/high-contrast';
import { horizon } from './themes/horizon';
import { hotdogStand } from './themes/hotdog-stand';
import { materialDarker } from './themes/material-darker';
import { materialOcean } from './themes/material-ocean';
import { materialPalenight } from './themes/material-palenight';
import {
monokaiPro,
monokaiProClassic,
monokaiProMachine,
monokaiProOctagon,
monokaiProRistretto,
monokaiProSpectrum,
} from './themes/monokai-pro';
import { moonlight } from './themes/moonlight';
import { lightOwl, nightOwl } from './themes/night-owl';
import { noctisAzureus } from './themes/noctis';
import { nord, nordLight, nordLightBrighter } from './themes/nord';
// VSCode themes
import { oneDarkPro } from './themes/one-dark-pro';
import { pandaSyntax } from './themes/panda';
import { relaxing } from './themes/relaxing';
import { rosePine, rosePineDawn, rosePineMoon } from './themes/rose-pine';
import { shadesOfPurple, shadesOfPurpleSuperDark } from './themes/shades-of-purple';
import { slackAubergine } from './themes/slack';
import { solarizedDark, solarizedLight } from './themes/solarized';
import { synthwave84 } from './themes/synthwave-84';
import { tokyoNight, tokyoNightDay, tokyoNightStorm } from './themes/tokyo-night';
import { triangle } from './themes/triangle';
import { vitesseDark, vitesseLight } from './themes/vitesse';
import { winterIsComing } from './themes/winter-is-coming';
export const plugin: PluginDefinition = {
themes: [
{
id: 'high-contrast',
label: 'High Contrast Light',
dark: false,
base: {
surface: 'white',
surfaceHighlight: 'hsl(218,24%,93%)',
text: 'black',
textSubtle: 'hsl(217,24%,40%)',
textSubtlest: 'hsl(217,24%,40%)',
border: 'hsl(217,22%,50%)',
borderSubtle: 'hsl(217,22%,60%)',
primary: 'hsl(267,67%,47%)',
secondary: 'hsl(218,18%,53%)',
info: 'hsl(206,100%,36%)',
success: 'hsl(155,100%,26%)',
notice: 'hsl(45,100%,31%)',
warning: 'hsl(30,99%,34%)',
danger: 'hsl(334,100%,35%)',
},
},
{
id: 'high-contrast-dark',
label: 'High Contrast Dark',
dark: true,
base: {
surface: 'hsl(0,0%,0%)',
surfaceHighlight: 'hsl(0,0%,20%)',
text: 'hsl(0,0%,100%)',
textSubtle: 'hsl(0,0%,90%)',
textSubtlest: 'hsl(0,0%,80%)',
selection: 'hsl(276,100%,30%)',
surfaceActive: 'hsl(276,100%,30%)',
border: 'hsl(0,0%,60%)',
primary: 'hsl(266,100%,85%)',
secondary: 'hsl(242,20%,72%)',
info: 'hsl(208,100%,83%)',
success: 'hsl(150,100%,63%)',
notice: 'hsl(49,100%,77%)',
warning: 'hsl(28,100%,73%)',
danger: 'hsl(343,100%,79%)',
},
},
{
id: 'catppuccin-frappe',
label: 'Catppuccin Frappé',
dark: true,
base: {
surface: 'hsl(231,19%,20%)',
text: 'hsl(227,70%,87%)',
textSubtle: 'hsl(228,29%,73%)',
textSubtlest: 'hsl(227,17%,58%)',
primary: 'hsl(277,59%,76%)',
secondary: 'hsl(228,39%,80%)',
info: 'hsl(222,74%,74%)',
success: 'hsl(96,44%,68%)',
notice: 'hsl(40,62%,73%)',
warning: 'hsl(20,79%,70%)',
danger: 'hsl(359,68%,71%)',
},
components: {
dialog: {
surface: 'hsl(240,21%,12%)',
},
sidebar: {
surface: 'hsl(229,19%,23%)',
border: 'hsl(229,19%,27%)',
},
appHeader: {
surface: 'hsl(229,20%,17%)',
border: 'hsl(229,20%,25%)',
},
responsePane: {
surface: 'hsl(229,19%,23%)',
border: 'hsl(229,19%,27%)',
},
button: {
primary: 'hsl(277,59%,68%)',
secondary: 'hsl(228,39%,72%)',
info: 'hsl(222,74%,67%)',
success: 'hsl(96,44%,61%)',
notice: 'hsl(40,62%,66%)',
warning: 'hsl(20,79%,63%)',
danger: 'hsl(359,68%,64%)',
},
},
},
{
id: 'catppuccin-macchiato',
label: 'Catppuccin Macchiato',
dark: true,
base: {
surface: 'hsl(233,23%,15%)',
text: 'hsl(227,68%,88%)',
textSubtle: 'hsl(227,27%,72%)',
textSubtlest: 'hsl(228,15%,57%)',
primary: 'hsl(267,83%,80%)',
secondary: 'hsl(228,39%,80%)',
info: 'hsl(220,83%,75%)',
success: 'hsl(105,48%,72%)',
notice: 'hsl(40,70%,78%)',
warning: 'hsl(21,86%,73%)',
danger: 'hsl(351,74%,73%)',
},
components: {
dialog: {
surface: 'hsl(240,21%,12%)',
},
sidebar: {
surface: 'hsl(232,23%,18%)',
border: 'hsl(231,23%,22%)',
},
appHeader: {
surface: 'hsl(236,23%,12%)',
border: 'hsl(236,23%,21%)',
},
responsePane: {
surface: 'hsl(232,23%,18%)',
border: 'hsl(231,23%,22%)',
},
button: {
primary: 'hsl(267,82%,72%)',
secondary: 'hsl(228,39%,72%)',
info: 'hsl(220,83%,68%)',
success: 'hsl(105,48%,65%)',
notice: 'hsl(40,70%,70%)',
warning: 'hsl(21,86%,66%)',
danger: 'hsl(351,74%,66%)',
},
},
},
{
id: 'catppuccin-mocha',
label: 'Catppuccin Mocha',
dark: true,
base: {
surface: 'hsl(240,21%,12%)',
text: 'hsl(226,64%,88%)',
textSubtle: 'hsl(228,24%,72%)',
textSubtlest: 'hsl(230,13%,55%)',
primary: 'hsl(267,83%,80%)',
secondary: 'hsl(227,35%,80%)',
info: 'hsl(217,92%,76%)',
success: 'hsl(115,54%,76%)',
notice: 'hsl(41,86%,83%)',
warning: 'hsl(23,92%,75%)',
danger: 'hsl(343,81%,75%)',
},
components: {
dialog: {
surface: 'hsl(240,21%,12%)',
},
sidebar: {
surface: 'hsl(240,21%,15%)',
border: 'hsl(240,21%,19%)',
},
appHeader: {
surface: 'hsl(240,23%,9%)',
border: 'hsl(240,22%,18%)',
},
responsePane: {
surface: 'hsl(240,21%,15%)',
border: 'hsl(240,21%,19%)',
},
button: {
primary: 'hsl(267,67%,65%)',
secondary: 'hsl(227,28%,64%)',
info: 'hsl(217,74%,61%)',
success: 'hsl(115,43%,61%)',
notice: 'hsl(41,69%,66%)',
warning: 'hsl(23,74%,60%)',
danger: 'hsl(343,65%,60%)',
},
},
},
{
id: 'catppuccin-latte',
label: 'Catppuccin Latte',
dark: false,
base: {
surface: 'hsl(220,23%,95%)',
text: 'hsl(234,16%,35%)',
textSubtle: 'hsl(233,10%,47%)',
textSubtlest: 'hsl(231,10%,59%)',
primary: 'hsl(266,85%,58%)',
secondary: 'hsl(233,10%,47%)',
info: 'hsl(231,97%,72%)',
success: 'hsl(183,74%,35%)',
notice: 'hsl(35,77%,49%)',
warning: 'hsl(22,99%,52%)',
danger: 'hsl(355,76%,59%)',
},
components: {
sidebar: {
surface: 'hsl(220,22%,92%)',
border: 'hsl(220,22%,87%)',
},
appHeader: {
surface: 'hsl(220,21%,89%)',
border: 'hsl(220,22%,87%)',
},
},
},
{
id: 'dracula',
label: 'Dracula',
dark: true,
base: {
surface: 'hsl(231,15%,18%)',
surfaceHighlight: 'hsl(230,15%,24%)',
text: 'hsl(60,30%,96%)',
textSubtle: 'hsl(232,14%,65%)',
textSubtlest: 'hsl(232,14%,50%)',
primary: 'hsl(265,89%,78%)',
secondary: 'hsl(225,27%,51%)',
info: 'hsl(191,97%,77%)',
success: 'hsl(135,94%,65%)',
notice: 'hsl(65,92%,76%)',
warning: 'hsl(31,100%,71%)',
danger: 'hsl(0,100%,67%)',
},
components: {
sidebar: {
backdrop: 'hsl(230,15%,24%)',
},
appHeader: {
backdrop: 'hsl(235,14%,15%)',
},
},
},
{
id: 'github-dark',
label: 'GitHub',
dark: true,
base: {
surface: 'hsl(213,30%,7%)',
surfaceHighlight: 'hsl(213,16%,13%)',
text: 'hsl(212,27%,89%)',
textSubtle: 'hsl(212,9%,57%)',
textSubtlest: 'hsl(217,8%,45%)',
border: 'hsl(215,21%,11%)',
primary: 'hsl(262,78%,74%)',
secondary: 'hsl(217,8%,50%)',
info: 'hsl(215,84%,64%)',
success: 'hsl(129,48%,52%)',
notice: 'hsl(39,71%,58%)',
warning: 'hsl(22,83%,60%)',
danger: 'hsl(3,83%,65%)',
},
components: {
button: {
primary: 'hsl(262,79%,71%)',
secondary: 'hsl(217,8%,45%)',
info: 'hsl(215,84%,60%)',
success: 'hsl(129,48%,47%)',
notice: 'hsl(39,71%,53%)',
warning: 'hsl(22,83%,56%)',
danger: 'hsl(3,83%,61%)',
},
},
},
{
id: 'github-light',
label: 'GitHub',
dark: false,
base: {
surface: 'hsl(0,0%,100%)',
surfaceHighlight: 'hsl(210,29%,94%)',
text: 'hsl(213,13%,14%)',
textSubtle: 'hsl(212,9%,43%)',
textSubtlest: 'hsl(203,8%,55%)',
border: 'hsl(210,15%,92%)',
borderSubtle: 'hsl(210,15%,92%)',
primary: 'hsl(261,69%,59%)',
secondary: 'hsl(212,8%,47%)',
info: 'hsl(212,92%,48%)',
success: 'hsl(137,66%,32%)',
notice: 'hsl(40,100%,40%)',
warning: 'hsl(24,100%,44%)',
danger: 'hsl(356,71%,48%)',
},
},
{
id: 'gruvbox',
label: 'Gruvbox',
dark: true,
base: {
surface: 'hsl(0,0%,16%)',
surfaceHighlight: 'hsl(20,3%,19%)',
text: 'hsl(53,74%,91%)',
textSubtle: 'hsl(39,24%,66%)',
textSubtlest: 'hsl(30,12%,51%)',
primary: 'hsl(344,47%,68%)',
secondary: 'hsl(157,16%,58%)',
info: 'hsl(104,35%,62%)',
success: 'hsl(61,66%,44%)',
notice: 'hsl(42,95%,58%)',
warning: 'hsl(27,99%,55%)',
danger: 'hsl(6,96%,59%)',
},
},
{
id: 'hotdog-stand',
label: 'Hotdog Stand',
dark: true,
base: {
surface: 'hsl(0,100%,50%)',
surfaceHighlight: 'hsl(0,0%,0%)',
text: 'hsl(0,0%,100%)',
textSubtle: 'hsl(0,0%,100%)',
textSubtlest: 'hsl(60,100%,50%)',
border: 'hsl(0,0%,0%)',
primary: 'hsl(60,100%,50%)',
secondary: 'hsl(60,100%,50%)',
info: 'hsl(60,100%,50%)',
success: 'hsl(60,100%,50%)',
notice: 'hsl(60,100%,50%)',
warning: 'hsl(60,100%,50%)',
danger: 'hsl(60,100%,50%)',
},
components: {
appHeader: {
surface: 'hsl(0,0%,0%)',
text: 'hsl(0,0%,100%)',
textSubtle: 'hsl(60,100%,50%)',
textSubtlest: 'hsl(0,100%,50%)',
},
menu: {
surface: 'hsl(0,0%,0%)',
border: 'hsl(0,100%,50%)',
surfaceHighlight: 'hsl(0,100%,50%)',
text: 'hsl(0,0%,100%)',
textSubtle: 'hsl(60,100%,50%)',
textSubtlest: 'hsl(60,100%,50%)',
},
button: {
surface: 'hsl(0,0%,0%)',
text: 'hsl(0,0%,100%)',
primary: 'hsl(0,0%,0%)',
secondary: 'hsl(0,0%,100%)',
info: 'hsl(0,0%,0%)',
success: 'hsl(60,100%,50%)',
notice: 'hsl(60,100%,50%)',
warning: 'hsl(0,0%,0%)',
danger: 'hsl(0,100%,50%)',
},
editor: {
primary: 'hsl(0,0%,100%)',
secondary: 'hsl(0,0%,100%)',
info: 'hsl(0,0%,100%)',
success: 'hsl(0,0%,100%)',
notice: 'hsl(60,100%,50%)',
warning: 'hsl(0,0%,100%)',
danger: 'hsl(0,0%,100%)',
},
},
},
{
id: 'monokai-pro',
label: 'Monokai Pro',
dark: true,
base: {
surface: 'hsl(285,5%,17%)',
text: 'hsl(60,25%,98%)',
textSubtle: 'hsl(0,1%,75%)',
textSubtlest: 'hsl(300,0%,57%)',
primary: 'hsl(250,77%,78%)',
secondary: 'hsl(0,1%,75%)',
info: 'hsl(186,71%,69%)',
success: 'hsl(90,59%,66%)',
notice: 'hsl(45,100%,70%)',
warning: 'hsl(20,96%,70%)',
danger: 'hsl(345,100%,69%)',
},
components: {
appHeader: {
surface: 'hsl(300,5%,13%)',
text: 'hsl(0,1%,75%)',
textSubtle: 'hsl(300,0%,57%)',
textSubtlest: 'hsl(300,1%,44%)',
},
button: {
primary: 'hsl(250,77%,70%)',
secondary: 'hsl(0,1%,68%)',
info: 'hsl(186,71%,62%)',
success: 'hsl(90,59%,59%)',
notice: 'hsl(45,100%,63%)',
warning: 'hsl(20,96%,63%)',
danger: 'hsl(345,100%,62%)',
},
},
},
{
id: 'monokai-pro-classic',
label: 'Monokai Pro Classic',
dark: true,
base: {
surface: 'hsl(70,8%,15%)',
text: 'hsl(69,100%,97%)',
textSubtle: 'hsl(65,9%,73%)',
textSubtlest: 'hsl(66,4%,55%)',
primary: 'hsl(261,100%,75%)',
secondary: 'hsl(202,8%,72%)',
info: 'hsl(190,81%,67%)',
success: 'hsl(80,76%,53%)',
notice: 'hsl(54,70%,68%)',
warning: 'hsl(32,98%,56%)',
danger: 'hsl(338,95%,56%)',
},
components: {
appHeader: {
surface: 'hsl(72,9%,11%)',
text: 'hsl(202,8%,72%)',
textSubtle: 'hsl(213,4%,48%)',
textSubtlest: 'hsl(223,6%,44%)',
},
button: {
primary: 'hsl(261,100%,68%)',
secondary: 'hsl(202,8%,65%)',
info: 'hsl(190,81%,60%)',
success: 'hsl(80,76%,48%)',
notice: 'hsl(54,71%,61%)',
warning: 'hsl(32,98%,50%)',
danger: 'hsl(338,95%,50%)',
},
},
},
{
id: 'monokai-pro-machine',
label: 'Monokai Pro Machine',
dark: true,
base: {
surface: 'hsl(200,16%,18%)',
text: 'hsl(173,24%,93%)',
textSubtle: 'hsl(185,6%,57%)',
textSubtlest: 'hsl(189,6%,45%)',
primary: 'hsl(258,86%,80%)',
secondary: 'hsl(175,9%,75%)',
info: 'hsl(194,81%,72%)',
success: 'hsl(98,67%,69%)',
notice: 'hsl(52,100%,72%)',
warning: 'hsl(28,100%,72%)',
danger: 'hsl(353,100%,71%)',
},
components: {
appHeader: {
surface: 'hsl(196,16%,14%)',
text: 'hsl(202,8%,72%)',
textSubtle: 'hsl(213,4%,48%)',
textSubtlest: 'hsl(223,6%,44%)',
},
button: {
primary: 'hsl(258,86%,72%)',
secondary: 'hsl(175,9%,68%)',
info: 'hsl(194,80%,65%)',
success: 'hsl(98,67%,62%)',
notice: 'hsl(52,100%,65%)',
warning: 'hsl(28,100%,65%)',
danger: 'hsl(353,100%,64%)',
},
},
},
{
id: 'monokai-pro-octagon',
label: 'Monokai Pro Octagon',
dark: true,
base: {
surface: 'hsl(233,18%,19%)',
text: 'hsl(173,24%,93%)',
textSubtle: 'hsl(202,8%,72%)',
textSubtlest: 'hsl(213,4%,48%)',
primary: 'hsl(292,30%,70%)',
secondary: 'hsl(202,8%,72%)',
info: 'hsl(155,37%,72%)',
success: 'hsl(75,60%,61%)',
notice: 'hsl(44,100%,71%)',
warning: 'hsl(23,100%,68%)',
danger: 'hsl(352,100%,70%)',
},
components: {
appHeader: {
surface: 'hsl(235,18%,14%)',
text: 'hsl(202,8%,72%)',
textSubtle: 'hsl(213,4%,48%)',
textSubtlest: 'hsl(223,6%,44%)',
},
button: {
primary: 'hsl(292,26%,63%)',
secondary: 'hsl(201,7%,65%)',
info: 'hsl(155,33%,65%)',
success: 'hsl(75,54%,55%)',
notice: 'hsl(44,90%,64%)',
warning: 'hsl(23,90%,61%)',
danger: 'hsl(352,90%,63%)',
},
},
},
{
id: 'monokai-pro-ristretto',
label: 'Monokai Pro Ristretto',
dark: true,
base: {
surface: 'hsl(0,9%,16%)',
text: 'hsl(351,100%,97%)',
textSubtle: 'hsl(355,9%,74%)',
textSubtlest: 'hsl(354,4%,56%)',
primary: 'hsl(239,63%,79%)',
secondary: 'hsl(355,9%,74%)',
info: 'hsl(170,53%,69%)',
success: 'hsl(88,57%,66%)',
notice: 'hsl(41,92%,70%)',
warning: 'hsl(13,85%,70%)',
danger: 'hsl(349,97%,70%)',
},
components: {
appHeader: {
surface: 'hsl(0,8%,12%)',
text: 'hsl(355,9%,74%)',
textSubtle: 'hsl(354,4%,56%)',
textSubtlest: 'hsl(353,4%,43%)',
},
button: {
primary: 'hsl(239,63%,71%)',
secondary: 'hsl(355,9%,67%)',
info: 'hsl(170,53%,62%)',
success: 'hsl(88,57%,59%)',
notice: 'hsl(41,92%,63%)',
warning: 'hsl(13,86%,63%)',
danger: 'hsl(349,97%,63%)',
},
},
},
{
id: 'monokai-pro-spectrum',
label: 'Monokai Pro Spectrum',
dark: true,
base: {
surface: 'hsl(0,0%,13%)',
text: 'hsl(266,100%,97%)',
textSubtle: 'hsl(264,7%,73%)',
textSubtlest: 'hsl(266,3%,55%)',
primary: 'hsl(247,61%,72%)',
secondary: 'hsl(264,7%,73%)',
info: 'hsl(188,74%,63%)',
success: 'hsl(133,54%,66%)',
notice: 'hsl(51,96%,69%)',
warning: 'hsl(23,98%,66%)',
danger: 'hsl(343,96%,68%)',
},
components: {
appHeader: {
surface: 'hsl(0,0%,10%)',
text: 'hsl(264,7%,73%)',
textSubtle: 'hsl(266,3%,55%)',
textSubtlest: 'hsl(264,2%,41%)',
},
button: {
primary: 'hsl(247,61%,65%)',
secondary: 'hsl(264,7%,66%)',
info: 'hsl(188,74%,57%)',
success: 'hsl(133,54%,59%)',
notice: 'hsl(51,96%,62%)',
warning: 'hsl(23,98%,59%)',
danger: 'hsl(343,96%,61%)',
},
},
},
{
id: 'moonlight',
label: 'Moonlight',
dark: true,
base: {
surface: 'hsl(234,23%,17%)',
text: 'hsl(225,71%,90%)',
textSubtle: 'hsl(230,28%,62%)',
textSubtlest: 'hsl(232,26%,43%)',
primary: 'hsl(262,100%,82%)',
secondary: 'hsl(232,18%,65%)',
info: 'hsl(217,100%,74%)',
success: 'hsl(174,66%,54%)',
notice: 'hsl(35,100%,73%)',
warning: 'hsl(17,100%,71%)',
danger: 'hsl(356,100%,73%)',
},
components: {
appHeader: {
surface: 'hsl(233,23%,15%)',
},
sidebar: {
surface: 'hsl(233,23%,15%)',
},
},
},
{
id: 'nord',
label: 'Nord',
dark: true,
base: {
surface: 'hsl(220,16%,22%)',
surfaceHighlight: 'hsl(220,14%,28%)',
text: 'hsl(220,28%,93%)',
textSubtle: 'hsl(220,26%,90%)',
textSubtlest: 'hsl(220,24%,86%)',
primary: 'hsl(193,38%,68%)',
secondary: 'hsl(210,34%,63%)',
info: 'hsl(174,25%,69%)',
success: 'hsl(89,26%,66%)',
notice: 'hsl(40,66%,73%)',
warning: 'hsl(17,48%,64%)',
danger: 'hsl(353,43%,56%)',
},
components: {
sidebar: {
backdrop: 'hsl(220,16%,22%)',
},
appHeader: {
backdrop: 'hsl(220,14%,28%)',
},
},
},
{
id: 'relaxing',
label: 'Relaxing',
dark: true,
base: {
surface: 'hsl(267,33%,17%)',
text: 'hsl(275,49%,92%)',
primary: 'hsl(267,84%,81%)',
secondary: 'hsl(227,35%,80%)',
info: 'hsl(217,92%,76%)',
success: 'hsl(115,54%,76%)',
notice: 'hsl(41,86%,83%)',
warning: 'hsl(23,92%,75%)',
danger: 'hsl(343,81%,75%)',
},
},
{
id: 'rose-pine',
label: 'Rosé Pine',
dark: true,
base: {
surface: 'hsl(249,22%,12%)',
text: 'hsl(245,50%,91%)',
textSubtle: 'hsl(248,15%,61%)',
textSubtlest: 'hsl(249,12%,47%)',
primary: 'hsl(267,57%,78%)',
secondary: 'hsl(249,12%,47%)',
info: 'hsl(199,49%,60%)',
success: 'hsl(180,43%,73%)',
notice: 'hsl(35,88%,72%)',
warning: 'hsl(1,74%,79%)',
danger: 'hsl(343,76%,68%)',
},
components: {
responsePane: {
surface: 'hsl(247,23%,15%)',
},
sidebar: {
surface: 'hsl(247,23%,15%)',
},
menu: {
surface: 'hsl(248,21%,26%)',
textSubtle: 'hsl(248,15%,66%)',
textSubtlest: 'hsl(249,12%,52%)',
border: 'hsl(248,21%,35%)',
borderSubtle: 'hsl(248,21%,33%)',
},
},
},
{
id: 'rose-pine-moon',
label: 'Rosé Pine Moon',
dark: true,
base: {
surface: 'hsl(246,24%,17%)',
text: 'hsl(245,50%,91%)',
textSubtle: 'hsl(248,15%,61%)',
textSubtlest: 'hsl(249,12%,47%)',
primary: 'hsl(267,57%,78%)',
secondary: 'hsl(248,15%,61%)',
info: 'hsl(197,48%,60%)',
success: 'hsl(197,48%,60%)',
notice: 'hsl(35,88%,72%)',
warning: 'hsl(2,66%,75%)',
danger: 'hsl(343,76%,68%)',
},
components: {
responsePane: {
surface: 'hsl(247,24%,20%)',
},
sidebar: {
surface: 'hsl(247,24%,20%)',
},
menu: {
surface: 'hsl(248,21%,26%)',
textSubtle: 'hsl(248,15%,61%)',
textSubtlest: 'hsl(249,12%,55%)',
border: 'hsl(248,21%,35%)',
borderSubtle: 'hsl(248,21%,31%)',
},
},
},
{
id: 'rose-pine-dawn',
label: 'Rosé Pine Dawn',
dark: false,
base: {
surface: 'hsl(32,57%,95%)',
border: 'hsl(10,9%,86%)',
surfaceHighlight: 'hsl(25,35%,93%)',
text: 'hsl(248,19%,40%)',
textSubtle: 'hsl(248,12%,52%)',
textSubtlest: 'hsl(257,9%,61%)',
primary: 'hsl(271,27%,56%)',
secondary: 'hsl(249,12%,47%)',
info: 'hsl(197,52%,36%)',
success: 'hsl(188,31%,45%)',
notice: 'hsl(34,64%,49%)',
warning: 'hsl(2,47%,64%)',
danger: 'hsl(343,35%,55%)',
},
components: {
responsePane: {
border: 'hsl(20,12%,90%)',
},
sidebar: {
border: 'hsl(20,12%,90%)',
},
appHeader: {
border: 'hsl(20,12%,90%)',
},
input: {
border: 'hsl(10,9%,86%)',
},
dialog: {
border: 'hsl(20,12%,90%)',
},
menu: {
surface: 'hsl(28,40%,92%)',
border: 'hsl(10,9%,86%)',
},
},
},
{
id: 'triangle',
dark: true,
label: 'Triangle',
base: {
surface: 'rgb(0,0,0)',
surfaceHighlight: 'rgb(21,21,21)',
surfaceActive: 'rgb(31,31,31)',
text: 'rgb(237,237,237)',
textSubtle: 'rgb(161,161,161)',
textSubtlest: 'rgb(115,115,115)',
border: 'rgb(31,31,31)',
primary: 'rgb(196,114,251)',
secondary: 'rgb(161,161,161)',
info: 'rgb(71,168,255)',
success: 'rgb(0,202,81)',
notice: 'rgb(255,175,0)',
warning: '#FF4C8D',
danger: '#fd495a',
},
components: {
editor: {
danger: '#FF4C8D',
warning: '#fd495a',
},
dialog: {
surface: 'rgb(10,10,10)',
border: 'rgb(31,31,31)',
},
sidebar: {
border: 'rgb(31,31,31)',
},
responsePane: {
surface: 'rgb(10,10,10)',
border: 'rgb(31,31,31)',
},
appHeader: {
surface: 'rgb(10,10,10)',
border: 'rgb(31,31,31)',
},
},
},
andromeda,
atomOneDark,
ayuDark,
ayuLight,
ayuMirage,
blulocoDark,
blulocoLight,
catppuccinFrappe,
catppuccinLatte,
catppuccinMacchiato,
catppuccinMocha,
cobalt2,
dracula,
everforestDark,
everforestLight,
fleetDark,
fleetDarkPurple,
fleetLight,
githubDark,
githubDarkDimmed,
githubLight,
gruvbox,
highContrast,
highContrastDark,
horizon,
hotdogStand,
lightOwl,
materialDarker,
materialOcean,
materialPalenight,
monokaiPro,
monokaiProClassic,
monokaiProMachine,
monokaiProOctagon,
monokaiProRistretto,
monokaiProSpectrum,
moonlight,
nightOwl,
noctisAzureus,
nord,
nordLight,
nordLightBrighter,
oneDarkPro,
pandaSyntax,
relaxing,
rosePine,
rosePineDawn,
rosePineMoon,
shadesOfPurple,
shadesOfPurpleSuperDark,
slackAubergine,
solarizedDark,
solarizedLight,
synthwave84,
tokyoNight,
tokyoNightDay,
tokyoNightStorm,
triangle,
vitesseDark,
vitesseLight,
winterIsComing,
],
};

View File

@@ -0,0 +1,47 @@
import type { Theme } from '@yaakapp/api';
export const andromeda: Theme = {
id: 'andromeda',
label: 'Andromeda',
dark: true,
base: {
surface: 'hsl(251, 25%, 15%)',
surfaceHighlight: 'hsl(251, 22%, 20%)',
text: 'hsl(220, 10%, 85%)',
textSubtle: 'hsl(220, 8%, 60%)',
textSubtlest: 'hsl(220, 6%, 45%)',
primary: 'hsl(293, 75%, 68%)',
secondary: 'hsl(220, 8%, 60%)',
info: 'hsl(180, 60%, 60%)',
success: 'hsl(85, 60%, 55%)',
notice: 'hsl(38, 100%, 65%)',
warning: 'hsl(25, 95%, 60%)',
danger: 'hsl(358, 80%, 60%)',
},
components: {
dialog: {
surface: 'hsl(251, 25%, 12%)',
},
sidebar: {
surface: 'hsl(251, 23%, 13%)',
border: 'hsl(251, 20%, 18%)',
},
appHeader: {
surface: 'hsl(251, 25%, 11%)',
border: 'hsl(251, 20%, 16%)',
},
responsePane: {
surface: 'hsl(251, 23%, 13%)',
border: 'hsl(251, 20%, 18%)',
},
button: {
primary: 'hsl(293, 75%, 61%)',
secondary: 'hsl(220, 8%, 53%)',
info: 'hsl(180, 60%, 53%)',
success: 'hsl(85, 60%, 48%)',
notice: 'hsl(38, 100%, 58%)',
warning: 'hsl(25, 95%, 53%)',
danger: 'hsl(358, 80%, 53%)',
},
},
};

View File

@@ -0,0 +1,47 @@
import type { Theme } from '@yaakapp/api';
export const atomOneDark: Theme = {
id: 'atom-one-dark',
label: 'Atom One Dark',
dark: true,
base: {
surface: 'hsl(220, 13%, 18%)',
surfaceHighlight: 'hsl(219, 13%, 22%)',
text: 'hsl(219, 14%, 71%)',
textSubtle: 'hsl(220, 9%, 55%)',
textSubtlest: 'hsl(220, 8%, 45%)',
primary: 'hsl(286, 60%, 67%)',
secondary: 'hsl(220, 9%, 55%)',
info: 'hsl(207, 82%, 66%)',
success: 'hsl(95, 38%, 62%)',
notice: 'hsl(39, 67%, 69%)',
warning: 'hsl(29, 54%, 61%)',
danger: 'hsl(355, 65%, 65%)',
},
components: {
dialog: {
surface: 'hsl(220, 13%, 14%)',
},
sidebar: {
surface: 'hsl(220, 13%, 16%)',
border: 'hsl(220, 13%, 20%)',
},
appHeader: {
surface: 'hsl(220, 13%, 12%)',
border: 'hsl(220, 13%, 18%)',
},
responsePane: {
surface: 'hsl(220, 13%, 16%)',
border: 'hsl(220, 13%, 20%)',
},
button: {
primary: 'hsl(286, 60%, 60%)',
secondary: 'hsl(220, 9%, 48%)',
info: 'hsl(207, 82%, 59%)',
success: 'hsl(95, 38%, 55%)',
notice: 'hsl(39, 67%, 62%)',
warning: 'hsl(29, 54%, 54%)',
danger: 'hsl(355, 65%, 58%)',
},
},
};

View File

@@ -0,0 +1,122 @@
import type { Theme } from '@yaakapp/api';
export const ayuDark: Theme = {
id: 'ayu-dark',
label: 'Ayu Dark',
dark: true,
base: {
surface: 'hsl(220, 25%, 10%)',
surfaceHighlight: 'hsl(220, 20%, 15%)',
text: 'hsl(210, 22%, 78%)',
textSubtle: 'hsl(40, 13%, 50%)',
textSubtlest: 'hsl(220, 10%, 40%)',
primary: 'hsl(38, 100%, 56%)',
secondary: 'hsl(210, 15%, 55%)',
info: 'hsl(200, 80%, 60%)',
success: 'hsl(100, 75%, 60%)',
notice: 'hsl(38, 100%, 56%)',
warning: 'hsl(25, 100%, 60%)',
danger: 'hsl(345, 80%, 60%)',
},
components: {
dialog: {
surface: 'hsl(220, 25%, 8%)',
},
sidebar: {
surface: 'hsl(220, 22%, 12%)',
border: 'hsl(220, 20%, 16%)',
},
appHeader: {
surface: 'hsl(220, 25%, 7%)',
border: 'hsl(220, 20%, 13%)',
},
responsePane: {
surface: 'hsl(220, 22%, 12%)',
border: 'hsl(220, 20%, 16%)',
},
button: {
primary: 'hsl(38, 100%, 50%)',
secondary: 'hsl(210, 15%, 48%)',
info: 'hsl(200, 80%, 53%)',
success: 'hsl(100, 75%, 53%)',
notice: 'hsl(38, 100%, 50%)',
warning: 'hsl(25, 100%, 53%)',
danger: 'hsl(345, 80%, 53%)',
},
},
};
export const ayuMirage: Theme = {
id: 'ayu-mirage',
label: 'Ayu Mirage',
dark: true,
base: {
surface: 'hsl(226, 23%, 17%)',
surfaceHighlight: 'hsl(226, 20%, 22%)',
text: 'hsl(212, 15%, 81%)',
textSubtle: 'hsl(212, 12%, 55%)',
textSubtlest: 'hsl(212, 10%, 45%)',
primary: 'hsl(38, 100%, 67%)',
secondary: 'hsl(212, 12%, 55%)',
info: 'hsl(200, 80%, 70%)',
success: 'hsl(100, 50%, 68%)',
notice: 'hsl(38, 100%, 67%)',
warning: 'hsl(25, 100%, 70%)',
danger: 'hsl(345, 80%, 70%)',
},
components: {
dialog: {
surface: 'hsl(226, 23%, 14%)',
},
sidebar: {
surface: 'hsl(226, 22%, 15%)',
border: 'hsl(226, 20%, 20%)',
},
appHeader: {
surface: 'hsl(226, 23%, 12%)',
border: 'hsl(226, 20%, 17%)',
},
responsePane: {
surface: 'hsl(226, 22%, 15%)',
border: 'hsl(226, 20%, 20%)',
},
button: {
primary: 'hsl(38, 100%, 60%)',
info: 'hsl(200, 80%, 63%)',
success: 'hsl(100, 50%, 61%)',
notice: 'hsl(38, 100%, 60%)',
warning: 'hsl(25, 100%, 63%)',
danger: 'hsl(345, 80%, 63%)',
},
},
};
export const ayuLight: Theme = {
id: 'ayu-light',
label: 'Ayu Light',
dark: false,
base: {
surface: 'hsl(40, 22%, 97%)',
surfaceHighlight: 'hsl(40, 20%, 93%)',
text: 'hsl(214, 10%, 35%)',
textSubtle: 'hsl(214, 8%, 50%)',
textSubtlest: 'hsl(214, 6%, 60%)',
primary: 'hsl(35, 100%, 45%)',
secondary: 'hsl(214, 8%, 50%)',
info: 'hsl(200, 75%, 45%)',
success: 'hsl(100, 60%, 40%)',
notice: 'hsl(35, 100%, 45%)',
warning: 'hsl(22, 100%, 50%)',
danger: 'hsl(345, 70%, 55%)',
},
components: {
sidebar: {
surface: 'hsl(40, 20%, 95%)',
border: 'hsl(40, 15%, 90%)',
},
appHeader: {
surface: 'hsl(40, 20%, 93%)',
border: 'hsl(40, 15%, 88%)',
},
},
};

View File

@@ -0,0 +1,77 @@
import type { Theme } from '@yaakapp/api';
export const blulocoDark: Theme = {
id: 'bluloco-dark',
label: 'Bluloco Dark',
dark: true,
base: {
surface: 'hsl(230, 20%, 14%)',
surfaceHighlight: 'hsl(230, 17%, 19%)',
text: 'hsl(220, 15%, 80%)',
textSubtle: 'hsl(220, 10%, 55%)',
textSubtlest: 'hsl(220, 8%, 42%)',
primary: 'hsl(218, 85%, 65%)',
secondary: 'hsl(220, 10%, 55%)',
info: 'hsl(218, 85%, 65%)',
success: 'hsl(95, 55%, 55%)',
notice: 'hsl(37, 90%, 60%)',
warning: 'hsl(22, 85%, 55%)',
danger: 'hsl(355, 75%, 60%)',
},
components: {
dialog: {
surface: 'hsl(230, 20%, 11%)',
},
sidebar: {
surface: 'hsl(230, 18%, 12%)',
border: 'hsl(230, 16%, 17%)',
},
appHeader: {
surface: 'hsl(230, 20%, 10%)',
border: 'hsl(230, 16%, 15%)',
},
responsePane: {
surface: 'hsl(230, 18%, 12%)',
border: 'hsl(230, 16%, 17%)',
},
button: {
primary: 'hsl(218, 85%, 58%)',
secondary: 'hsl(220, 10%, 48%)',
info: 'hsl(218, 85%, 58%)',
success: 'hsl(95, 55%, 48%)',
notice: 'hsl(37, 90%, 53%)',
warning: 'hsl(22, 85%, 48%)',
danger: 'hsl(355, 75%, 53%)',
},
},
};
export const blulocoLight: Theme = {
id: 'bluloco-light',
label: 'Bluloco Light',
dark: false,
base: {
surface: 'hsl(0, 0%, 98%)',
surfaceHighlight: 'hsl(220, 15%, 94%)',
text: 'hsl(228, 18%, 30%)',
textSubtle: 'hsl(228, 10%, 48%)',
textSubtlest: 'hsl(228, 8%, 58%)',
primary: 'hsl(218, 80%, 48%)',
secondary: 'hsl(228, 10%, 48%)',
info: 'hsl(218, 80%, 48%)',
success: 'hsl(138, 55%, 40%)',
notice: 'hsl(35, 85%, 45%)',
warning: 'hsl(22, 80%, 48%)',
danger: 'hsl(355, 70%, 48%)',
},
components: {
sidebar: {
surface: 'hsl(220, 15%, 96%)',
border: 'hsl(220, 12%, 90%)',
},
appHeader: {
surface: 'hsl(220, 15%, 94%)',
border: 'hsl(220, 12%, 88%)',
},
},
};

View File

@@ -0,0 +1,165 @@
import type { Theme } from '@yaakapp/api';
export const catppuccinFrappe: Theme = {
id: 'catppuccin-frappe',
label: 'Catppuccin Frappé',
dark: true,
base: {
surface: 'hsl(231,19%,20%)',
text: 'hsl(227,70%,87%)',
textSubtle: 'hsl(228,29%,73%)',
textSubtlest: 'hsl(227,17%,58%)',
primary: 'hsl(277,59%,76%)',
secondary: 'hsl(228,39%,80%)',
info: 'hsl(222,74%,74%)',
success: 'hsl(96,44%,68%)',
notice: 'hsl(40,62%,73%)',
warning: 'hsl(20,79%,70%)',
danger: 'hsl(359,68%,71%)',
},
components: {
dialog: {
surface: 'hsl(240,21%,12%)',
},
sidebar: {
surface: 'hsl(229,19%,23%)',
border: 'hsl(229,19%,27%)',
},
appHeader: {
surface: 'hsl(229,20%,17%)',
border: 'hsl(229,20%,25%)',
},
responsePane: {
surface: 'hsl(229,19%,23%)',
border: 'hsl(229,19%,27%)',
},
button: {
primary: 'hsl(277,59%,68%)',
secondary: 'hsl(228,39%,72%)',
info: 'hsl(222,74%,67%)',
success: 'hsl(96,44%,61%)',
notice: 'hsl(40,62%,66%)',
warning: 'hsl(20,79%,63%)',
danger: 'hsl(359,68%,64%)',
},
},
};
export const catppuccinMacchiato: Theme = {
id: 'catppuccin-macchiato',
label: 'Catppuccin Macchiato',
dark: true,
base: {
surface: 'hsl(233,23%,15%)',
text: 'hsl(227,68%,88%)',
textSubtle: 'hsl(227,27%,72%)',
textSubtlest: 'hsl(228,15%,57%)',
primary: 'hsl(267,83%,80%)',
secondary: 'hsl(228,39%,80%)',
info: 'hsl(220,83%,75%)',
success: 'hsl(105,48%,72%)',
notice: 'hsl(40,70%,78%)',
warning: 'hsl(21,86%,73%)',
danger: 'hsl(351,74%,73%)',
},
components: {
dialog: {
surface: 'hsl(240,21%,12%)',
},
sidebar: {
surface: 'hsl(232,23%,18%)',
border: 'hsl(231,23%,22%)',
},
appHeader: {
surface: 'hsl(236,23%,12%)',
border: 'hsl(236,23%,21%)',
},
responsePane: {
surface: 'hsl(232,23%,18%)',
border: 'hsl(231,23%,22%)',
},
button: {
primary: 'hsl(267,82%,72%)',
secondary: 'hsl(228,39%,72%)',
info: 'hsl(220,83%,68%)',
success: 'hsl(105,48%,65%)',
notice: 'hsl(40,70%,70%)',
warning: 'hsl(21,86%,66%)',
danger: 'hsl(351,74%,66%)',
},
},
};
export const catppuccinMocha: Theme = {
id: 'catppuccin-mocha',
label: 'Catppuccin Mocha',
dark: true,
base: {
surface: 'hsl(240,21%,12%)',
text: 'hsl(226,64%,88%)',
textSubtle: 'hsl(228,24%,72%)',
textSubtlest: 'hsl(230,13%,55%)',
primary: 'hsl(267,83%,80%)',
secondary: 'hsl(227,35%,80%)',
info: 'hsl(217,92%,76%)',
success: 'hsl(115,54%,76%)',
notice: 'hsl(41,86%,83%)',
warning: 'hsl(23,92%,75%)',
danger: 'hsl(343,81%,75%)',
},
components: {
dialog: {
surface: 'hsl(240,21%,12%)',
},
sidebar: {
surface: 'hsl(240,21%,15%)',
border: 'hsl(240,21%,19%)',
},
appHeader: {
surface: 'hsl(240,23%,9%)',
border: 'hsl(240,22%,18%)',
},
responsePane: {
surface: 'hsl(240,21%,15%)',
border: 'hsl(240,21%,19%)',
},
button: {
primary: 'hsl(267,67%,65%)',
secondary: 'hsl(227,28%,64%)',
info: 'hsl(217,74%,61%)',
success: 'hsl(115,43%,61%)',
notice: 'hsl(41,69%,66%)',
warning: 'hsl(23,74%,60%)',
danger: 'hsl(343,65%,60%)',
},
},
};
export const catppuccinLatte: Theme = {
id: 'catppuccin-latte',
label: 'Catppuccin Latte',
dark: false,
base: {
surface: 'hsl(220,23%,95%)',
text: 'hsl(234,16%,35%)',
textSubtle: 'hsl(233,10%,47%)',
textSubtlest: 'hsl(231,10%,59%)',
primary: 'hsl(266,85%,58%)',
secondary: 'hsl(233,10%,47%)',
info: 'hsl(231,97%,72%)',
success: 'hsl(183,74%,35%)',
notice: 'hsl(35,77%,49%)',
warning: 'hsl(22,99%,52%)',
danger: 'hsl(355,76%,59%)',
},
components: {
sidebar: {
surface: 'hsl(220,22%,92%)',
border: 'hsl(220,22%,87%)',
},
appHeader: {
surface: 'hsl(220,21%,89%)',
border: 'hsl(220,22%,87%)',
},
},
};

View File

@@ -0,0 +1,47 @@
import type { Theme } from '@yaakapp/api';
export const cobalt2: Theme = {
id: 'cobalt2',
label: 'Cobalt2',
dark: true,
base: {
surface: '#193549',
surfaceHighlight: '#1f4662',
text: '#d2e1f1',
textSubtle: '#709ac8',
textSubtlest: '#55749e',
primary: '#ffc600',
secondary: '#819fc3',
info: '#0088FF',
success: '#3AD900',
notice: '#FFEE80',
warning: '#FF9D00',
danger: '#FF628C',
},
components: {
sidebar: {
surface: '#13283a',
border: '#102332',
},
input: {
border: '#1f4561',
},
appHeader: {
surface: '#13283a',
border: '#112636',
},
responsePane: {
surface: '#13283a',
border: '#112636',
},
button: {
primary: '#ffc600',
secondary: '#709ac8',
info: '#0088FF',
success: '#3AD900',
notice: '#ecdc6a',
warning: '#FF9D00',
danger: '#FF628C',
},
},
};

View File

@@ -0,0 +1,29 @@
import type { Theme } from '@yaakapp/api';
export const dracula: Theme = {
id: 'dracula',
label: 'Dracula',
dark: true,
base: {
surface: 'hsl(231,15%,18%)',
surfaceHighlight: 'hsl(230,15%,24%)',
text: 'hsl(60,30%,96%)',
textSubtle: 'hsl(232,14%,65%)',
textSubtlest: 'hsl(232,14%,50%)',
primary: 'hsl(265,89%,78%)',
secondary: 'hsl(225,27%,51%)',
info: 'hsl(191,97%,77%)',
success: 'hsl(135,94%,65%)',
notice: 'hsl(65,92%,76%)',
warning: 'hsl(31,100%,71%)',
danger: 'hsl(0,100%,67%)',
},
components: {
sidebar: {
backdrop: 'hsl(230,15%,24%)',
},
appHeader: {
backdrop: 'hsl(235,14%,15%)',
},
},
};

View File

@@ -0,0 +1,77 @@
import type { Theme } from '@yaakapp/api';
export const everforestDark: Theme = {
id: 'everforest-dark',
label: 'Everforest Dark',
dark: true,
base: {
surface: 'hsl(150, 8%, 18%)',
surfaceHighlight: 'hsl(150, 7%, 22%)',
text: 'hsl(45, 30%, 78%)',
textSubtle: 'hsl(145, 8%, 55%)',
textSubtlest: 'hsl(145, 6%, 42%)',
primary: 'hsl(142, 35%, 60%)',
secondary: 'hsl(145, 8%, 55%)',
info: 'hsl(200, 35%, 65%)',
success: 'hsl(142, 35%, 60%)',
notice: 'hsl(46, 55%, 68%)',
warning: 'hsl(24, 55%, 65%)',
danger: 'hsl(358, 50%, 68%)',
},
components: {
dialog: {
surface: 'hsl(150, 8%, 15%)',
},
sidebar: {
surface: 'hsl(150, 7%, 16%)',
border: 'hsl(150, 6%, 20%)',
},
appHeader: {
surface: 'hsl(150, 8%, 14%)',
border: 'hsl(150, 6%, 18%)',
},
responsePane: {
surface: 'hsl(150, 7%, 16%)',
border: 'hsl(150, 6%, 20%)',
},
button: {
primary: 'hsl(142, 35%, 53%)',
secondary: 'hsl(145, 8%, 48%)',
info: 'hsl(200, 35%, 58%)',
success: 'hsl(142, 35%, 53%)',
notice: 'hsl(46, 55%, 61%)',
warning: 'hsl(24, 55%, 58%)',
danger: 'hsl(358, 50%, 61%)',
},
},
};
export const everforestLight: Theme = {
id: 'everforest-light',
label: 'Everforest Light',
dark: false,
base: {
surface: 'hsl(40, 32%, 93%)',
surfaceHighlight: 'hsl(40, 28%, 89%)',
text: 'hsl(135, 8%, 35%)',
textSubtle: 'hsl(135, 6%, 45%)',
textSubtlest: 'hsl(135, 4%, 55%)',
primary: 'hsl(128, 30%, 45%)',
secondary: 'hsl(135, 6%, 45%)',
info: 'hsl(200, 35%, 45%)',
success: 'hsl(128, 30%, 45%)',
notice: 'hsl(45, 70%, 40%)',
warning: 'hsl(22, 60%, 48%)',
danger: 'hsl(355, 55%, 50%)',
},
components: {
sidebar: {
surface: 'hsl(40, 30%, 91%)',
border: 'hsl(40, 25%, 86%)',
},
appHeader: {
surface: 'hsl(40, 30%, 89%)',
border: 'hsl(40, 25%, 84%)',
},
},
};

View File

@@ -0,0 +1,173 @@
import type { Theme } from '@yaakapp/api';
export const fleetLight: Theme = {
id: 'fleet-light',
label: 'Fleet Light',
dark: false,
base: {
surface: '#FFFFFF',
surfaceHighlight: '#F8F8F9',
surfaceActive: '#EEEFF0',
border: '#18191B33',
text: '#090909',
textSubtle: '#6E747B',
textSubtlest: '#898E94',
primary: '#1D61BA',
secondary: '#6E747B',
info: '#4B8DEC',
success: '#169068',
notice: '#B07203',
warning: '#B07203',
danger: '#E1465E',
},
components: {
sidebar: {
surface: '#EEEFF0',
border: '#18191B33',
},
appHeader: {
surface: '#EEEFF0',
border: '#18191B33',
},
responsePane: {
surface: '#FFFFFF',
border: '#18191B33',
},
dialog: {
surface: '#FFFFFF',
border: '#18191B33',
},
button: {
surface: '#F8F8F9',
text: '#090909',
primary: '#2A7DEB',
secondary: '#6E747B',
info: '#4B8DEC',
success: '#169068',
notice: '#B07203',
warning: '#B07203',
danger: '#E1465E',
},
editor: {
primary: '#5511BF',
secondary: '#A31D8D',
info: '#14646E',
success: '#086E14',
notice: '#616605',
warning: '#747576',
danger: '#1749BD',
},
},
};
export const fleetDarkPurple: Theme = {
id: 'fleet-dark-purple',
label: 'Fleet Dark Purple',
dark: true,
base: {
surface: '#1C1827',
surfaceHighlight: '#262136',
surfaceActive: '#3E3852',
border: '#3E3852',
text: '#E0E1E4',
textSubtle: '#E0E1E480',
textSubtlest: '#E0E1E44D',
primary: '#B174D9',
secondary: '#E0E1E480',
info: '#4B8DEC',
success: '#169068',
notice: '#B07203',
warning: '#B07203',
danger: '#E1465E',
},
components: {
appHeader: {
surface: '#13101B',
border: '#3E3852',
},
responsePane: {
surface: '#1C1827',
border: '#3E3852',
},
dialog: {
surface: '#262136',
border: '#3E3852',
},
button: {
surface: '#262136',
text: '#E0E1E4',
primary: '#A660D4',
secondary: '#E0E1E480',
info: '#4B8DEC',
success: '#169068',
notice: '#B07203',
warning: '#B07203',
danger: '#E1465E',
},
editor: {
primary: '#C7A65D',
secondary: '#93A6F5',
info: '#E09B70',
success: '#62A362',
notice: '#85A658',
warning: '#7e7d86',
danger: '#4DACF0',
},
},
};
export const fleetDark: Theme = {
id: 'fleet-dark',
label: 'Fleet Dark',
dark: true,
base: {
surface: '#18191B',
surfaceHighlight: '#252629',
surfaceActive: '#3E4147',
border: '#3E4147',
text: '#E0E1E4',
textSubtle: '#898E94',
textSubtlest: '#646B71',
primary: '#4B8DEC',
secondary: '#898E94',
info: '#4B8DEC',
success: '#169068',
notice: '#B07203',
warning: '#B07203',
danger: '#E1465E',
},
components: {
appHeader: {
surface: '#090909',
border: '#3E4147',
},
responsePane: {
surface: '#18191B',
border: '#3E4147',
},
dialog: {
surface: '#252629',
border: '#3E4147',
},
button: {
surface: '#252629',
text: '#E0E1E4',
primary: '#2A7DEB',
secondary: '#898E94',
info: '#4B8DEC',
success: '#169068',
notice: '#B07203',
warning: '#B07203',
danger: '#E1465E',
},
editor: {
primary: '#EBC88D',
secondary: '#AF9CFF',
info: '#82D2CE',
success: '#A8C5A0',
notice: '#C7A65D',
warning: '#909194',
danger: '#87C3FF',
},
},
};

View File

@@ -0,0 +1,46 @@
import type { Theme } from '@yaakapp/api';
export const githubDarkDimmed: Theme = {
id: 'github-dark-dimmed',
label: 'GitHub Dark Dimmed',
dark: true,
base: {
surface: 'hsl(215, 15%, 16%)',
surfaceHighlight: 'hsl(215, 13%, 20%)',
text: 'hsl(212, 15%, 78%)',
textSubtle: 'hsl(212, 10%, 55%)',
textSubtlest: 'hsl(212, 8%, 42%)',
primary: 'hsl(212, 80%, 65%)',
secondary: 'hsl(212, 10%, 55%)',
info: 'hsl(212, 80%, 65%)',
success: 'hsl(140, 50%, 50%)',
notice: 'hsl(42, 75%, 55%)',
warning: 'hsl(27, 80%, 55%)',
danger: 'hsl(355, 70%, 55%)',
},
components: {
dialog: {
surface: 'hsl(215, 15%, 13%)',
},
sidebar: {
surface: 'hsl(215, 14%, 14%)',
border: 'hsl(215, 12%, 19%)',
},
appHeader: {
surface: 'hsl(215, 15%, 12%)',
border: 'hsl(215, 12%, 17%)',
},
responsePane: {
surface: 'hsl(215, 14%, 14%)',
border: 'hsl(215, 12%, 19%)',
},
button: {
primary: 'hsl(212, 80%, 58%)',
info: 'hsl(212, 80%, 58%)',
success: 'hsl(140, 50%, 45%)',
notice: 'hsl(42, 75%, 48%)',
warning: 'hsl(27, 80%, 48%)',
danger: 'hsl(355, 70%, 48%)',
},
},
};

View File

@@ -0,0 +1,55 @@
import type { Theme } from '@yaakapp/api';
export const githubDark: Theme = {
id: 'github-dark',
label: 'GitHub',
dark: true,
base: {
surface: 'hsl(213,30%,7%)',
surfaceHighlight: 'hsl(213,16%,13%)',
text: 'hsl(212,27%,89%)',
textSubtle: 'hsl(212,9%,57%)',
textSubtlest: 'hsl(217,8%,45%)',
border: 'hsl(215,21%,11%)',
primary: 'hsl(262,78%,74%)',
secondary: 'hsl(217,8%,50%)',
info: 'hsl(215,84%,64%)',
success: 'hsl(129,48%,52%)',
notice: 'hsl(39,71%,58%)',
warning: 'hsl(22,83%,60%)',
danger: 'hsl(3,83%,65%)',
},
components: {
button: {
primary: 'hsl(262,79%,71%)',
secondary: 'hsl(217,8%,45%)',
info: 'hsl(215,84%,60%)',
success: 'hsl(129,48%,47%)',
notice: 'hsl(39,71%,53%)',
warning: 'hsl(22,83%,56%)',
danger: 'hsl(3,83%,61%)',
},
},
};
export const githubLight: Theme = {
id: 'github-light',
label: 'GitHub',
dark: false,
base: {
surface: 'hsl(0,0%,100%)',
surfaceHighlight: 'hsl(210,29%,94%)',
text: 'hsl(213,13%,14%)',
textSubtle: 'hsl(212,9%,43%)',
textSubtlest: 'hsl(203,8%,55%)',
border: 'hsl(210,15%,92%)',
borderSubtle: 'hsl(210,15%,92%)',
primary: 'hsl(261,69%,59%)',
secondary: 'hsl(212,8%,47%)',
info: 'hsl(212,92%,48%)',
success: 'hsl(137,66%,32%)',
notice: 'hsl(40,100%,40%)',
warning: 'hsl(24,100%,44%)',
danger: 'hsl(356,71%,48%)',
},
};

View File

@@ -0,0 +1,21 @@
import type { Theme } from '@yaakapp/api';
export const gruvbox: Theme = {
id: 'gruvbox',
label: 'Gruvbox',
dark: true,
base: {
surface: 'hsl(0,0%,16%)',
surfaceHighlight: 'hsl(20,3%,19%)',
text: 'hsl(53,74%,91%)',
textSubtle: 'hsl(39,24%,66%)',
textSubtlest: 'hsl(30,12%,51%)',
primary: 'hsl(344,47%,68%)',
secondary: 'hsl(157,16%,58%)',
info: 'hsl(104,35%,62%)',
success: 'hsl(61,66%,44%)',
notice: 'hsl(42,95%,58%)',
warning: 'hsl(27,99%,55%)',
danger: 'hsl(6,96%,59%)',
},
};

View File

@@ -0,0 +1,46 @@
import type { Theme } from '@yaakapp/api';
export const highContrast: Theme = {
id: 'high-contrast',
label: 'High Contrast Light',
dark: false,
base: {
surface: 'white',
surfaceHighlight: 'hsl(218,24%,93%)',
text: 'black',
textSubtle: 'hsl(217,24%,40%)',
textSubtlest: 'hsl(217,24%,40%)',
border: 'hsl(217,22%,50%)',
borderSubtle: 'hsl(217,22%,60%)',
primary: 'hsl(267,67%,47%)',
secondary: 'hsl(218,18%,53%)',
info: 'hsl(206,100%,36%)',
success: 'hsl(155,100%,26%)',
notice: 'hsl(45,100%,31%)',
warning: 'hsl(30,99%,34%)',
danger: 'hsl(334,100%,35%)',
},
};
export const highContrastDark: Theme = {
id: 'high-contrast-dark',
label: 'High Contrast Dark',
dark: true,
base: {
surface: 'hsl(0,0%,0%)',
surfaceHighlight: 'hsl(0,0%,20%)',
text: 'hsl(0,0%,100%)',
textSubtle: 'hsl(0,0%,90%)',
textSubtlest: 'hsl(0,0%,80%)',
selection: 'hsl(276,100%,30%)',
surfaceActive: 'hsl(276,100%,30%)',
border: 'hsl(0,0%,60%)',
primary: 'hsl(266,100%,85%)',
secondary: 'hsl(242,20%,72%)',
info: 'hsl(208,100%,83%)',
success: 'hsl(150,100%,63%)',
notice: 'hsl(49,100%,77%)',
warning: 'hsl(28,100%,73%)',
danger: 'hsl(343,100%,79%)',
},
};

View File

@@ -0,0 +1,47 @@
import type { Theme } from '@yaakapp/api';
export const horizon: Theme = {
id: 'horizon',
label: 'Horizon',
dark: true,
base: {
surface: 'hsl(220, 16%, 13%)',
surfaceHighlight: 'hsl(220, 14%, 18%)',
text: 'hsl(220, 15%, 85%)',
textSubtle: 'hsl(220, 10%, 55%)',
textSubtlest: 'hsl(220, 8%, 45%)',
primary: 'hsl(5, 85%, 68%)',
secondary: 'hsl(220, 10%, 55%)',
info: 'hsl(217, 70%, 68%)',
success: 'hsl(92, 50%, 60%)',
notice: 'hsl(34, 92%, 70%)',
warning: 'hsl(20, 90%, 65%)',
danger: 'hsl(355, 80%, 65%)',
},
components: {
dialog: {
surface: 'hsl(220, 16%, 10%)',
},
sidebar: {
surface: 'hsl(220, 14%, 15%)',
border: 'hsl(220, 14%, 19%)',
},
appHeader: {
surface: 'hsl(220, 16%, 11%)',
border: 'hsl(220, 14%, 17%)',
},
responsePane: {
surface: 'hsl(220, 14%, 15%)',
border: 'hsl(220, 14%, 19%)',
},
button: {
primary: 'hsl(5, 85%, 61%)',
secondary: 'hsl(224,8%,53%)',
info: 'hsl(217, 70%, 61%)',
success: 'hsl(92, 50%, 53%)',
notice: 'hsl(34, 92%, 63%)',
warning: 'hsl(20, 90%, 58%)',
danger: 'hsl(355, 80%, 58%)',
},
},
};

View File

@@ -0,0 +1,58 @@
import type { Theme } from '@yaakapp/api';
export const hotdogStand: Theme = {
id: 'hotdog-stand',
label: 'Hotdog Stand',
dark: true,
base: {
surface: 'hsl(0,100%,50%)',
surfaceHighlight: 'hsl(0,0%,0%)',
text: 'hsl(0,0%,100%)',
textSubtle: 'hsl(0,0%,100%)',
textSubtlest: 'hsl(60,100%,50%)',
border: 'hsl(0,0%,0%)',
primary: 'hsl(60,100%,50%)',
secondary: 'hsl(60,100%,50%)',
info: 'hsl(60,100%,50%)',
success: 'hsl(60,100%,50%)',
notice: 'hsl(60,100%,50%)',
warning: 'hsl(60,100%,50%)',
danger: 'hsl(60,100%,50%)',
},
components: {
appHeader: {
surface: 'hsl(0,0%,0%)',
text: 'hsl(0,0%,100%)',
textSubtle: 'hsl(60,100%,50%)',
textSubtlest: 'hsl(0,100%,50%)',
},
menu: {
surface: 'hsl(0,0%,0%)',
border: 'hsl(0,100%,50%)',
surfaceHighlight: 'hsl(0,100%,50%)',
text: 'hsl(0,0%,100%)',
textSubtle: 'hsl(60,100%,50%)',
textSubtlest: 'hsl(60,100%,50%)',
},
button: {
surface: 'hsl(0,0%,0%)',
text: 'hsl(0,0%,100%)',
primary: 'hsl(0,0%,0%)',
secondary: 'hsl(0,0%,100%)',
info: 'hsl(0,0%,0%)',
success: 'hsl(60,100%,50%)',
notice: 'hsl(60,100%,50%)',
warning: 'hsl(0,0%,0%)',
danger: 'hsl(0,100%,50%)',
},
editor: {
primary: 'hsl(0,0%,100%)',
secondary: 'hsl(0,0%,100%)',
info: 'hsl(0,0%,100%)',
success: 'hsl(0,0%,100%)',
notice: 'hsl(60,100%,50%)',
warning: 'hsl(0,0%,100%)',
danger: 'hsl(0,0%,100%)',
},
},
};

View File

@@ -0,0 +1,39 @@
import type { Theme } from '@yaakapp/api';
export const materialDarker: Theme = {
id: 'material-darker',
label: 'Material Darker',
dark: true,
base: {
surface: 'hsl(0, 0%, 13%)',
surfaceHighlight: 'hsl(0, 0%, 18%)',
text: 'hsl(0, 0%, 93%)',
textSubtle: 'hsl(0, 0%, 65%)',
textSubtlest: 'hsl(0, 0%, 50%)',
primary: 'hsl(262, 100%, 75%)',
secondary: 'hsl(0, 0%, 60%)',
info: 'hsl(224, 100%, 75%)',
success: 'hsl(84, 60%, 73%)',
notice: 'hsl(43, 100%, 70%)',
warning: 'hsl(14, 85%, 70%)',
danger: 'hsl(1, 77%, 59%)',
},
components: {
sidebar: {
surface: 'hsl(0, 0%, 11%)',
border: 'hsl(0, 0%, 16%)',
},
appHeader: {
surface: 'hsl(0, 0%, 9%)',
border: 'hsl(0, 0%, 14%)',
},
button: {
primary: 'hsl(262, 100%, 68%)',
info: 'hsl(224, 100%, 68%)',
success: 'hsl(84, 60%, 66%)',
notice: 'hsl(43, 100%, 63%)',
warning: 'hsl(14, 85%, 63%)',
danger: 'hsl(1, 77%, 52%)',
},
},
};

View File

@@ -0,0 +1,43 @@
import type { Theme } from '@yaakapp/api';
export const materialOcean: Theme = {
id: 'material-ocean',
label: 'Material Ocean',
dark: true,
base: {
surface: 'hsl(230, 25%, 14%)',
surfaceHighlight: 'hsl(230, 20%, 18%)',
text: 'hsl(220, 53%, 85%)',
textSubtle: 'hsl(228, 12%, 54%)',
textSubtlest: 'hsl(228, 12%, 42%)',
primary: 'hsl(262, 100%, 75%)',
secondary: 'hsl(228, 12%, 60%)',
info: 'hsl(224, 100%, 75%)',
success: 'hsl(84, 60%, 73%)',
notice: 'hsl(43, 100%, 70%)',
warning: 'hsl(14, 85%, 70%)',
danger: 'hsl(1, 77%, 59%)',
},
components: {
sidebar: {
surface: 'hsl(230, 25%, 12%)',
border: 'hsl(230, 20%, 18%)',
},
appHeader: {
surface: 'hsl(230, 25%, 10%)',
border: 'hsl(230, 20%, 16%)',
},
responsePane: {
surface: 'hsl(230, 25%, 12%)',
border: 'hsl(230, 20%, 18%)',
},
button: {
primary: 'hsl(262, 100%, 68%)',
info: 'hsl(224, 100%, 68%)',
success: 'hsl(84, 60%, 66%)',
notice: 'hsl(43, 100%, 63%)',
warning: 'hsl(14, 85%, 63%)',
danger: 'hsl(1, 77%, 52%)',
},
},
};

View File

@@ -0,0 +1,45 @@
import type { Theme } from '@yaakapp/api';
export const materialPalenight: Theme = {
id: 'material-palenight',
label: 'Material Palenight',
dark: true,
base: {
surface: '#292D3E',
surfaceHighlight: '#313850',
text: '#BFC7D5',
textSubtle: '#697098',
textSubtlest: '#4E5579',
primary: '#c792ea',
secondary: '#697098',
info: '#82AAFF',
success: '#C3E88D',
notice: '#FFCB6B',
warning: '#F78C6C',
danger: '#ff5572',
},
components: {
dialog: {
surface: '#232635',
},
sidebar: {
surface: '#292D3E',
},
appHeader: {
surface: '#282C3D',
},
responsePane: {
surface: '#313850',
border: '#3a3f58',
},
button: {
primary: '#c792ea',
secondary: '#697098',
info: '#82AAFF',
success: '#C3E88D',
notice: '#FFCB6B',
warning: '#F78C6C',
danger: '#ff5572',
},
},
};

View File

@@ -0,0 +1,217 @@
import type { Theme } from '@yaakapp/api';
export const monokaiPro: Theme = {
id: 'monokai-pro',
label: 'Monokai Pro',
dark: true,
base: {
surface: 'hsl(285,5%,17%)',
text: 'hsl(60,25%,98%)',
textSubtle: 'hsl(0,1%,75%)',
textSubtlest: 'hsl(300,0%,57%)',
primary: 'hsl(250,77%,78%)',
secondary: 'hsl(0,1%,75%)',
info: 'hsl(186,71%,69%)',
success: 'hsl(90,59%,66%)',
notice: 'hsl(45,100%,70%)',
warning: 'hsl(20,96%,70%)',
danger: 'hsl(345,100%,69%)',
},
components: {
appHeader: {
surface: 'hsl(300,5%,13%)',
text: 'hsl(0,1%,75%)',
textSubtle: 'hsl(300,0%,57%)',
textSubtlest: 'hsl(300,1%,44%)',
},
button: {
primary: 'hsl(250,77%,70%)',
secondary: 'hsl(0,1%,68%)',
info: 'hsl(186,71%,62%)',
success: 'hsl(90,59%,59%)',
notice: 'hsl(45,100%,63%)',
warning: 'hsl(20,96%,63%)',
danger: 'hsl(345,100%,62%)',
},
},
};
export const monokaiProClassic: Theme = {
id: 'monokai-pro-classic',
label: 'Monokai Pro Classic',
dark: true,
base: {
surface: 'hsl(70,8%,15%)',
text: 'hsl(69,100%,97%)',
textSubtle: 'hsl(65,9%,73%)',
textSubtlest: 'hsl(66,4%,55%)',
primary: 'hsl(261,100%,75%)',
secondary: 'hsl(202,8%,72%)',
info: 'hsl(190,81%,67%)',
success: 'hsl(80,76%,53%)',
notice: 'hsl(54,70%,68%)',
warning: 'hsl(32,98%,56%)',
danger: 'hsl(338,95%,56%)',
},
components: {
appHeader: {
surface: 'hsl(72,9%,11%)',
text: 'hsl(202,8%,72%)',
textSubtle: 'hsl(213,4%,48%)',
textSubtlest: 'hsl(223,6%,44%)',
},
button: {
primary: 'hsl(261,100%,68%)',
secondary: 'hsl(202,8%,65%)',
info: 'hsl(190,81%,60%)',
success: 'hsl(80,76%,48%)',
notice: 'hsl(54,71%,61%)',
warning: 'hsl(32,98%,50%)',
danger: 'hsl(338,95%,50%)',
},
},
};
export const monokaiProMachine: Theme = {
id: 'monokai-pro-machine',
label: 'Monokai Pro Machine',
dark: true,
base: {
surface: 'hsl(200,16%,18%)',
text: 'hsl(173,24%,93%)',
textSubtle: 'hsl(185,6%,57%)',
textSubtlest: 'hsl(189,6%,45%)',
primary: 'hsl(258,86%,80%)',
secondary: 'hsl(175,9%,75%)',
info: 'hsl(194,81%,72%)',
success: 'hsl(98,67%,69%)',
notice: 'hsl(52,100%,72%)',
warning: 'hsl(28,100%,72%)',
danger: 'hsl(353,100%,71%)',
},
components: {
appHeader: {
surface: 'hsl(196,16%,14%)',
text: 'hsl(202,8%,72%)',
textSubtle: 'hsl(213,4%,48%)',
textSubtlest: 'hsl(223,6%,44%)',
},
button: {
primary: 'hsl(258,86%,72%)',
secondary: 'hsl(175,9%,68%)',
info: 'hsl(194,80%,65%)',
success: 'hsl(98,67%,62%)',
notice: 'hsl(52,100%,65%)',
warning: 'hsl(28,100%,65%)',
danger: 'hsl(353,100%,64%)',
},
},
};
export const monokaiProOctagon: Theme = {
id: 'monokai-pro-octagon',
label: 'Monokai Pro Octagon',
dark: true,
base: {
surface: 'hsl(233,18%,19%)',
text: 'hsl(173,24%,93%)',
textSubtle: 'hsl(202,8%,72%)',
textSubtlest: 'hsl(213,4%,48%)',
primary: 'hsl(292,30%,70%)',
secondary: 'hsl(202,8%,72%)',
info: 'hsl(155,37%,72%)',
success: 'hsl(75,60%,61%)',
notice: 'hsl(44,100%,71%)',
warning: 'hsl(23,100%,68%)',
danger: 'hsl(352,100%,70%)',
},
components: {
appHeader: {
surface: 'hsl(235,18%,14%)',
text: 'hsl(202,8%,72%)',
textSubtle: 'hsl(213,4%,48%)',
textSubtlest: 'hsl(223,6%,44%)',
},
button: {
primary: 'hsl(292,26%,63%)',
secondary: 'hsl(201,7%,65%)',
info: 'hsl(155,33%,65%)',
success: 'hsl(75,54%,55%)',
notice: 'hsl(44,90%,64%)',
warning: 'hsl(23,90%,61%)',
danger: 'hsl(352,90%,63%)',
},
},
};
export const monokaiProRistretto: Theme = {
id: 'monokai-pro-ristretto',
label: 'Monokai Pro Ristretto',
dark: true,
base: {
surface: 'hsl(0,9%,16%)',
text: 'hsl(351,100%,97%)',
textSubtle: 'hsl(355,9%,74%)',
textSubtlest: 'hsl(354,4%,56%)',
primary: 'hsl(239,63%,79%)',
secondary: 'hsl(355,9%,74%)',
info: 'hsl(170,53%,69%)',
success: 'hsl(88,57%,66%)',
notice: 'hsl(41,92%,70%)',
warning: 'hsl(13,85%,70%)',
danger: 'hsl(349,97%,70%)',
},
components: {
appHeader: {
surface: 'hsl(0,8%,12%)',
text: 'hsl(355,9%,74%)',
textSubtle: 'hsl(354,4%,56%)',
textSubtlest: 'hsl(353,4%,43%)',
},
button: {
primary: 'hsl(239,63%,71%)',
secondary: 'hsl(355,9%,67%)',
info: 'hsl(170,53%,62%)',
success: 'hsl(88,57%,59%)',
notice: 'hsl(41,92%,63%)',
warning: 'hsl(13,86%,63%)',
danger: 'hsl(349,97%,63%)',
},
},
};
export const monokaiProSpectrum: Theme = {
id: 'monokai-pro-spectrum',
label: 'Monokai Pro Spectrum',
dark: true,
base: {
surface: 'hsl(0,0%,13%)',
text: 'hsl(266,100%,97%)',
textSubtle: 'hsl(264,7%,73%)',
textSubtlest: 'hsl(266,3%,55%)',
primary: 'hsl(247,61%,72%)',
secondary: 'hsl(264,7%,73%)',
info: 'hsl(188,74%,63%)',
success: 'hsl(133,54%,66%)',
notice: 'hsl(51,96%,69%)',
warning: 'hsl(23,98%,66%)',
danger: 'hsl(343,96%,68%)',
},
components: {
appHeader: {
surface: 'hsl(0,0%,10%)',
text: 'hsl(264,7%,73%)',
textSubtle: 'hsl(266,3%,55%)',
textSubtlest: 'hsl(264,2%,41%)',
},
button: {
primary: 'hsl(247,61%,65%)',
secondary: 'hsl(264,7%,66%)',
info: 'hsl(188,74%,57%)',
success: 'hsl(133,54%,59%)',
notice: 'hsl(51,96%,62%)',
warning: 'hsl(23,98%,59%)',
danger: 'hsl(343,96%,61%)',
},
},
};

View File

@@ -0,0 +1,28 @@
import type { Theme } from '@yaakapp/api';
export const moonlight: Theme = {
id: 'moonlight',
label: 'Moonlight',
dark: true,
base: {
surface: 'hsl(234,23%,17%)',
text: 'hsl(225,71%,90%)',
textSubtle: 'hsl(230,28%,62%)',
textSubtlest: 'hsl(232,26%,43%)',
primary: 'hsl(262,100%,82%)',
secondary: 'hsl(232,18%,65%)',
info: 'hsl(217,100%,74%)',
success: 'hsl(174,66%,54%)',
notice: 'hsl(35,100%,73%)',
warning: 'hsl(17,100%,71%)',
danger: 'hsl(356,100%,73%)',
},
components: {
appHeader: {
surface: 'hsl(233,23%,15%)',
},
sidebar: {
surface: 'hsl(233,23%,15%)',
},
},
};

View File

@@ -0,0 +1,78 @@
import type { Theme } from '@yaakapp/api';
export const nightOwl: Theme = {
id: 'night-owl',
label: 'Night Owl',
dark: true,
base: {
surface: 'hsl(207, 95%, 8%)',
surfaceHighlight: 'hsl(207, 50%, 14%)',
text: 'hsl(213, 50%, 90%)',
textSubtle: 'hsl(213, 30%, 70%)',
textSubtlest: 'hsl(213, 20%, 50%)',
border: 'hsl(207, 50%, 14%)',
primary: 'hsl(261, 51%, 51%)',
secondary: 'hsl(213, 30%, 60%)',
info: 'hsl(220, 100%, 75%)',
success: 'hsl(145, 100%, 43%)',
notice: 'hsl(62, 61%, 71%)',
warning: 'hsl(4, 90%, 58%)',
danger: 'hsl(4, 90%, 58%)',
},
components: {
dialog: {
surface: 'hsl(207, 95%, 6%)',
},
sidebar: {
surface: 'hsl(207, 95%, 8%)',
border: 'hsl(207, 50%, 14%)',
},
appHeader: {
surface: 'hsl(207, 95%, 5%)',
border: 'hsl(207, 50%, 12%)',
},
responsePane: {
surface: 'hsl(207, 70%, 10%)',
border: 'hsl(207, 50%, 14%)',
},
button: {
primary: 'hsl(261, 51%, 45%)',
secondary: 'hsl(213, 30%, 60%)',
info: 'hsl(220, 100%, 68%)',
success: 'hsl(145, 100%, 38%)',
notice: 'hsl(62, 61%, 64%)',
warning: 'hsl(4, 90%, 52%)',
danger: 'hsl(4, 90%, 52%)',
},
},
};
export const lightOwl: Theme = {
id: 'light-owl',
label: 'Light Owl',
dark: false,
base: {
surface: 'hsl(0, 0%, 98%)',
surfaceHighlight: 'hsl(210, 18%, 94%)',
text: 'hsl(224, 26%, 27%)',
textSubtle: 'hsl(224, 15%, 45%)',
textSubtlest: 'hsl(224, 10%, 55%)',
primary: 'hsl(283, 100%, 41%)',
secondary: 'hsl(224, 15%, 50%)',
info: 'hsl(219, 75%, 40%)',
success: 'hsl(145, 70%, 35%)',
notice: 'hsl(36, 95%, 40%)',
warning: 'hsl(0, 55%, 55%)',
danger: 'hsl(0, 55%, 50%)',
},
components: {
sidebar: {
surface: 'hsl(210, 20%, 96%)',
border: 'hsl(210, 15%, 90%)',
},
appHeader: {
surface: 'hsl(210, 20%, 94%)',
border: 'hsl(210, 15%, 88%)',
},
},
};

View File

@@ -0,0 +1,47 @@
import type { Theme } from '@yaakapp/api';
export const noctisAzureus: Theme = {
id: 'noctis-azureus',
label: 'Noctis Azureus',
dark: true,
base: {
surface: 'hsl(210, 35%, 14%)',
surfaceHighlight: 'hsl(210, 30%, 19%)',
text: 'hsl(180, 45%, 85%)',
textSubtle: 'hsl(180, 25%, 60%)',
textSubtlest: 'hsl(180, 18%, 45%)',
primary: 'hsl(175, 60%, 55%)',
secondary: 'hsl(200, 70%, 65%)',
info: 'hsl(200, 70%, 65%)',
success: 'hsl(85, 55%, 60%)',
notice: 'hsl(45, 90%, 60%)',
warning: 'hsl(25, 85%, 58%)',
danger: 'hsl(355, 75%, 62%)',
},
components: {
dialog: {
surface: 'hsl(210, 35%, 11%)',
},
sidebar: {
surface: 'hsl(210, 33%, 12%)',
border: 'hsl(210, 30%, 17%)',
},
appHeader: {
surface: 'hsl(210, 35%, 10%)',
border: 'hsl(210, 30%, 15%)',
},
responsePane: {
surface: 'hsl(210, 33%, 12%)',
border: 'hsl(210, 30%, 17%)',
},
button: {
primary: 'hsl(175, 60%, 48%)',
secondary: 'hsl(200, 70%, 58%)',
info: 'hsl(200, 70%, 58%)',
success: 'hsl(85, 55%, 53%)',
notice: 'hsl(45, 90%, 53%)',
warning: 'hsl(25, 85%, 51%)',
danger: 'hsl(355, 75%, 55%)',
},
},
};

View File

@@ -0,0 +1,85 @@
import type { Theme } from '@yaakapp/api';
export const nord: Theme = {
id: 'nord',
label: 'Nord',
dark: true,
base: {
surface: 'hsl(220,16%,22%)',
surfaceHighlight: 'hsl(220,14%,28%)',
text: 'hsl(220,28%,93%)',
textSubtle: 'hsl(220,26%,90%)',
textSubtlest: 'hsl(220,24%,86%)',
primary: 'hsl(193,38%,68%)',
secondary: 'hsl(210,34%,63%)',
info: 'hsl(174,25%,69%)',
success: 'hsl(89,26%,66%)',
notice: 'hsl(40,66%,73%)',
warning: 'hsl(17,48%,64%)',
danger: 'hsl(353,43%,56%)',
},
components: {
sidebar: {
surface: 'hsl(220,16%,22%)',
},
appHeader: {
surface: 'hsl(220,14%,28%)',
},
},
};
export const nordLight: Theme = {
id: 'nord-light',
label: 'Nord Light',
dark: false,
base: {
surface: '#eceff4',
surfaceHighlight: '#e5e9f0',
text: '#24292e',
textSubtle: '#444d56',
textSubtlest: '#586069',
primary: '#2188ff',
secondary: '#586069',
info: '#005cc5',
success: '#28a745',
notice: '#e36209',
warning: '#e36209',
danger: '#cb2431',
},
components: {
sidebar: {
surface: '#e5e9f0',
},
appHeader: {
surface: '#e5e9f0',
},
},
};
export const nordLightBrighter: Theme = {
id: 'nord-light-brighter',
label: 'Nord Light Brighter',
dark: false,
base: {
surface: '#ffffff',
surfaceHighlight: '#f6f8fa',
text: '#24292e',
textSubtle: '#444d56',
textSubtlest: '#586069',
primary: '#2188ff',
secondary: '#586069',
info: '#005cc5',
success: '#28a745',
notice: '#e36209',
warning: '#e36209',
danger: '#cb2431',
},
components: {
sidebar: {
surface: '#f6f8fa',
},
appHeader: {
surface: '#f6f8fa',
},
},
};

View File

@@ -0,0 +1,44 @@
import type { Theme } from '@yaakapp/api';
export const oneDarkPro: Theme = {
id: 'one-dark-pro',
label: 'One Dark Pro',
dark: true,
base: {
surface: 'hsl(220, 13%, 18%)',
surfaceHighlight: 'hsl(220, 13%, 22%)',
text: 'hsl(219, 14%, 71%)',
textSubtle: 'hsl(219, 10%, 53%)',
textSubtlest: 'hsl(220, 9%, 45%)',
primary: 'hsl(286, 60%, 67%)',
secondary: 'hsl(219, 14%, 60%)',
info: 'hsl(207, 82%, 66%)',
success: 'hsl(95, 38%, 62%)',
notice: 'hsl(39, 67%, 69%)',
warning: 'hsl(29, 54%, 61%)',
danger: 'hsl(355, 65%, 65%)',
},
components: {
sidebar: {
surface: 'hsl(220, 13%, 16%)',
border: 'hsl(220, 13%, 20%)',
},
appHeader: {
surface: 'hsl(220, 13%, 14%)',
border: 'hsl(220, 13%, 20%)',
},
responsePane: {
surface: 'hsl(220, 13%, 16%)',
border: 'hsl(220, 13%, 20%)',
},
button: {
primary: 'hsl(286, 60%, 60%)',
secondary: 'hsl(219, 14%, 53%)',
info: 'hsl(207, 82%, 59%)',
success: 'hsl(95, 38%, 55%)',
notice: 'hsl(39, 67%, 62%)',
warning: 'hsl(29, 54%, 54%)',
danger: 'hsl(355, 65%, 58%)',
},
},
};

View File

@@ -0,0 +1,47 @@
import type { Theme } from '@yaakapp/api';
export const pandaSyntax: Theme = {
id: 'panda',
label: 'Panda Syntax',
dark: true,
base: {
surface: 'hsl(225, 15%, 15%)',
surfaceHighlight: 'hsl(225, 12%, 20%)',
text: 'hsl(0, 0%, 90%)',
textSubtle: 'hsl(0, 0%, 65%)',
textSubtlest: 'hsl(0, 0%, 50%)',
primary: 'hsl(353, 95%, 70%)',
secondary: 'hsl(0, 0%, 65%)',
info: 'hsl(200, 85%, 65%)',
success: 'hsl(175, 90%, 65%)',
notice: 'hsl(40, 100%, 65%)',
warning: 'hsl(40, 100%, 65%)',
danger: 'hsl(0, 90%, 65%)',
},
components: {
dialog: {
surface: 'hsl(225, 15%, 12%)',
},
sidebar: {
surface: 'hsl(225, 14%, 13%)',
border: 'hsl(225, 12%, 18%)',
},
appHeader: {
surface: 'hsl(225, 15%, 11%)',
border: 'hsl(225, 12%, 16%)',
},
responsePane: {
surface: 'hsl(225, 14%, 13%)',
border: 'hsl(225, 12%, 18%)',
},
button: {
primary: 'hsl(353, 95%, 63%)',
secondary: 'hsl(0, 0%, 58%)',
info: 'hsl(200, 85%, 58%)',
success: 'hsl(175, 90%, 58%)',
notice: 'hsl(40, 100%, 58%)',
warning: 'hsl(40, 100%, 58%)',
danger: 'hsl(0, 90%, 58%)',
},
},
};

View File

@@ -0,0 +1,18 @@
import type { Theme } from '@yaakapp/api';
export const relaxing: Theme = {
id: 'relaxing',
label: 'Relaxing',
dark: true,
base: {
surface: 'hsl(267,33%,17%)',
text: 'hsl(275,49%,92%)',
primary: 'hsl(267,84%,81%)',
secondary: 'hsl(227,35%,80%)',
info: 'hsl(217,92%,76%)',
success: 'hsl(115,54%,76%)',
notice: 'hsl(41,86%,83%)',
warning: 'hsl(23,92%,75%)',
danger: 'hsl(343,81%,75%)',
},
};

View File

@@ -0,0 +1,111 @@
import type { Theme } from '@yaakapp/api';
export const rosePine: Theme = {
id: 'rose-pine',
label: 'Rosé Pine',
dark: true,
base: {
surface: 'hsl(249,22%,12%)',
text: 'hsl(245,50%,91%)',
textSubtle: 'hsl(248,15%,61%)',
textSubtlest: 'hsl(249,12%,47%)',
primary: 'hsl(267,57%,78%)',
secondary: 'hsl(249,12%,47%)',
info: 'hsl(199,49%,60%)',
success: 'hsl(180,43%,73%)',
notice: 'hsl(35,88%,72%)',
warning: 'hsl(1,74%,79%)',
danger: 'hsl(343,76%,68%)',
},
components: {
responsePane: {
surface: 'hsl(247,23%,15%)',
},
sidebar: {
surface: 'hsl(247,23%,15%)',
},
menu: {
surface: 'hsl(248,21%,26%)',
textSubtle: 'hsl(248,15%,66%)',
textSubtlest: 'hsl(249,12%,52%)',
border: 'hsl(248,21%,35%)',
borderSubtle: 'hsl(248,21%,33%)',
},
},
};
export const rosePineMoon: Theme = {
id: 'rose-pine-moon',
label: 'Rosé Pine Moon',
dark: true,
base: {
surface: 'hsl(246,24%,17%)',
text: 'hsl(245,50%,91%)',
textSubtle: 'hsl(248,15%,61%)',
textSubtlest: 'hsl(249,12%,47%)',
primary: 'hsl(267,57%,78%)',
secondary: 'hsl(248,15%,61%)',
info: 'hsl(197,48%,60%)',
success: 'hsl(197,48%,60%)',
notice: 'hsl(35,88%,72%)',
warning: 'hsl(2,66%,75%)',
danger: 'hsl(343,76%,68%)',
},
components: {
responsePane: {
surface: 'hsl(247,24%,20%)',
},
sidebar: {
surface: 'hsl(247,24%,20%)',
},
menu: {
surface: 'hsl(248,21%,26%)',
textSubtle: 'hsl(248,15%,61%)',
textSubtlest: 'hsl(249,12%,55%)',
border: 'hsl(248,21%,35%)',
borderSubtle: 'hsl(248,21%,31%)',
},
},
};
export const rosePineDawn: Theme = {
id: 'rose-pine-dawn',
label: 'Rosé Pine Dawn',
dark: false,
base: {
surface: 'hsl(32,57%,95%)',
border: 'hsl(10,9%,86%)',
surfaceHighlight: 'hsl(25,35%,93%)',
text: 'hsl(248,19%,40%)',
textSubtle: 'hsl(248,12%,52%)',
textSubtlest: 'hsl(257,9%,61%)',
primary: 'hsl(271,27%,56%)',
secondary: 'hsl(249,12%,47%)',
info: 'hsl(197,52%,36%)',
success: 'hsl(188,31%,45%)',
notice: 'hsl(34,64%,49%)',
warning: 'hsl(2,47%,64%)',
danger: 'hsl(343,35%,55%)',
},
components: {
responsePane: {
border: 'hsl(20,12%,90%)',
},
sidebar: {
border: 'hsl(20,12%,90%)',
},
appHeader: {
border: 'hsl(20,12%,90%)',
},
input: {
border: 'hsl(10,9%,86%)',
},
dialog: {
border: 'hsl(20,12%,90%)',
},
menu: {
surface: 'hsl(28,40%,92%)',
border: 'hsl(10,9%,86%)',
},
},
};

View File

@@ -0,0 +1,99 @@
import type { Theme } from '@yaakapp/api';
export const shadesOfPurple: Theme = {
id: 'shades-of-purple',
label: 'Shades of Purple',
dark: true,
base: {
surface: '#2D2B55',
surfaceHighlight: '#1F1F41',
text: '#FFFFFF',
textSubtle: '#A599E9',
textSubtlest: '#7E72C4',
primary: '#FAD000',
secondary: '#A599E9',
info: '#80FFBB',
success: '#3AD900',
notice: '#FAD000',
warning: '#FF9D00',
danger: '#EC3A37F5',
},
components: {
dialog: {
surface: '#1E1E3F',
},
sidebar: {
surface: '#222244',
border: '#1E1E3F',
},
input: {
border: '#7E72C4',
},
appHeader: {
surface: '#1E1E3F',
border: '#1E1E3F',
},
responsePane: {
surface: 'hsl(240,33%,20%)',
border: 'hsl(240,33%,20%)',
},
button: {
primary: '#FAD000',
secondary: '#A599E9',
info: '#80FFBB',
success: '#3AD900',
notice: '#FAD000',
warning: '#FF9D00',
danger: '#EC3A37F5',
},
},
};
export const shadesOfPurpleSuperDark: Theme = {
id: 'shades-of-purple-super-dark',
label: 'Shades of Purple (Super Dark)',
dark: true,
base: {
surface: '#191830',
surfaceHighlight: '#1F1E3A',
text: '#FFFFFF',
textSubtle: '#A599E9',
textSubtlest: '#7E72C4',
primary: '#FAD000',
secondary: '#A599E9',
info: '#80FFBB',
success: '#3AD900',
notice: '#FAD000',
warning: '#FF9D00',
danger: '#EC3A37F5',
},
components: {
dialog: {
surface: '#15152b',
},
input: {
border: '#2D2B55',
},
sidebar: {
surface: '#131327',
border: '#131327',
},
appHeader: {
surface: '#15152a',
border: '#15152a',
},
responsePane: {
surface: '#131327',
border: '#131327',
},
button: {
primary: '#FAD000',
secondary: '#A599E9',
info: '#80FFBB',
success: '#3AD900',
notice: '#FAD000',
warning: '#FF9D00',
danger: '#EC3A37F5',
},
},
};

View File

@@ -0,0 +1,47 @@
import type { Theme } from '@yaakapp/api';
export const slackAubergine: Theme = {
id: 'slack-aubergine',
label: 'Slack Aubergine',
dark: true,
base: {
surface: 'hsl(270, 25%, 18%)',
surfaceHighlight: 'hsl(270, 22%, 24%)',
text: 'hsl(0, 0%, 100%)',
textSubtle: 'hsl(270, 15%, 75%)',
textSubtlest: 'hsl(270, 12%, 58%)',
primary: 'hsl(165, 100%, 40%)',
secondary: 'hsl(270, 12%, 65%)',
info: 'hsl(195, 95%, 55%)',
success: 'hsl(145, 80%, 50%)',
notice: 'hsl(43, 100%, 55%)',
warning: 'hsl(43, 100%, 50%)',
danger: 'hsl(0, 80%, 55%)',
},
components: {
dialog: {
surface: 'hsl(270, 25%, 14%)',
},
sidebar: {
surface: 'hsl(270, 23%, 15%)',
border: 'hsl(270, 22%, 22%)',
},
appHeader: {
surface: 'hsl(270, 25%, 13%)',
border: 'hsl(270, 22%, 20%)',
},
responsePane: {
surface: 'hsl(270, 23%, 15%)',
border: 'hsl(270, 22%, 22%)',
},
button: {
primary: 'hsl(165, 100%, 35%)',
secondary: 'hsl(270, 12%, 58%)',
info: 'hsl(195, 95%, 48%)',
success: 'hsl(145, 80%, 45%)',
notice: 'hsl(43, 100%, 48%)',
warning: 'hsl(43, 100%, 45%)',
danger: 'hsl(0, 80%, 48%)',
},
},
};

View File

@@ -0,0 +1,77 @@
import type { Theme } from '@yaakapp/api';
export const solarizedDark: Theme = {
id: 'solarized-dark',
label: 'Solarized Dark',
dark: true,
base: {
surface: '#002b36',
surfaceHighlight: '#073642',
text: '#839496',
textSubtle: '#657b83',
textSubtlest: '#586e75',
primary: '#268bd2',
secondary: '#657b83',
info: '#268bd2',
success: '#859900',
notice: '#b58900',
warning: '#cb4b16',
danger: '#dc322f',
},
components: {
dialog: {
surface: '#002b36',
},
sidebar: {
surface: '#073642',
border: 'hsl(192,81%,17%)',
},
appHeader: {
surface: '#002b36',
border: 'hsl(192,81%,16%)',
},
responsePane: {
surface: '#073642',
border: 'hsl(192,81%,17%)',
},
button: {
primary: '#268bd2',
secondary: '#657b83',
info: '#268bd2',
success: '#859900',
notice: '#b58900',
warning: '#cb4b16',
danger: '#dc322f',
},
},
};
export const solarizedLight: Theme = {
id: 'solarized-light',
label: 'Solarized Light',
dark: false,
base: {
surface: '#fdf6e3',
surfaceHighlight: '#eee8d5',
text: '#657b83',
textSubtle: '#839496',
textSubtlest: '#93a1a1',
primary: '#268bd2',
secondary: '#839496',
info: '#268bd2',
success: '#859900',
notice: '#b58900',
warning: '#cb4b16',
danger: '#dc322f',
},
components: {
sidebar: {
surface: '#eee8d5',
border: '#d3cbb7',
},
appHeader: {
surface: '#eee8d5',
border: '#d3cbb7',
},
},
};

View File

@@ -0,0 +1,56 @@
import type { Theme } from '@yaakapp/api';
export const synthwave84: Theme = {
id: 'synthwave-84',
label: "SynthWave '84",
dark: true,
base: {
surface: 'hsl(253, 45%, 15%)',
surfaceHighlight: 'hsl(253, 40%, 20%)',
text: 'hsl(300, 50%, 90%)',
textSubtle: 'hsl(280, 25%, 65%)',
textSubtlest: 'hsl(280, 20%, 50%)',
primary: 'hsl(320, 100%, 75%)',
secondary: 'hsl(280, 20%, 60%)',
info: 'hsl(177, 100%, 55%)',
success: 'hsl(83, 100%, 60%)',
notice: 'hsl(57, 100%, 60%)',
warning: 'hsl(30, 100%, 60%)',
danger: 'hsl(340, 100%, 65%)',
},
components: {
dialog: {
surface: 'hsl(253, 45%, 12%)',
},
sidebar: {
surface: 'hsl(253, 42%, 18%)',
border: 'hsl(253, 40%, 22%)',
},
appHeader: {
surface: 'hsl(253, 45%, 11%)',
border: 'hsl(253, 40%, 18%)',
},
responsePane: {
surface: 'hsl(253, 42%, 18%)',
border: 'hsl(253, 40%, 22%)',
},
button: {
primary: 'hsl(320, 100%, 68%)',
secondary: 'hsl(280, 20%, 53%)',
info: 'hsl(177, 100%, 48%)',
success: 'hsl(83, 100%, 53%)',
notice: 'hsl(57, 100%, 53%)',
warning: 'hsl(30, 100%, 53%)',
danger: 'hsl(340, 100%, 58%)',
},
editor: {
primary: 'hsl(177, 100%, 55%)',
secondary: 'hsl(280, 20%, 60%)',
info: 'hsl(320, 100%, 75%)',
success: 'hsl(83, 100%, 60%)',
notice: 'hsl(57, 100%, 60%)',
warning: 'hsl(30, 100%, 60%)',
danger: 'hsl(340, 100%, 65%)',
},
},
};

View File

@@ -0,0 +1,121 @@
import type { Theme } from '@yaakapp/api';
export const tokyoNight: Theme = {
id: 'tokyo-night',
label: 'Tokyo Night',
dark: true,
base: {
surface: 'hsl(235, 21%, 13%)',
surfaceHighlight: 'hsl(235, 18%, 18%)',
text: 'hsl(229, 28%, 76%)',
textSubtle: 'hsl(232, 18%, 52%)',
textSubtlest: 'hsl(234, 16%, 40%)',
primary: 'hsl(266, 100%, 78%)',
secondary: 'hsl(232, 18%, 52%)',
info: 'hsl(217, 100%, 73%)',
success: 'hsl(158, 57%, 63%)',
notice: 'hsl(40, 67%, 65%)',
warning: 'hsl(25, 75%, 58%)',
danger: 'hsl(358, 100%, 70%)',
},
components: {
dialog: {
surface: 'hsl(235, 21%, 11%)',
},
sidebar: {
surface: 'hsl(235, 21%, 11%)',
border: 'hsl(235, 18%, 16%)',
},
appHeader: {
surface: 'hsl(235, 21%, 9%)',
border: 'hsl(235, 18%, 14%)',
},
responsePane: {
surface: 'hsl(235, 21%, 11%)',
border: 'hsl(235, 18%, 16%)',
},
button: {
primary: 'hsl(266, 100%, 71%)',
info: 'hsl(217, 100%, 66%)',
success: 'hsl(158, 57%, 56%)',
notice: 'hsl(40, 67%, 58%)',
warning: 'hsl(25, 75%, 52%)',
danger: 'hsl(358, 100%, 63%)',
},
},
};
export const tokyoNightStorm: Theme = {
id: 'tokyo-night-storm',
label: 'Tokyo Night Storm',
dark: true,
base: {
surface: 'hsl(232, 25%, 17%)',
surfaceHighlight: 'hsl(232, 22%, 22%)',
text: 'hsl(229, 28%, 76%)',
textSubtle: 'hsl(232, 18%, 52%)',
textSubtlest: 'hsl(234, 16%, 40%)',
primary: 'hsl(266, 100%, 78%)',
secondary: 'hsl(232, 18%, 52%)',
info: 'hsl(217, 100%, 73%)',
success: 'hsl(158, 57%, 63%)',
notice: 'hsl(40, 67%, 65%)',
warning: 'hsl(25, 75%, 58%)',
danger: 'hsl(358, 100%, 70%)',
},
components: {
dialog: {
surface: 'hsl(232, 25%, 14%)',
},
sidebar: {
surface: 'hsl(232, 25%, 14%)',
border: 'hsl(232, 22%, 20%)',
},
appHeader: {
surface: 'hsl(232, 25%, 12%)',
border: 'hsl(232, 22%, 18%)',
},
responsePane: {
surface: 'hsl(232, 25%, 14%)',
border: 'hsl(232, 22%, 20%)',
},
button: {
primary: 'hsl(266, 100%, 71%)',
info: 'hsl(217, 100%, 66%)',
success: 'hsl(158, 57%, 56%)',
notice: 'hsl(40, 67%, 58%)',
warning: 'hsl(25, 75%, 52%)',
danger: 'hsl(358, 100%, 63%)',
},
},
};
export const tokyoNightDay: Theme = {
id: 'tokyo-night-day',
label: 'Tokyo Night Day',
dark: false,
base: {
surface: 'hsl(212, 100%, 98%)',
surfaceHighlight: 'hsl(212, 60%, 93%)',
text: 'hsl(233, 26%, 27%)',
textSubtle: 'hsl(232, 18%, 45%)',
textSubtlest: 'hsl(232, 12%, 55%)',
primary: 'hsl(290, 80%, 45%)',
secondary: 'hsl(232, 18%, 50%)',
info: 'hsl(217, 88%, 52%)',
success: 'hsl(160, 75%, 35%)',
notice: 'hsl(41, 80%, 40%)',
warning: 'hsl(20, 80%, 48%)',
danger: 'hsl(359, 65%, 48%)',
},
components: {
sidebar: {
surface: 'hsl(212, 60%, 95%)',
border: 'hsl(212, 40%, 88%)',
},
appHeader: {
surface: 'hsl(212, 60%, 93%)',
border: 'hsl(212, 40%, 86%)',
},
},
};

View File

@@ -0,0 +1,44 @@
import type { Theme } from '@yaakapp/api';
export const triangle: Theme = {
id: 'triangle',
dark: true,
label: 'Triangle',
base: {
surface: 'rgb(0,0,0)',
surfaceHighlight: 'rgb(21,21,21)',
surfaceActive: 'rgb(31,31,31)',
text: 'rgb(237,237,237)',
textSubtle: 'rgb(161,161,161)',
textSubtlest: 'rgb(115,115,115)',
border: 'rgb(31,31,31)',
primary: 'rgb(196,114,251)',
secondary: 'rgb(161,161,161)',
info: 'rgb(71,168,255)',
success: 'rgb(0,202,81)',
notice: 'rgb(255,175,0)',
warning: '#FF4C8D',
danger: '#fd495a',
},
components: {
editor: {
danger: '#FF4C8D',
warning: '#fd495a',
},
dialog: {
surface: 'rgb(10,10,10)',
border: 'rgb(31,31,31)',
},
sidebar: {
border: 'rgb(31,31,31)',
},
responsePane: {
surface: 'rgb(10,10,10)',
border: 'rgb(31,31,31)',
},
appHeader: {
surface: 'rgb(10,10,10)',
border: 'rgb(31,31,31)',
},
},
};

View File

@@ -0,0 +1,77 @@
import type { Theme } from '@yaakapp/api';
export const vitesseDark: Theme = {
id: 'vitesse-dark',
label: 'Vitesse Dark',
dark: true,
base: {
surface: 'hsl(220, 13%, 10%)',
surfaceHighlight: 'hsl(220, 12%, 15%)',
text: 'hsl(220, 10%, 80%)',
textSubtle: 'hsl(220, 8%, 55%)',
textSubtlest: 'hsl(220, 6%, 42%)',
primary: 'hsl(143, 50%, 55%)',
secondary: 'hsl(220, 8%, 55%)',
info: 'hsl(214, 60%, 65%)',
success: 'hsl(143, 50%, 55%)',
notice: 'hsl(45, 65%, 65%)',
warning: 'hsl(30, 60%, 60%)',
danger: 'hsl(355, 60%, 60%)',
},
components: {
dialog: {
surface: 'hsl(220, 13%, 7%)',
},
sidebar: {
surface: 'hsl(220, 12%, 8%)',
border: 'hsl(220, 10%, 14%)',
},
appHeader: {
surface: 'hsl(220, 13%, 6%)',
border: 'hsl(220, 10%, 12%)',
},
responsePane: {
surface: 'hsl(220, 12%, 8%)',
border: 'hsl(220, 10%, 14%)',
},
button: {
primary: 'hsl(143, 50%, 48%)',
secondary: 'hsl(220, 8%, 48%)',
info: 'hsl(214, 60%, 58%)',
success: 'hsl(143, 50%, 48%)',
notice: 'hsl(45, 65%, 58%)',
warning: 'hsl(30, 60%, 53%)',
danger: 'hsl(355, 60%, 53%)',
},
},
};
export const vitesseLight: Theme = {
id: 'vitesse-light',
label: 'Vitesse Light',
dark: false,
base: {
surface: 'hsl(0, 0%, 100%)',
surfaceHighlight: 'hsl(40, 20%, 96%)',
text: 'hsl(0, 0%, 24%)',
textSubtle: 'hsl(0, 0%, 45%)',
textSubtlest: 'hsl(0, 0%, 55%)',
primary: 'hsl(143, 40%, 40%)',
secondary: 'hsl(0, 0%, 45%)',
info: 'hsl(214, 50%, 48%)',
success: 'hsl(143, 40%, 40%)',
notice: 'hsl(40, 60%, 42%)',
warning: 'hsl(25, 60%, 48%)',
danger: 'hsl(345, 50%, 48%)',
},
components: {
sidebar: {
surface: 'hsl(40, 20%, 97%)',
border: 'hsl(40, 15%, 92%)',
},
appHeader: {
surface: 'hsl(40, 20%, 95%)',
border: 'hsl(40, 15%, 90%)',
},
},
};

View File

@@ -0,0 +1,47 @@
import type { Theme } from '@yaakapp/api';
export const winterIsComing: Theme = {
id: 'winter-is-coming',
label: 'Winter is Coming',
dark: true,
base: {
surface: 'hsl(216, 50%, 10%)',
surfaceHighlight: 'hsl(216, 40%, 15%)',
text: 'hsl(210, 20%, 88%)',
textSubtle: 'hsl(210, 15%, 60%)',
textSubtlest: 'hsl(210, 10%, 45%)',
primary: 'hsl(176, 85%, 60%)',
secondary: 'hsl(210, 15%, 60%)',
info: 'hsl(210, 65%, 65%)',
success: 'hsl(100, 65%, 55%)',
notice: 'hsl(45, 100%, 65%)',
warning: 'hsl(30, 90%, 55%)',
danger: 'hsl(350, 100%, 65%)',
},
components: {
dialog: {
surface: 'hsl(216, 50%, 7%)',
},
sidebar: {
surface: 'hsl(216, 45%, 12%)',
border: 'hsl(216, 40%, 17%)',
},
appHeader: {
surface: 'hsl(216, 50%, 8%)',
border: 'hsl(216, 40%, 14%)',
},
responsePane: {
surface: 'hsl(216, 45%, 12%)',
border: 'hsl(216, 40%, 17%)',
},
button: {
primary: 'hsl(176, 85%, 53%)',
secondary: 'hsl(210, 15%, 53%)',
info: 'hsl(210, 65%, 58%)',
success: 'hsl(100, 65%, 48%)',
notice: 'hsl(45, 100%, 58%)',
warning: 'hsl(30, 90%, 48%)',
danger: 'hsl(350, 100%, 58%)',
},
},
};

View File

@@ -5,3 +5,4 @@ chain_width = 100
max_width = 100
single_line_if_else_max_width = 100
fn_call_width = 100
struct_lit_width = 100

View File

@@ -0,0 +1,153 @@
#!/usr/bin/env node
/**
* Git post-checkout hook for auto-configuring worktree environments.
* This runs after 'git checkout' or 'git worktree add'.
*
* Args from git:
* process.argv[2] - previous HEAD ref
* process.argv[3] - new HEAD ref
* process.argv[4] - flag (1 = branch checkout, 0 = file checkout)
*/
import fs from 'fs';
import path from 'path';
import { execSync, execFileSync } from 'child_process';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const isBranchCheckout = process.argv[4] === '1';
if (!isBranchCheckout) {
process.exit(0);
}
// Check if we're in a worktree by looking for .git file (not directory)
const gitPath = path.join(process.cwd(), '.git');
const isWorktree = fs.existsSync(gitPath) && fs.statSync(gitPath).isFile();
if (!isWorktree) {
process.exit(0);
}
const envLocalPath = path.join(process.cwd(), '.env.local');
// Don't overwrite existing .env.local
if (fs.existsSync(envLocalPath)) {
process.exit(0);
}
console.log('Detected new worktree - configuring ports in .env.local');
// Find the highest ports in use across all worktrees
// Main worktree (first in list) is assumed to use default ports 1420/64343
let maxMcpPort = 64343;
let maxDevPort = 1420;
try {
const worktreeList = execSync('git worktree list --porcelain', { encoding: 'utf8' });
const worktreePaths = worktreeList
.split('\n')
.filter(line => line.startsWith('worktree '))
.map(line => line.replace('worktree ', '').trim());
// Skip the first worktree (main) since it uses default ports
for (let i = 1; i < worktreePaths.length; i++) {
const worktreePath = worktreePaths[i];
const envPath = path.join(worktreePath, '.env.local');
if (fs.existsSync(envPath)) {
const content = fs.readFileSync(envPath, 'utf8');
const mcpMatch = content.match(/^YAAK_PLUGIN_MCP_SERVER_PORT=(\d+)/m);
if (mcpMatch) {
const port = parseInt(mcpMatch[1], 10);
if (port > maxMcpPort) {
maxMcpPort = port;
}
}
const devMatch = content.match(/^YAAK_DEV_PORT=(\d+)/m);
if (devMatch) {
const port = parseInt(devMatch[1], 10);
if (port > maxDevPort) {
maxDevPort = port;
}
}
}
}
// Increment to get the next available port
maxDevPort++;
maxMcpPort++;
} catch (err) {
console.error('Warning: Could not check other worktrees for port conflicts:', err.message);
// Continue with default ports
}
// Create .env.local with unique ports
const envContent = `# Auto-generated by git post-checkout hook
# This file configures ports for this worktree to avoid conflicts
# Vite dev server port (main worktree uses 1420)
YAAK_DEV_PORT=${maxDevPort}
# MCP Server port (main worktree uses 64343)
YAAK_PLUGIN_MCP_SERVER_PORT=${maxMcpPort}
`;
fs.writeFileSync(envLocalPath, envContent, 'utf8');
console.log(`Created .env.local with YAAK_DEV_PORT=${maxDevPort} and YAAK_PLUGIN_MCP_SERVER_PORT=${maxMcpPort}`);
// Copy gitignored editor config folders from main worktree (.zed, .vscode, .claude, etc.)
// This ensures your editor settings, tasks, and configurations are available in the new worktree
// without needing to manually copy them or commit them to git.
try {
const worktreeList = execSync('git worktree list --porcelain', { encoding: 'utf8' });
const mainWorktreePath = worktreeList
.split('\n')
.find(line => line.startsWith('worktree '))
?.replace('worktree ', '')
.trim();
if (mainWorktreePath) {
// Find all .* folders in main worktree root that are gitignored
const entries = fs.readdirSync(mainWorktreePath, { withFileTypes: true });
const dotFolders = entries
.filter(entry => entry.isDirectory() && entry.name.startsWith('.'))
.map(entry => entry.name);
for (const folder of dotFolders) {
const sourcePath = path.join(mainWorktreePath, folder);
const destPath = path.join(process.cwd(), folder);
try {
// Check if it's gitignored - run from main worktree directory
execFileSync('git', ['check-ignore', '-q', folder], {
stdio: 'pipe',
cwd: mainWorktreePath
});
// It's gitignored, copy it
fs.cpSync(sourcePath, destPath, { recursive: true });
console.log(`Copied ${folder} from main worktree`);
} catch {
// Not gitignored or doesn't exist, skip
}
}
}
} catch (err) {
console.warn('Warning: Could not copy files from main worktree:', err.message);
// Continue anyway
}
// Run npm run init to install dependencies and bootstrap
console.log('\nRunning npm run init to install dependencies and bootstrap...');
try {
execSync('npm run init', { stdio: 'inherit' });
console.log('\n✓ Worktree setup complete!');
} catch (err) {
console.error('\n✗ Failed to run npm run init. You may need to run it manually.');
process.exit(1);
}

View File

@@ -7,7 +7,7 @@ if (version.startsWith('wasm-pack ')) {
}
console.log('Installing wasm-pack via cargo...');
execSync('cargo install wasm-pack', { stdio: 'inherit' });
execSync('cargo install wasm-pack --locked', { stdio: 'inherit' });
function tryExecSync(cmd) {
try {

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