Repository: the-dev-tools/dev-tools Branch: main Commit: 6ce75086a2d6 Files: 1290 Total size: 21.2 MB Directory structure: gitextract_0r326h2t/ ├── .editorconfig ├── .envrc ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.yaml │ │ ├── config.yml │ │ └── feature-request.yaml │ ├── actions/ │ │ ├── dependencies-unix/ │ │ │ └── action.yaml │ │ ├── dependencies-windows/ │ │ │ └── action.yaml │ │ └── setup/ │ │ └── action.yaml │ └── workflows/ │ ├── benchmark-generic.yml │ ├── check.yaml │ ├── goci.yml │ ├── release-chrome-extension.yaml │ ├── release-cloudflare-pages.yaml │ ├── release-electron-builder.yaml │ ├── release-go.yaml │ ├── release.yaml │ ├── sql.yml │ └── update-scoop.yaml ├── .gitignore ├── .golangci.yml ├── .prettierignore ├── .sqlfluff ├── .vscode/ │ ├── extensions.json │ └── settings.json ├── AGENTS.md ├── LICENSE ├── README.md ├── apps/ │ ├── api-recorder-extension/ │ │ ├── eslint.config.ts │ │ ├── package.disabled.json │ │ ├── postcss.config.js │ │ ├── project.disabled.json │ │ ├── src/ │ │ │ ├── auth.ts │ │ │ ├── background.ts │ │ │ ├── layout.tsx │ │ │ ├── popup.tsx │ │ │ ├── postman.ts │ │ │ ├── recorder.ts │ │ │ ├── runtime.ts │ │ │ ├── storage.ts │ │ │ ├── styles.css │ │ │ ├── tabs/ │ │ │ │ └── auth-callback.tsx │ │ │ ├── types.d.ts │ │ │ ├── ui/ │ │ │ │ └── button.tsx │ │ │ └── utils.ts │ │ ├── tsconfig.json │ │ └── tsconfig.lib.json │ ├── cli/ │ │ ├── .devtools.yaml │ │ ├── CHANGELOG.md │ │ ├── cmd/ │ │ │ ├── config.go │ │ │ ├── flow.go │ │ │ ├── import.go │ │ │ ├── root.go │ │ │ └── version.go │ │ ├── embedded/ │ │ │ └── embeddedJS/ │ │ │ └── embededJS.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── install.sh │ │ ├── internal/ │ │ │ ├── common/ │ │ │ │ └── services.go │ │ │ ├── importer/ │ │ │ │ ├── importer.go │ │ │ │ └── importer_test.go │ │ │ ├── model/ │ │ │ │ └── result.go │ │ │ ├── reporter/ │ │ │ │ ├── reporter.go │ │ │ │ └── reporter_test.go │ │ │ └── runner/ │ │ │ ├── jsrunner.go │ │ │ ├── runner.go │ │ │ └── runner_test.go │ │ ├── main.go │ │ ├── mode_cli.go │ │ ├── package.json │ │ ├── project.json │ │ ├── taskfile.yaml │ │ └── test/ │ │ └── yamlflow/ │ │ ├── ai_node_example.yaml │ │ ├── example_run_yamlflow.yaml │ │ ├── file_upload_example.yaml │ │ ├── graphql_run_example.yaml │ │ ├── integration_yamlflow_test.go │ │ ├── loop_break_example.yaml │ │ ├── multi_flow_run_example.yaml │ │ ├── simple_run_example.yaml │ │ ├── test_run_field.yaml │ │ ├── testdata/ │ │ │ └── sample.txt │ │ └── ws_run_example.yaml │ └── desktop/ │ ├── CHANGELOG.md │ ├── build/ │ │ ├── electron-publisher-custom.js │ │ ├── entitlements.mac.plist │ │ └── icon.icns │ ├── build.ts │ ├── electron.vite.config.ts │ ├── eslint.config.ts │ ├── package.json │ ├── project.json │ ├── src/ │ │ ├── main/ │ │ │ ├── env.d.ts │ │ │ ├── index.ts │ │ │ └── update.ts │ │ ├── preload/ │ │ │ └── index.ts │ │ └── renderer/ │ │ ├── env.d.ts │ │ ├── index.html │ │ ├── main.tsx │ │ └── styles.css │ ├── tsconfig.json │ └── tsconfig.lib.json ├── docs/ │ ├── CODE-OF-CONDUCT.md │ ├── CONTRIBUTING.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── cli.md ├── eslint.config.ts ├── flake.nix ├── go.work ├── go.work.sum ├── install.ps1 ├── nx.json ├── package.json ├── packages/ │ ├── auth/ │ │ ├── eslint.config.ts │ │ ├── package.json │ │ ├── project.json │ │ ├── src/ │ │ │ ├── adapter.test.ts │ │ │ ├── adapter.ts │ │ │ ├── auth-effect.ts │ │ │ ├── auth.ts │ │ │ ├── client.ts │ │ │ ├── config.ts │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ └── vitest.config.ts │ ├── auth-lib/ │ │ ├── go.mod │ │ ├── go.sum │ │ ├── jwks/ │ │ │ ├── jwks.go │ │ │ └── jwks_test.go │ │ └── project.json │ ├── client/ │ │ ├── .storybook/ │ │ │ ├── main.ts │ │ │ ├── manager.ts │ │ │ └── preview.tsx │ │ ├── eslint.config.ts │ │ ├── index.html │ │ ├── package.json │ │ ├── project.json │ │ ├── src/ │ │ │ ├── app/ │ │ │ │ ├── context.tsx │ │ │ │ ├── dev-tools.tsx │ │ │ │ ├── entrypoint.tsx │ │ │ │ ├── env.d.ts │ │ │ │ ├── error.tsx │ │ │ │ ├── import-meta.d.ts │ │ │ │ ├── index.tsx │ │ │ │ ├── router/ │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── route-tree.gen.ts │ │ │ │ │ ├── routes/ │ │ │ │ │ │ ├── (dashboard)/ │ │ │ │ │ │ │ └── __virtual.ts │ │ │ │ │ │ └── __root.tsx │ │ │ │ │ └── vite.tsx │ │ │ │ ├── styles.css │ │ │ │ └── umami.tsx │ │ │ ├── features/ │ │ │ │ ├── agent/ │ │ │ │ │ ├── agent-logger.ts │ │ │ │ │ ├── context-builder.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── layout.ts │ │ │ │ │ ├── tool-executor.ts │ │ │ │ │ ├── tool-schemas.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ ├── use-agent-chat.ts │ │ │ │ │ └── use-agent-provider-key.ts │ │ │ │ ├── delta/ │ │ │ │ │ └── index.tsx │ │ │ │ ├── expression/ │ │ │ │ │ ├── code-mirror/ │ │ │ │ │ │ ├── drop-extension.ts │ │ │ │ │ │ ├── extensions.tsx │ │ │ │ │ │ ├── syntax.grammar │ │ │ │ │ │ └── syntax.grammar.d.ts │ │ │ │ │ ├── guess-language.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── prettier.tsx │ │ │ │ │ ├── reference-path.ts │ │ │ │ │ └── reference.tsx │ │ │ │ ├── file-system/ │ │ │ │ │ └── index.tsx │ │ │ │ └── form-table/ │ │ │ │ └── index.tsx │ │ │ ├── pages/ │ │ │ │ ├── credential/ │ │ │ │ │ ├── @x/ │ │ │ │ │ │ └── workspace.tsx │ │ │ │ │ ├── routes/ │ │ │ │ │ │ └── credential/ │ │ │ │ │ │ └── $credentialIdCan/ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── tab.tsx │ │ │ │ ├── dashboard/ │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── routes/ │ │ │ │ │ ├── (user)/ │ │ │ │ │ │ └── __virtual.ts │ │ │ │ │ ├── (workspace)/ │ │ │ │ │ │ └── __virtual.ts │ │ │ │ │ └── index.tsx │ │ │ │ ├── flow/ │ │ │ │ │ ├── @x/ │ │ │ │ │ │ └── workspace.tsx │ │ │ │ │ ├── add-node.tsx │ │ │ │ │ ├── agent-panel.tsx │ │ │ │ │ ├── context.tsx │ │ │ │ │ ├── edge.tsx │ │ │ │ │ ├── edit.tsx │ │ │ │ │ ├── handle.tsx │ │ │ │ │ ├── history.tsx │ │ │ │ │ ├── node.tsx │ │ │ │ │ ├── nodes/ │ │ │ │ │ │ ├── ai.tsx │ │ │ │ │ │ ├── condition.tsx │ │ │ │ │ │ ├── for-each.tsx │ │ │ │ │ │ ├── for.tsx │ │ │ │ │ │ ├── graphql.tsx │ │ │ │ │ │ ├── http.tsx │ │ │ │ │ │ ├── javascript.tsx │ │ │ │ │ │ ├── manual-start.tsx │ │ │ │ │ │ ├── run-sub-flow.tsx │ │ │ │ │ │ ├── sub-flow-return.tsx │ │ │ │ │ │ ├── sub-flow-trigger.tsx │ │ │ │ │ │ ├── wait.tsx │ │ │ │ │ │ ├── ws-connection.tsx │ │ │ │ │ │ └── ws-send.tsx │ │ │ │ │ ├── routes/ │ │ │ │ │ │ └── flow/ │ │ │ │ │ │ └── $flowIdCan/ │ │ │ │ │ │ ├── history.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── route.tsx │ │ │ │ │ ├── selection.ts │ │ │ │ │ ├── tab.tsx │ │ │ │ │ ├── undo.ts │ │ │ │ │ └── viewport.tsx │ │ │ │ ├── graphql/ │ │ │ │ │ ├── @x/ │ │ │ │ │ │ ├── flow.tsx │ │ │ │ │ │ └── workspace.tsx │ │ │ │ │ ├── history.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ ├── request/ │ │ │ │ │ │ ├── assert.tsx │ │ │ │ │ │ ├── header.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── panel.tsx │ │ │ │ │ │ ├── query-editor.tsx │ │ │ │ │ │ ├── top-bar.tsx │ │ │ │ │ │ ├── url.tsx │ │ │ │ │ │ └── variables-editor.tsx │ │ │ │ │ ├── response/ │ │ │ │ │ │ ├── assert.tsx │ │ │ │ │ │ ├── body.tsx │ │ │ │ │ │ ├── header.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── routes/ │ │ │ │ │ │ └── graphql/ │ │ │ │ │ │ └── $graphqlIdCan/ │ │ │ │ │ │ ├── delta.$deltaGraphqlIdCan.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── route.tsx │ │ │ │ │ └── tab.tsx │ │ │ │ ├── http/ │ │ │ │ │ ├── @x/ │ │ │ │ │ │ ├── flow.tsx │ │ │ │ │ │ └── workspace.tsx │ │ │ │ │ ├── history.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ ├── request/ │ │ │ │ │ │ ├── assert.tsx │ │ │ │ │ │ ├── body/ │ │ │ │ │ │ │ ├── form-data.tsx │ │ │ │ │ │ │ ├── panel.tsx │ │ │ │ │ │ │ ├── raw.tsx │ │ │ │ │ │ │ └── url-encoded.tsx │ │ │ │ │ │ ├── header.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── panel.tsx │ │ │ │ │ │ ├── search-param.tsx │ │ │ │ │ │ ├── top-bar.tsx │ │ │ │ │ │ └── url.tsx │ │ │ │ │ ├── response/ │ │ │ │ │ │ ├── assert.tsx │ │ │ │ │ │ ├── body.tsx │ │ │ │ │ │ ├── header.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── routes/ │ │ │ │ │ │ └── http/ │ │ │ │ │ │ └── $httpIdCan/ │ │ │ │ │ │ ├── delta.$deltaHttpIdCan.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── route.tsx │ │ │ │ │ └── tab.tsx │ │ │ │ ├── user/ │ │ │ │ │ ├── @x/ │ │ │ │ │ │ └── dashboard.tsx │ │ │ │ │ └── routes/ │ │ │ │ │ ├── signIn.tsx │ │ │ │ │ └── signUp.tsx │ │ │ │ ├── websocket/ │ │ │ │ │ ├── @x/ │ │ │ │ │ │ ├── flow.tsx │ │ │ │ │ │ └── workspace.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ ├── request/ │ │ │ │ │ │ ├── header.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── panel.tsx │ │ │ │ │ │ ├── top-bar.tsx │ │ │ │ │ │ └── url.tsx │ │ │ │ │ ├── response/ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── routes/ │ │ │ │ │ │ └── websocket/ │ │ │ │ │ │ └── $websocketIdCan/ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── route.tsx │ │ │ │ │ ├── tab.tsx │ │ │ │ │ └── use-websocket.ts │ │ │ │ └── workspace/ │ │ │ │ ├── @x/ │ │ │ │ │ └── dashboard.tsx │ │ │ │ ├── routes/ │ │ │ │ │ └── workspace/ │ │ │ │ │ └── $workspaceIdCan/ │ │ │ │ │ ├── (credential)/ │ │ │ │ │ │ └── __virtual.ts │ │ │ │ │ ├── (flow)/ │ │ │ │ │ │ └── __virtual.ts │ │ │ │ │ ├── (graphql)/ │ │ │ │ │ │ └── __virtual.ts │ │ │ │ │ ├── (http)/ │ │ │ │ │ │ └── __virtual.ts │ │ │ │ │ ├── (websocket)/ │ │ │ │ │ │ └── __virtual.ts │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── route.tsx │ │ │ │ └── ui/ │ │ │ │ └── status-bar.tsx │ │ │ ├── shared/ │ │ │ │ ├── api/ │ │ │ │ │ ├── auth.internal.tsx │ │ │ │ │ ├── auth.tsx │ │ │ │ │ ├── collection.internal.tsx │ │ │ │ │ ├── collection.tsx │ │ │ │ │ ├── connect-query.tsx │ │ │ │ │ ├── connect-rpc.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── interceptors.tsx │ │ │ │ │ ├── mock.tsx │ │ │ │ │ ├── protobuf.tsx │ │ │ │ │ └── transport.tsx │ │ │ │ ├── lib/ │ │ │ │ │ ├── faker.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── order.tsx │ │ │ │ │ ├── react-render.tsx │ │ │ │ │ ├── router.tsx │ │ │ │ │ ├── runtime.tsx │ │ │ │ │ ├── tanstack-db.tsx │ │ │ │ │ └── types.tsx │ │ │ │ ├── routes.tsx │ │ │ │ └── ui/ │ │ │ │ ├── dashboard.tsx │ │ │ │ └── index.tsx │ │ │ └── widgets/ │ │ │ ├── environment/ │ │ │ │ └── index.tsx │ │ │ ├── export/ │ │ │ │ └── index.tsx │ │ │ ├── import/ │ │ │ │ └── index.tsx │ │ │ └── tabs/ │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ └── vite.config.ts │ ├── db/ │ │ ├── ai_test.go │ │ ├── db.go │ │ ├── flow_node_http_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── pkg/ │ │ │ ├── dbtest/ │ │ │ │ ├── unix.go │ │ │ │ └── windows.go │ │ │ ├── sqlc/ │ │ │ │ ├── gen/ │ │ │ │ │ ├── ai.sql.go │ │ │ │ │ ├── betterauth.sql.go │ │ │ │ │ ├── db.go │ │ │ │ │ ├── environment.sql.go │ │ │ │ │ ├── files.sql.go │ │ │ │ │ ├── flow.sql.go │ │ │ │ │ ├── graphql.sql.go │ │ │ │ │ ├── http.sql.go │ │ │ │ │ ├── models.go │ │ │ │ │ ├── streaming_bench_test.go │ │ │ │ │ ├── users.sql.go │ │ │ │ │ ├── websocket.sql.go │ │ │ │ │ └── workspaces.sql.go │ │ │ │ ├── pyproject.toml │ │ │ │ ├── queries/ │ │ │ │ │ ├── ai.sql │ │ │ │ │ ├── betterauth.sql │ │ │ │ │ ├── environment.sql │ │ │ │ │ ├── files.sql │ │ │ │ │ ├── flow.sql │ │ │ │ │ ├── graphql.sql │ │ │ │ │ ├── http.sql │ │ │ │ │ ├── users.sql │ │ │ │ │ ├── websocket.sql │ │ │ │ │ └── workspaces.sql │ │ │ │ ├── schema/ │ │ │ │ │ ├── 00_users.sql │ │ │ │ │ ├── 01_workspaces.sql │ │ │ │ │ ├── 02_environment.sql │ │ │ │ │ ├── 03_files.sql │ │ │ │ │ ├── 04_http.sql │ │ │ │ │ ├── 05_flow.sql │ │ │ │ │ ├── 06_migration.sql │ │ │ │ │ ├── 07_ai.sql │ │ │ │ │ ├── 08_betterauth.sql │ │ │ │ │ ├── 08_graphql.sql │ │ │ │ │ ├── 09_graphql_delta.sql │ │ │ │ │ └── 10_websocket.sql │ │ │ │ ├── sqlc.go │ │ │ │ └── sqlc.yaml │ │ │ ├── sqlitelocal/ │ │ │ │ └── sqlitelocal.go │ │ │ ├── sqlitemem/ │ │ │ │ └── sqlitemem.go │ │ │ ├── tursolocal/ │ │ │ │ ├── linux.go │ │ │ │ ├── tursolocal_bench_test.go │ │ │ │ ├── types.go │ │ │ │ └── windows.go │ │ │ └── tursomem/ │ │ │ ├── linux.go │ │ │ └── windows.go │ │ ├── project.json │ │ └── verification_test.go │ ├── server/ │ │ ├── .golangci.yml │ │ ├── cmd/ │ │ │ ├── authadapter-testserver/ │ │ │ │ └── main.go │ │ │ ├── server/ │ │ │ │ └── server.go │ │ │ └── serverrun/ │ │ │ └── serverrun.go │ │ ├── docs/ │ │ │ └── specs/ │ │ │ ├── BACKEND_ARCHITECTURE_V2.md │ │ │ ├── BULK_SYNC_TRANSACTION_WRAPPERS.md │ │ │ ├── FLOW.md │ │ │ ├── GRAPHQL.md │ │ │ ├── HTTP.md │ │ │ ├── MUTATION.md │ │ │ ├── NEW_FLOW_NODE.md │ │ │ └── SYNC.md │ │ ├── go.mod │ │ ├── go.sum │ │ ├── internal/ │ │ │ ├── api/ │ │ │ │ ├── api.go │ │ │ │ ├── api_unix.go │ │ │ │ ├── api_windows.go │ │ │ │ ├── middleware/ │ │ │ │ │ ├── mwauth/ │ │ │ │ │ │ ├── helpers.go │ │ │ │ │ │ ├── mwauth.go │ │ │ │ │ │ └── mwauth_test.go │ │ │ │ │ ├── mwcodec/ │ │ │ │ │ │ └── mwcodec.go │ │ │ │ │ └── mwcompress/ │ │ │ │ │ └── mwcompress.go │ │ │ │ ├── rauthadapter/ │ │ │ │ │ ├── rauthadapter.go │ │ │ │ │ └── rauthadapter_test.go │ │ │ │ ├── rcredential/ │ │ │ │ │ ├── deps_test.go │ │ │ │ │ ├── rcredential.go │ │ │ │ │ └── rcredential_test.go │ │ │ │ ├── renv/ │ │ │ │ │ ├── deps_test.go │ │ │ │ │ ├── renv.go │ │ │ │ │ └── renv_test.go │ │ │ │ ├── rexportv2/ │ │ │ │ │ ├── export.go │ │ │ │ │ ├── exporter_test.go │ │ │ │ │ ├── integration_test.go │ │ │ │ │ ├── rexportv2.go │ │ │ │ │ ├── rexportv2_test.go │ │ │ │ │ ├── service_test.go │ │ │ │ │ ├── storage.go │ │ │ │ │ ├── storage_test.go │ │ │ │ │ └── validation_test.go │ │ │ │ ├── rfile/ │ │ │ │ │ ├── integration_test.go │ │ │ │ │ ├── rfile.go │ │ │ │ │ ├── rfile_rpc_test.go │ │ │ │ │ └── rfile_test.go │ │ │ │ ├── rflowv2/ │ │ │ │ │ ├── assertion_race_test.go │ │ │ │ │ ├── chaos_test.go │ │ │ │ │ ├── delta_integration_test.go │ │ │ │ │ ├── execution_cache_test.go │ │ │ │ │ ├── import_interface.go │ │ │ │ │ ├── js_e2e_test.go │ │ │ │ │ ├── logging_test.go │ │ │ │ │ ├── node_config_sync_test.go │ │ │ │ │ ├── node_sync_test.go │ │ │ │ │ ├── relaxed_insert_test.go │ │ │ │ │ ├── rflowv2.go │ │ │ │ │ ├── rflowv2_common.go │ │ │ │ │ ├── rflowv2_common_test.go │ │ │ │ │ ├── rflowv2_concurrency_test.go │ │ │ │ │ ├── rflowv2_copy_paste.go │ │ │ │ │ ├── rflowv2_copy_paste_test.go │ │ │ │ │ ├── rflowv2_edge.go │ │ │ │ │ ├── rflowv2_edge_test.go │ │ │ │ │ ├── rflowv2_edge_transaction_test.go │ │ │ │ │ ├── rflowv2_exec.go │ │ │ │ │ ├── rflowv2_exec_publisher.go │ │ │ │ │ ├── rflowv2_exec_sync_test.go │ │ │ │ │ ├── rflowv2_exec_test.go │ │ │ │ │ ├── rflowv2_exec_transaction_test.go │ │ │ │ │ ├── rflowv2_flow.go │ │ │ │ │ ├── rflowv2_flow_create_test.go │ │ │ │ │ ├── rflowv2_flow_duplicate_sync_test.go │ │ │ │ │ ├── rflowv2_flow_transaction_test.go │ │ │ │ │ ├── rflowv2_import.go │ │ │ │ │ ├── rflowv2_mutation_verification_test.go │ │ │ │ │ ├── rflowv2_node.go │ │ │ │ │ ├── rflowv2_node_ai.go │ │ │ │ │ ├── rflowv2_node_ai_provider.go │ │ │ │ │ ├── rflowv2_node_condition.go │ │ │ │ │ ├── rflowv2_node_condition_test.go │ │ │ │ │ ├── rflowv2_node_condition_transaction_test.go │ │ │ │ │ ├── rflowv2_node_exec.go │ │ │ │ │ ├── rflowv2_node_exec_test.go │ │ │ │ │ ├── rflowv2_node_for.go │ │ │ │ │ ├── rflowv2_node_for_test.go │ │ │ │ │ ├── rflowv2_node_for_transaction_test.go │ │ │ │ │ ├── rflowv2_node_for_zero_value_test.go │ │ │ │ │ ├── rflowv2_node_foreach.go │ │ │ │ │ ├── rflowv2_node_foreach_transaction_test.go │ │ │ │ │ ├── rflowv2_node_graphql.go │ │ │ │ │ ├── rflowv2_node_http.go │ │ │ │ │ ├── rflowv2_node_http_test.go │ │ │ │ │ ├── rflowv2_node_http_transaction_test.go │ │ │ │ │ ├── rflowv2_node_javascript.go │ │ │ │ │ ├── rflowv2_node_javascript_transaction_test.go │ │ │ │ │ ├── rflowv2_node_memory.go │ │ │ │ │ ├── rflowv2_node_run_sub_flow.go │ │ │ │ │ ├── rflowv2_node_sub_flow_return.go │ │ │ │ │ ├── rflowv2_node_sub_flow_trigger.go │ │ │ │ │ ├── rflowv2_node_test.go │ │ │ │ │ ├── rflowv2_node_wait.go │ │ │ │ │ ├── rflowv2_node_ws_connection.go │ │ │ │ │ ├── rflowv2_node_ws_send.go │ │ │ │ │ ├── rflowv2_parity_test.go │ │ │ │ │ ├── rflowv2_sync_zero_value_test.go │ │ │ │ │ ├── rflowv2_test.go │ │ │ │ │ ├── rflowv2_testutil_test.go │ │ │ │ │ ├── rflowv2_variable.go │ │ │ │ │ ├── rflowv2_variable_transaction_test.go │ │ │ │ │ ├── rflowv2_version.go │ │ │ │ │ ├── simple_import_test.go │ │ │ │ │ └── sync_robustness_test.go │ │ │ │ ├── rgraphql/ │ │ │ │ │ ├── rgraphql.go │ │ │ │ │ ├── rgraphql_converter.go │ │ │ │ │ ├── rgraphql_crud.go │ │ │ │ │ ├── rgraphql_crud_assert.go │ │ │ │ │ ├── rgraphql_crud_delta.go │ │ │ │ │ ├── rgraphql_crud_header.go │ │ │ │ │ ├── rgraphql_crud_header_delta.go │ │ │ │ │ ├── rgraphql_crud_response.go │ │ │ │ │ ├── rgraphql_crud_response_assert.go │ │ │ │ │ ├── rgraphql_crud_version.go │ │ │ │ │ ├── rgraphql_delta_converter.go │ │ │ │ │ ├── rgraphql_exec.go │ │ │ │ │ ├── rgraphql_exec_assert.go │ │ │ │ │ └── rgraphql_exec_assert_test.go │ │ │ │ ├── rhealth/ │ │ │ │ │ ├── rhealth.go │ │ │ │ │ └── rhealth_test.go │ │ │ │ ├── rhttp/ │ │ │ │ │ ├── logging_test.go │ │ │ │ │ ├── rhttp.go │ │ │ │ │ ├── rhttp_body_kind_test.go │ │ │ │ │ ├── rhttp_common.go │ │ │ │ │ ├── rhttp_converter.go │ │ │ │ │ ├── rhttp_crud.go │ │ │ │ │ ├── rhttp_crud_assert.go │ │ │ │ │ ├── rhttp_crud_assert_rpc_test.go │ │ │ │ │ ├── rhttp_crud_body.go │ │ │ │ │ ├── rhttp_crud_body_rpc_test.go │ │ │ │ │ ├── rhttp_crud_header.go │ │ │ │ │ ├── rhttp_crud_param.go │ │ │ │ │ ├── rhttp_crud_param_rpc_test.go │ │ │ │ │ ├── rhttp_crud_response.go │ │ │ │ │ ├── rhttp_crud_rpc_test.go │ │ │ │ │ ├── rhttp_default_test.go │ │ │ │ │ ├── rhttp_delta_assert.go │ │ │ │ │ ├── rhttp_delta_body_raw.go │ │ │ │ │ ├── rhttp_delta_body_raw_test.go │ │ │ │ │ ├── rhttp_delta_body_structured.go │ │ │ │ │ ├── rhttp_delta_body_structured_test.go │ │ │ │ │ ├── rhttp_delta_child_test.go │ │ │ │ │ ├── rhttp_delta_collection_test.go │ │ │ │ │ ├── rhttp_delta_header.go │ │ │ │ │ ├── rhttp_delta_header_test.go │ │ │ │ │ ├── rhttp_delta_param.go │ │ │ │ │ ├── rhttp_delta_param_test.go │ │ │ │ │ ├── rhttp_delta_request.go │ │ │ │ │ ├── rhttp_delta_test.go │ │ │ │ │ ├── rhttp_exec.go │ │ │ │ │ ├── rhttp_exec_delta_test.go │ │ │ │ │ ├── rhttp_id_test.go │ │ │ │ │ ├── rhttp_integration_test.go │ │ │ │ │ ├── rhttp_run_sync_parity_test.go │ │ │ │ │ ├── rhttp_run_sync_test.go │ │ │ │ │ ├── rhttp_run_version_test.go │ │ │ │ │ ├── rhttp_streaming_test.go │ │ │ │ │ ├── rhttp_sync_delta_test.go │ │ │ │ │ ├── rhttp_sync_parity_test.go │ │ │ │ │ ├── rhttp_sync_rpc_test.go │ │ │ │ │ ├── rhttp_test.go │ │ │ │ │ ├── rhttp_testutil_test.go │ │ │ │ │ ├── rhttp_transaction_test.go │ │ │ │ │ ├── rhttp_version.go │ │ │ │ │ ├── rhttp_version_concurrency_test.go │ │ │ │ │ ├── rhttp_version_fix_test.go │ │ │ │ │ └── rhttp_version_snapshot_test.go │ │ │ │ ├── rimportv2/ │ │ │ │ │ ├── concurrency_stress_test.go │ │ │ │ │ ├── dedup.go │ │ │ │ │ ├── dedup_test.go │ │ │ │ │ ├── duplicate_import_test.go │ │ │ │ │ ├── edge_cases_test.go │ │ │ │ │ ├── env_sync_test.go │ │ │ │ │ ├── file_collision_test.go │ │ │ │ │ ├── format_detection.go │ │ │ │ │ ├── fuzz_test.go │ │ │ │ │ ├── integration_test.go │ │ │ │ │ ├── integration_unified_test.go │ │ │ │ │ ├── integrity.go │ │ │ │ │ ├── integrity_test.go │ │ │ │ │ ├── performance_test.go │ │ │ │ │ ├── postman_stuck_test.go │ │ │ │ │ ├── realhar_test.go │ │ │ │ │ ├── realyaml_test.go │ │ │ │ │ ├── rimportv2.go │ │ │ │ │ ├── rimportv2_convert.go │ │ │ │ │ ├── rimportv2_delta_e2e_test.go │ │ │ │ │ ├── rimportv2_domain.go │ │ │ │ │ ├── rimportv2_event.go │ │ │ │ │ ├── rimportv2_test.go │ │ │ │ │ ├── rimportv2_translator.go │ │ │ │ │ ├── service.go │ │ │ │ │ ├── storage.go │ │ │ │ │ ├── sync_parity_test.go │ │ │ │ │ ├── sync_parity_yaml_test.go │ │ │ │ │ ├── testdata/ │ │ │ │ │ │ ├── ecommerce.yaml │ │ │ │ │ │ └── uuid.har │ │ │ │ │ ├── toposort.go │ │ │ │ │ ├── toposort_test.go │ │ │ │ │ ├── translators.go │ │ │ │ │ ├── unified_import_test.go │ │ │ │ │ ├── urlfetch.go │ │ │ │ │ ├── urlfetch_test.go │ │ │ │ │ └── validation.go │ │ │ │ ├── rlog/ │ │ │ │ │ ├── rlog.go │ │ │ │ │ └── rlog_test.go │ │ │ │ ├── rreference/ │ │ │ │ │ ├── rreference.go │ │ │ │ │ ├── rreference_context.go │ │ │ │ │ ├── rreference_execution.go │ │ │ │ │ ├── rreference_flow_context.go │ │ │ │ │ ├── rreference_integration_test.go │ │ │ │ │ ├── rreference_node_schema.go │ │ │ │ │ ├── rreference_response.go │ │ │ │ │ ├── rreference_rpc_test.go │ │ │ │ │ ├── rreference_test.go │ │ │ │ │ ├── rreference_tree.go │ │ │ │ │ └── rreference_unit_test.go │ │ │ │ ├── ruser/ │ │ │ │ │ ├── ruser.go.disabled │ │ │ │ │ └── ruser_test.go.disabled │ │ │ │ ├── rwebsocket/ │ │ │ │ │ ├── rwebsocket.go │ │ │ │ │ └── rwebsocket_proxy.go │ │ │ │ └── rworkspace/ │ │ │ │ ├── rworkspace.go │ │ │ │ └── rworkspace_test.go │ │ │ ├── converter/ │ │ │ │ ├── ai_converter_test.go │ │ │ │ ├── converter.go │ │ │ │ └── converter_test.go │ │ │ ├── migrate/ │ │ │ │ ├── README.md │ │ │ │ ├── backup.go │ │ │ │ ├── checkpoint.go │ │ │ │ ├── cursor_context.go │ │ │ │ ├── metadata.go │ │ │ │ ├── metadata_test.go │ │ │ │ ├── registry.go │ │ │ │ ├── registry_test.go │ │ │ │ ├── runner.go │ │ │ │ ├── runner_test.go │ │ │ │ └── types.go │ │ │ └── migrations/ │ │ │ ├── 01KFF093_add_ai_tables.go │ │ │ ├── 01KGTFDM_add_external_id.go │ │ │ ├── 01KGZC9E_add_http_is_snapshot.go │ │ │ ├── 01KH1AZ5_enforce_delta_snapshot_exclusivity.go │ │ │ ├── 01KHDYWX_add_graphql_tables.go │ │ │ ├── 01KJSYH5_add_flow_error.go │ │ │ ├── 01KJZMR5_add_flow_node_wait.go │ │ │ ├── 01KK31SQ_add_graphql_delta_file_kind.go │ │ │ ├── 01KK7EDJ_add_sub_flow_tables.go │ │ │ ├── 01KKFQT8_add_websocket_tables.go │ │ │ ├── migrations.go │ │ │ └── migrations_test.go │ │ ├── package.json │ │ ├── pkg/ │ │ │ ├── authadapter/ │ │ │ │ ├── account.go │ │ │ │ ├── adapter.go │ │ │ │ ├── adapter_test.go │ │ │ │ ├── dynquery.go │ │ │ │ ├── jwks.go │ │ │ │ ├── model.go │ │ │ │ ├── session.go │ │ │ │ ├── user.go │ │ │ │ └── verification.go │ │ │ ├── compress/ │ │ │ │ ├── compress.go │ │ │ │ └── compress_test.go │ │ │ ├── contenthash/ │ │ │ │ └── hasher.go │ │ │ ├── credvault/ │ │ │ │ ├── encryption_type.go │ │ │ │ ├── vault.go │ │ │ │ └── vault_test.go │ │ │ ├── dbtime/ │ │ │ │ └── dbtime.go │ │ │ ├── delta/ │ │ │ │ ├── delta.go │ │ │ │ └── delta_test.go │ │ │ ├── depfinder/ │ │ │ │ ├── depfinder.go │ │ │ │ └── depfinder_test.go │ │ │ ├── errmap/ │ │ │ │ ├── errmap.go │ │ │ │ └── errmap_test.go │ │ │ ├── eventstream/ │ │ │ │ ├── bridge.go │ │ │ │ ├── bridge_bulk_test.go │ │ │ │ ├── bridge_test.go │ │ │ │ ├── bulk_logic.txt │ │ │ │ ├── bulk_test.go │ │ │ │ ├── eventstream.go │ │ │ │ ├── eventstream_test.go │ │ │ │ └── memory/ │ │ │ │ └── memory.go │ │ │ ├── eventsync/ │ │ │ │ ├── batch.go │ │ │ │ ├── batch_test.go │ │ │ │ ├── kind.go │ │ │ │ ├── node_order.go │ │ │ │ ├── node_order_test.go │ │ │ │ ├── order.go │ │ │ │ └── order_test.go │ │ │ ├── expression/ │ │ │ │ ├── builtins.go │ │ │ │ ├── builtins_test.go │ │ │ │ ├── errors.go │ │ │ │ ├── eval.go │ │ │ │ ├── expression.go │ │ │ │ ├── expression_test.go │ │ │ │ ├── expression_tracking_test.go │ │ │ │ ├── file_utils.go │ │ │ │ ├── interpolate.go │ │ │ │ ├── path_resolver.go │ │ │ │ ├── unified_env.go │ │ │ │ └── unified_env_test.go │ │ │ ├── flow/ │ │ │ │ ├── flowbuilder/ │ │ │ │ │ ├── builder.go │ │ │ │ │ └── subflow_executor.go │ │ │ │ ├── flowexec/ │ │ │ │ │ ├── session.go │ │ │ │ │ ├── session_test.go │ │ │ │ │ ├── snapshot.go │ │ │ │ │ └── snapshot_test.go │ │ │ │ ├── flowresult/ │ │ │ │ │ ├── drain.go │ │ │ │ │ ├── noop.go │ │ │ │ │ ├── processor.go │ │ │ │ │ ├── publisher.go │ │ │ │ │ └── statetracker.go │ │ │ │ ├── node/ │ │ │ │ │ ├── entry.go │ │ │ │ │ ├── mocknode/ │ │ │ │ │ │ └── mocknode.go │ │ │ │ │ ├── nai/ │ │ │ │ │ │ ├── aiexpr.go │ │ │ │ │ │ ├── aiparam.go │ │ │ │ │ │ ├── benchmarks/ │ │ │ │ │ │ │ ├── 2026-01-26/ │ │ │ │ │ │ │ │ ├── claude-sonnet-4-5.json │ │ │ │ │ │ │ │ ├── gemini-2.5-pro.json │ │ │ │ │ │ │ │ ├── gpt-5.2.json │ │ │ │ │ │ │ │ └── summary.md │ │ │ │ │ │ │ └── README.md │ │ │ │ │ │ ├── bridge.go │ │ │ │ │ │ ├── bridge_test.go │ │ │ │ │ │ ├── execution.go │ │ │ │ │ │ ├── integration_benchmark_test.go │ │ │ │ │ │ ├── integration_custom_test.go │ │ │ │ │ │ ├── integration_flow_orchestration_test.go │ │ │ │ │ │ ├── integration_poc_advanced_test.go │ │ │ │ │ │ ├── integration_poc_comparison_test.go │ │ │ │ │ │ ├── integration_providers_test.go │ │ │ │ │ │ ├── integration_scoring_test.go │ │ │ │ │ │ ├── integration_setup_test.go │ │ │ │ │ │ ├── integration_tools_http_test.go │ │ │ │ │ │ ├── integration_tools_node_test.go │ │ │ │ │ │ ├── nai.go │ │ │ │ │ │ ├── nai_test.go │ │ │ │ │ │ ├── provider.go │ │ │ │ │ │ ├── tools.go │ │ │ │ │ │ └── tools_test.go │ │ │ │ │ ├── naiprovider/ │ │ │ │ │ │ ├── metrics.go │ │ │ │ │ │ ├── metrics_test.go │ │ │ │ │ │ ├── naiprovider.go │ │ │ │ │ │ └── naiprovider_test.go │ │ │ │ │ ├── nfor/ │ │ │ │ │ │ ├── nfor.go │ │ │ │ │ │ └── nfor_test.go │ │ │ │ │ ├── nforeach/ │ │ │ │ │ │ ├── nforeach.go │ │ │ │ │ │ └── nforeach_test.go │ │ │ │ │ ├── ngraphql/ │ │ │ │ │ │ └── ngraphql.go │ │ │ │ │ ├── nif/ │ │ │ │ │ │ ├── nif.go │ │ │ │ │ │ └── nif_test.go │ │ │ │ │ ├── njs/ │ │ │ │ │ │ ├── njs.go │ │ │ │ │ │ └── njs_test.go │ │ │ │ │ ├── nmemory/ │ │ │ │ │ │ ├── nmemory.go │ │ │ │ │ │ └── nmemory_test.go │ │ │ │ │ ├── node.go │ │ │ │ │ ├── node_test.go │ │ │ │ │ ├── node_tracking_test.go │ │ │ │ │ ├── nrequest/ │ │ │ │ │ │ ├── nrequest.go │ │ │ │ │ │ └── nrequest_test.go │ │ │ │ │ ├── nrunsubflow/ │ │ │ │ │ │ └── nrunsubflow.go │ │ │ │ │ ├── nstart/ │ │ │ │ │ │ └── nstart.go │ │ │ │ │ ├── nsubflowreturn/ │ │ │ │ │ │ └── nsubflowreturn.go │ │ │ │ │ ├── nsubflowtrigger/ │ │ │ │ │ │ └── nsubflowtrigger.go │ │ │ │ │ ├── nwait/ │ │ │ │ │ │ └── nwait.go │ │ │ │ │ ├── nwsconnection/ │ │ │ │ │ │ ├── nwsconnection.go │ │ │ │ │ │ └── nwsconnection_test.go │ │ │ │ │ └── nwssend/ │ │ │ │ │ ├── nwssend.go │ │ │ │ │ └── nwssend_test.go │ │ │ │ ├── runner/ │ │ │ │ │ ├── cancel.go │ │ │ │ │ ├── flowlocalrunner/ │ │ │ │ │ │ ├── executor.go │ │ │ │ │ │ ├── flowlocalrunner.go │ │ │ │ │ │ ├── flowlocalrunner_inputdata_test.go │ │ │ │ │ │ ├── flowlocalrunner_test.go │ │ │ │ │ │ ├── helpers.go │ │ │ │ │ │ ├── mode_select.go │ │ │ │ │ │ ├── strategy_multi.go │ │ │ │ │ │ └── strategy_single.go │ │ │ │ │ ├── graph.go │ │ │ │ │ ├── loop_test.go │ │ │ │ │ ├── runner.go │ │ │ │ │ └── status.go │ │ │ │ ├── simulation/ │ │ │ │ │ ├── mockflows.go │ │ │ │ │ └── mockflows_test.go │ │ │ │ └── tracking/ │ │ │ │ ├── env_wrapper.go │ │ │ │ ├── tracker.go │ │ │ │ ├── tracker_race_test.go │ │ │ │ ├── tracker_test.go │ │ │ │ ├── tracker_tree_test.go │ │ │ │ ├── tree_builder.go │ │ │ │ └── tree_builder_test.go │ │ │ ├── flowgraph/ │ │ │ │ ├── flowgraph.go │ │ │ │ ├── flowgraph_test.go │ │ │ │ ├── layout.go │ │ │ │ └── reduction.go │ │ │ ├── fuzzyfinder/ │ │ │ │ ├── fuzzyfinder.go │ │ │ │ └── fuzzyfinder_test.go │ │ │ ├── graphql/ │ │ │ │ ├── resolver/ │ │ │ │ │ └── resolver.go │ │ │ │ └── response/ │ │ │ │ └── response.go │ │ │ ├── http/ │ │ │ │ ├── request/ │ │ │ │ │ ├── body_tracking_test.go │ │ │ │ │ ├── content_type_test.go │ │ │ │ │ ├── request.go │ │ │ │ │ ├── request_test.go │ │ │ │ │ ├── request_tracking_test.go │ │ │ │ │ └── variable_substitution_test.go │ │ │ │ ├── resolver/ │ │ │ │ │ ├── resolver.go │ │ │ │ │ └── resolver_test.go │ │ │ │ └── response/ │ │ │ │ ├── response.go │ │ │ │ └── response_test.go │ │ │ ├── httpclient/ │ │ │ │ ├── charset_test.go │ │ │ │ ├── httpclient.go │ │ │ │ ├── httpclient_test.go │ │ │ │ └── httpmockclient/ │ │ │ │ └── httpmockclient.go │ │ │ ├── idwrap/ │ │ │ │ ├── idwrap.go │ │ │ │ └── idwrap_test.go │ │ │ ├── ioworkspace/ │ │ │ │ ├── doc.go │ │ │ │ ├── errors.go │ │ │ │ ├── exporter.go │ │ │ │ ├── exporter_test.go │ │ │ │ ├── har_delta_body_test.go │ │ │ │ ├── importer.go │ │ │ │ ├── importer_env.go │ │ │ │ ├── importer_file.go │ │ │ │ ├── importer_flow.go │ │ │ │ ├── importer_http.go │ │ │ │ ├── ioworkspace_test.go │ │ │ │ ├── layout.go │ │ │ │ ├── layout_test.go │ │ │ │ ├── service.go │ │ │ │ └── types.go │ │ │ ├── llm/ │ │ │ │ ├── convert.go │ │ │ │ ├── convert_test.go │ │ │ │ ├── llm.go │ │ │ │ ├── message.go │ │ │ │ └── tool.go │ │ │ ├── logconsole/ │ │ │ │ └── logconsole.go │ │ │ ├── logger/ │ │ │ │ └── mocklogger/ │ │ │ │ └── mocklogger.go │ │ │ ├── model/ │ │ │ │ ├── mcondition/ │ │ │ │ │ └── mcondition.go │ │ │ │ ├── mcredential/ │ │ │ │ │ └── mcredential.go │ │ │ │ ├── menv/ │ │ │ │ │ ├── menv.go │ │ │ │ │ └── variable.go │ │ │ │ ├── mfile/ │ │ │ │ │ ├── mfile.go │ │ │ │ │ └── mfile_test.go │ │ │ │ ├── mflow/ │ │ │ │ │ ├── edge.go │ │ │ │ │ ├── execution.go │ │ │ │ │ ├── mflow.go │ │ │ │ │ ├── node.go │ │ │ │ │ ├── node_sub_flow.go │ │ │ │ │ ├── node_test.go │ │ │ │ │ ├── node_types.go │ │ │ │ │ ├── tag.go │ │ │ │ │ └── variable.go │ │ │ │ ├── mgraphql/ │ │ │ │ │ └── mgraphql.go │ │ │ │ ├── mhttp/ │ │ │ │ │ └── mhttp.go │ │ │ │ ├── mtag/ │ │ │ │ │ └── mtag.go │ │ │ │ ├── muser/ │ │ │ │ │ └── muser.go │ │ │ │ ├── mwebsocket/ │ │ │ │ │ └── mwebsocket.go │ │ │ │ ├── mworkspace/ │ │ │ │ │ ├── mworkspace.go │ │ │ │ │ └── user.go │ │ │ │ ├── postman/ │ │ │ │ │ └── v21/ │ │ │ │ │ ├── mauth/ │ │ │ │ │ │ └── mauth.go │ │ │ │ │ ├── mbody/ │ │ │ │ │ │ └── mbody.go │ │ │ │ │ ├── mcookie/ │ │ │ │ │ │ └── mcookie.go │ │ │ │ │ ├── mevent/ │ │ │ │ │ │ └── mevent.go │ │ │ │ │ ├── mheader/ │ │ │ │ │ │ └── mheader.go │ │ │ │ │ ├── mitem/ │ │ │ │ │ │ └── mitem.go │ │ │ │ │ ├── mpostmancollection/ │ │ │ │ │ │ └── mpostmancollection.go │ │ │ │ │ ├── mrequest/ │ │ │ │ │ │ └── mrequest.go │ │ │ │ │ ├── mresponse/ │ │ │ │ │ │ └── mresponse.go │ │ │ │ │ ├── murl/ │ │ │ │ │ │ └── murl.go │ │ │ │ │ └── mvariable/ │ │ │ │ │ └── mvariable.go │ │ │ │ └── result/ │ │ │ │ └── mresultapi/ │ │ │ │ └── mresultapi.go │ │ │ ├── mutation/ │ │ │ │ ├── context.go │ │ │ │ ├── delete_environment.go │ │ │ │ ├── delete_file.go │ │ │ │ ├── delete_flow.go │ │ │ │ ├── delete_graphql.go │ │ │ │ ├── delete_http.go │ │ │ │ ├── delete_workspace.go │ │ │ │ ├── event.go │ │ │ │ ├── insert_credential.go │ │ │ │ ├── insert_graphql.go │ │ │ │ ├── insert_http.go │ │ │ │ ├── mutation_test.go │ │ │ │ ├── publish.go │ │ │ │ ├── replay_dev.go │ │ │ │ ├── replay_prod.go │ │ │ │ ├── update_graphql.go │ │ │ │ └── update_http.go │ │ │ ├── patch/ │ │ │ │ ├── optional.go │ │ │ │ ├── optional_test.go │ │ │ │ ├── patch.go │ │ │ │ └── patch_test.go │ │ │ ├── permcheck/ │ │ │ │ └── permcheck.go │ │ │ ├── reference/ │ │ │ │ ├── reference.go │ │ │ │ ├── reference_conversion_test.go │ │ │ │ ├── reference_enum_test.go │ │ │ │ └── reference_test.go │ │ │ ├── referencecompletion/ │ │ │ │ ├── referencecompletion.go │ │ │ │ └── referencecompletion_test.go │ │ │ ├── service/ │ │ │ │ ├── scredential/ │ │ │ │ │ ├── credential_mapper.go │ │ │ │ │ ├── credential_mapper_test.go │ │ │ │ │ ├── llm_provider.go │ │ │ │ │ ├── llm_provider_test.go │ │ │ │ │ ├── reader.go │ │ │ │ │ ├── scredential.go │ │ │ │ │ ├── scredential_test.go │ │ │ │ │ └── writer.go │ │ │ │ ├── senv/ │ │ │ │ │ ├── env.go │ │ │ │ │ ├── env_mapper.go │ │ │ │ │ ├── env_reader.go │ │ │ │ │ ├── env_writer.go │ │ │ │ │ ├── variable.go │ │ │ │ │ ├── variable_mapper.go │ │ │ │ │ ├── variable_reader.go │ │ │ │ │ └── variable_writer.go │ │ │ │ ├── sfile/ │ │ │ │ │ ├── mapper.go │ │ │ │ │ ├── reader.go │ │ │ │ │ ├── sfile.go │ │ │ │ │ ├── sfile_delta_test.go │ │ │ │ │ ├── sfile_test.go │ │ │ │ │ └── writer.go │ │ │ │ ├── sflow/ │ │ │ │ │ ├── edge.go │ │ │ │ │ ├── edge_mapper.go │ │ │ │ │ ├── edge_reader.go │ │ │ │ │ ├── edge_writer.go │ │ │ │ │ ├── edge_writer_test.go │ │ │ │ │ ├── flow.go │ │ │ │ │ ├── flow_mapper.go │ │ │ │ │ ├── flow_reader.go │ │ │ │ │ ├── flow_writer.go │ │ │ │ │ ├── node.go │ │ │ │ │ ├── node_ai.go │ │ │ │ │ ├── node_ai_mapper.go │ │ │ │ │ ├── node_ai_mapper_test.go │ │ │ │ │ ├── node_ai_provider.go │ │ │ │ │ ├── node_ai_provider_mapper.go │ │ │ │ │ ├── node_ai_provider_reader.go │ │ │ │ │ ├── node_ai_provider_test.go │ │ │ │ │ ├── node_ai_provider_writer.go │ │ │ │ │ ├── node_ai_reader.go │ │ │ │ │ ├── node_ai_writer.go │ │ │ │ │ ├── node_execution.go │ │ │ │ │ ├── node_execution_mapper.go │ │ │ │ │ ├── node_execution_reader.go │ │ │ │ │ ├── node_execution_writer.go │ │ │ │ │ ├── node_for.go │ │ │ │ │ ├── node_for_mapper.go │ │ │ │ │ ├── node_for_reader.go │ │ │ │ │ ├── node_for_writer.go │ │ │ │ │ ├── node_foreach.go │ │ │ │ │ ├── node_foreach_mapper.go │ │ │ │ │ ├── node_foreach_reader.go │ │ │ │ │ ├── node_foreach_writer.go │ │ │ │ │ ├── node_graphql.go │ │ │ │ │ ├── node_graphql_mapper.go │ │ │ │ │ ├── node_graphql_reader.go │ │ │ │ │ ├── node_graphql_writer.go │ │ │ │ │ ├── node_if.go │ │ │ │ │ ├── node_if_mapper.go │ │ │ │ │ ├── node_if_reader.go │ │ │ │ │ ├── node_if_writer.go │ │ │ │ │ ├── node_javascript.go │ │ │ │ │ ├── node_javascript_mapper.go │ │ │ │ │ ├── node_javascript_reader.go │ │ │ │ │ ├── node_javascript_writer.go │ │ │ │ │ ├── node_mapper.go │ │ │ │ │ ├── node_memory.go │ │ │ │ │ ├── node_memory_mapper.go │ │ │ │ │ ├── node_memory_reader.go │ │ │ │ │ ├── node_memory_test.go │ │ │ │ │ ├── node_memory_writer.go │ │ │ │ │ ├── node_reader.go │ │ │ │ │ ├── node_readers.go │ │ │ │ │ ├── node_request.go │ │ │ │ │ ├── node_request_mapper.go │ │ │ │ │ ├── node_request_reader.go │ │ │ │ │ ├── node_request_writer.go │ │ │ │ │ ├── node_run_sub_flow.go │ │ │ │ │ ├── node_run_sub_flow_mapper.go │ │ │ │ │ ├── node_run_sub_flow_reader.go │ │ │ │ │ ├── node_run_sub_flow_writer.go │ │ │ │ │ ├── node_sub_flow_return.go │ │ │ │ │ ├── node_sub_flow_return_mapper.go │ │ │ │ │ ├── node_sub_flow_return_reader.go │ │ │ │ │ ├── node_sub_flow_return_writer.go │ │ │ │ │ ├── node_sub_flow_trigger.go │ │ │ │ │ ├── node_sub_flow_trigger_mapper.go │ │ │ │ │ ├── node_sub_flow_trigger_reader.go │ │ │ │ │ ├── node_sub_flow_trigger_writer.go │ │ │ │ │ ├── node_wait.go │ │ │ │ │ ├── node_wait_mapper.go │ │ │ │ │ ├── node_wait_reader.go │ │ │ │ │ ├── node_wait_writer.go │ │ │ │ │ ├── node_writer.go │ │ │ │ │ ├── node_writer_test.go │ │ │ │ │ ├── node_ws_connection.go │ │ │ │ │ ├── node_ws_connection_mapper.go │ │ │ │ │ ├── node_ws_connection_reader.go │ │ │ │ │ ├── node_ws_connection_writer.go │ │ │ │ │ ├── node_ws_send.go │ │ │ │ │ ├── node_ws_send_mapper.go │ │ │ │ │ ├── node_ws_send_reader.go │ │ │ │ │ ├── node_ws_send_writer.go │ │ │ │ │ ├── tag.go │ │ │ │ │ ├── tag_mapper.go │ │ │ │ │ ├── tag_reader.go │ │ │ │ │ ├── tag_writer.go │ │ │ │ │ ├── variable.go │ │ │ │ │ ├── variable_mapper.go │ │ │ │ │ ├── variable_reader.go │ │ │ │ │ └── variable_writer.go │ │ │ │ ├── sgraphql/ │ │ │ │ │ ├── assert.go │ │ │ │ │ ├── header.go │ │ │ │ │ ├── mapper.go │ │ │ │ │ ├── reader.go │ │ │ │ │ ├── response.go │ │ │ │ │ ├── sgraphql.go │ │ │ │ │ └── writer.go │ │ │ │ ├── shttp/ │ │ │ │ │ ├── assert.go │ │ │ │ │ ├── assert_reader.go │ │ │ │ │ ├── assert_test.go │ │ │ │ │ ├── assert_writer.go │ │ │ │ │ ├── body_form.go │ │ │ │ │ ├── body_form_reader.go │ │ │ │ │ ├── body_form_writer.go │ │ │ │ │ ├── body_form_writer_test.go │ │ │ │ │ ├── body_raw.go │ │ │ │ │ ├── body_raw_reader.go │ │ │ │ │ ├── body_raw_writer.go │ │ │ │ │ ├── body_test.go │ │ │ │ │ ├── body_urlencoded.go │ │ │ │ │ ├── body_urlencoded_reader.go │ │ │ │ │ ├── body_urlencoded_writer.go │ │ │ │ │ ├── body_urlencoded_writer_test.go │ │ │ │ │ ├── header.go │ │ │ │ │ ├── header_reader.go │ │ │ │ │ ├── header_test.go │ │ │ │ │ ├── header_writer.go │ │ │ │ │ ├── header_writer_test.go │ │ │ │ │ ├── http.go │ │ │ │ │ ├── http_children.go │ │ │ │ │ ├── http_test.go │ │ │ │ │ ├── mapper.go │ │ │ │ │ ├── reader.go │ │ │ │ │ ├── response.go │ │ │ │ │ ├── response_reader.go │ │ │ │ │ ├── response_writer.go │ │ │ │ │ ├── search_param.go │ │ │ │ │ ├── search_param_reader.go │ │ │ │ │ ├── search_param_test.go │ │ │ │ │ ├── search_param_writer.go │ │ │ │ │ ├── search_param_writer_test.go │ │ │ │ │ ├── shttp.test │ │ │ │ │ ├── utils.go │ │ │ │ │ └── writer.go │ │ │ │ ├── stag/ │ │ │ │ │ ├── mapper.go │ │ │ │ │ ├── reader.go │ │ │ │ │ ├── stag.go │ │ │ │ │ └── writer.go │ │ │ │ ├── suser/ │ │ │ │ │ ├── reader.go │ │ │ │ │ ├── suser.go │ │ │ │ │ └── writer.go │ │ │ │ ├── swebsocket/ │ │ │ │ │ ├── header.go │ │ │ │ │ ├── mapper.go │ │ │ │ │ └── swebsocket.go │ │ │ │ └── sworkspace/ │ │ │ │ ├── sworkspace_test.go │ │ │ │ ├── user.go │ │ │ │ ├── user_mapper.go │ │ │ │ ├── user_reader.go │ │ │ │ ├── user_writer.go │ │ │ │ ├── workspace.go │ │ │ │ ├── workspace_mapper.go │ │ │ │ ├── workspace_reader.go │ │ │ │ └── workspace_writer.go │ │ │ ├── sort/ │ │ │ │ └── sortenabled/ │ │ │ │ ├── sortenabled.go │ │ │ │ └── sortenabled_test.go │ │ │ ├── stoken/ │ │ │ │ ├── stoken.go │ │ │ │ └── stoken_test.go │ │ │ ├── streamregistry/ │ │ │ │ ├── registry.go │ │ │ │ └── registry_test.go │ │ │ ├── streamtest/ │ │ │ │ ├── helpers.go │ │ │ │ ├── verifier.go │ │ │ │ └── verifier_test.go │ │ │ ├── testutil/ │ │ │ │ ├── concurrency.go │ │ │ │ ├── sync_parity.go │ │ │ │ ├── sync_zero_value.go │ │ │ │ └── testutil.go │ │ │ ├── translate/ │ │ │ │ ├── harv2/ │ │ │ │ │ ├── benchmark_test.go │ │ │ │ │ ├── delta.go │ │ │ │ │ ├── harv2.go │ │ │ │ │ ├── harv2_delta_test.go │ │ │ │ │ ├── harv2_dependency_unit_test.go │ │ │ │ │ ├── harv2_layout_test.go │ │ │ │ │ ├── harv2_test.go │ │ │ │ │ ├── integration_test.go │ │ │ │ │ └── request.go │ │ │ │ ├── tcurlv2/ │ │ │ │ │ ├── tcurlv2.go │ │ │ │ │ └── tcurlv2_test.go │ │ │ │ ├── tgeneric/ │ │ │ │ │ └── tgeneric.go │ │ │ │ ├── topenapiv2/ │ │ │ │ │ ├── deterministic_test.go │ │ │ │ │ ├── real_world_test.go │ │ │ │ │ ├── topenapiv2.go │ │ │ │ │ └── topenapiv2_test.go │ │ │ │ ├── tpostmanv2/ │ │ │ │ │ ├── integration_test.go │ │ │ │ │ ├── mappers.go │ │ │ │ │ ├── real_world_test.go │ │ │ │ │ ├── tpostmanv2.go │ │ │ │ │ └── tpostmanv2_test.go │ │ │ │ └── yamlflowsimplev2/ │ │ │ │ ├── README.md │ │ │ │ ├── converter.go │ │ │ │ ├── converter_flow.go │ │ │ │ ├── converter_node.go │ │ │ │ ├── converter_template.go │ │ │ │ ├── converter_test.go │ │ │ │ ├── exporter.go │ │ │ │ ├── exporter_test.go │ │ │ │ ├── reproduce_weird_test.go │ │ │ │ ├── types.go │ │ │ │ ├── utils.go │ │ │ │ └── utils_test.go │ │ │ ├── txutil/ │ │ │ │ ├── bulk_sync_tx.go │ │ │ │ ├── bulk_sync_tx_test.go │ │ │ │ └── sync_tx.go │ │ │ ├── varsystem/ │ │ │ │ ├── varsystem.go │ │ │ │ ├── varsystem_test.go │ │ │ │ └── varsystem_tracker_test.go │ │ │ └── zstdcompress/ │ │ │ ├── zstdcompress.go │ │ │ └── zstdcompress_test.go │ │ ├── project.json │ │ ├── test/ │ │ │ ├── collection/ │ │ │ │ ├── FiveWayAutomateCollection.json │ │ │ │ ├── GalaxyCollection.json │ │ │ │ ├── IntroductionCollection.json │ │ │ │ ├── SimpleCollection.json │ │ │ │ └── collection_test.go │ │ │ ├── delta_execution_e2e_test.go │ │ │ ├── delta_header_e2e_test.go │ │ │ ├── e2e_har_to_cli_test.go │ │ │ ├── flow_execution_e2e_test.go │ │ │ ├── har_import_dep_test.go │ │ │ ├── har_import_e2e_test.go │ │ │ ├── har_import_sync_test.go │ │ │ ├── har_import_url_dep_test.go │ │ │ ├── openapi/ │ │ │ │ ├── petstore_swagger2.json │ │ │ │ └── stripe_openapi3.json │ │ │ └── testdata/ │ │ │ └── collection/ │ │ │ ├── FiveWayAutomateCollection.json │ │ │ └── SimpleCollection.json │ │ └── testing.md │ ├── spec/ │ │ ├── api/ │ │ │ ├── credential.tsp │ │ │ ├── environment.tsp │ │ │ ├── export.tsp │ │ │ ├── file-system.tsp │ │ │ ├── flow.tsp │ │ │ ├── graphql.tsp │ │ │ ├── health.tsp │ │ │ ├── http.tsp │ │ │ ├── import.tsp │ │ │ ├── log.tsp │ │ │ ├── main.tsp │ │ │ ├── private/ │ │ │ │ ├── auth-adapter.tsp │ │ │ │ └── node-js-executor.tsp │ │ │ ├── reference.tsp │ │ │ ├── user.tsp │ │ │ ├── websocket.tsp │ │ │ └── workspace.tsp │ │ ├── buf.gen.yaml │ │ ├── buf.yaml │ │ ├── eslint.config.ts │ │ ├── go.mod │ │ ├── go.sum │ │ ├── lib/ │ │ │ └── collect-file-descriptors.ts │ │ ├── package.json │ │ ├── project.json │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ └── tspconfig.yaml │ ├── ui/ │ │ ├── .storybook/ │ │ │ ├── main.ts │ │ │ ├── manager.ts │ │ │ └── preview.tsx │ │ ├── eslint.config.ts │ │ ├── package.json │ │ ├── project.json │ │ ├── src/ │ │ │ ├── add-button.stories.tsx │ │ │ ├── add-button.tsx │ │ │ ├── avatar.button.stories.tsx │ │ │ ├── avatar.stories.tsx │ │ │ ├── avatar.tsx │ │ │ ├── badge.stories.tsx │ │ │ ├── badge.tsx │ │ │ ├── button.as-link.stories.tsx │ │ │ ├── button.stories.tsx │ │ │ ├── button.tsx │ │ │ ├── checkbox.stories.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── field.tsx │ │ │ ├── file-drop-zone.tsx │ │ │ ├── focus-ring.tsx │ │ │ ├── icons.tsx │ │ │ ├── illustrations.tsx │ │ │ ├── index.tsx │ │ │ ├── json-tree.stories.tsx │ │ │ ├── json-tree.tsx │ │ │ ├── link.tsx │ │ │ ├── list-box.stories.tsx │ │ │ ├── list-box.tsx │ │ │ ├── menu.stories.tsx │ │ │ ├── menu.tsx │ │ │ ├── method-badge.stories.tsx │ │ │ ├── method-badge.tsx │ │ │ ├── modal.stories.tsx │ │ │ ├── modal.tsx │ │ │ ├── number-field.tsx │ │ │ ├── popover.tsx │ │ │ ├── primitives/ │ │ │ │ ├── index.tsx │ │ │ │ └── list-box.tsx │ │ │ ├── progress-bar.tsx │ │ │ ├── provider.tsx │ │ │ ├── radio-group.tsx │ │ │ ├── reorder.tsx │ │ │ ├── resizable-panel.tsx │ │ │ ├── select.stories.tsx │ │ │ ├── select.tsx │ │ │ ├── separator.tsx │ │ │ ├── spinner.stories.tsx │ │ │ ├── spinner.tsx │ │ │ ├── styles/ │ │ │ │ ├── colors/ │ │ │ │ │ ├── dark.css │ │ │ │ │ ├── index.css │ │ │ │ │ └── light.css │ │ │ │ └── index.css │ │ │ ├── table.tsx │ │ │ ├── tag-group.stories.tsx │ │ │ ├── tag-group.tsx │ │ │ ├── tailwind-literal.tsx │ │ │ ├── text-field.tsx │ │ │ ├── theme.tsx │ │ │ ├── toast.tsx │ │ │ ├── tree.stories.tsx │ │ │ ├── tree.tsx │ │ │ ├── utils/ │ │ │ │ └── link.tsx │ │ │ └── utils.tsx │ │ ├── tsconfig.json │ │ └── tsconfig.lib.json │ └── worker-js/ │ ├── eslint.config.ts │ ├── package.json │ ├── project.json │ ├── src/ │ │ ├── main.ts │ │ └── nodejs-executor.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── patches/ │ ├── @effect__platform-node-shared.patch │ ├── @nx__eslint.patch │ └── @react-stately__table.patch ├── pnpm-workspace.yaml ├── prettier.config.mjs ├── project.json ├── scoop.json ├── syncpack.config.mjs ├── taskfile.yaml ├── tools/ │ ├── benchmark/ │ │ ├── compare.go │ │ ├── go.mod │ │ ├── main.go │ │ ├── parser.go │ │ └── types.go │ ├── eslint/ │ │ ├── config.ts │ │ ├── eslint.config.ts │ │ ├── package.json │ │ ├── project.json │ │ ├── tsconfig.json │ │ └── tsconfig.lib.json │ ├── gha-scripts/ │ │ ├── eslint.config.ts │ │ ├── package.json │ │ ├── project.json │ │ ├── src/ │ │ │ ├── cli.ts │ │ │ └── repository.ts │ │ ├── tsconfig.json │ │ └── tsconfig.lib.json │ ├── go-tool/ │ │ ├── go.mod │ │ └── go.sum │ ├── modmigrate/ │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ ├── norawsql/ │ │ ├── cmd/ │ │ │ └── norawsql/ │ │ │ └── main.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── norawsql.go │ │ ├── norawsql_test.go │ │ └── testdata/ │ │ └── src/ │ │ └── rawsql/ │ │ └── rawsql.go │ ├── notxread/ │ │ ├── cmd/ │ │ │ └── notxread/ │ │ │ └── main.go │ │ ├── go.mod │ │ ├── go.sum │ │ ├── notxread.go │ │ ├── notxread_test.go │ │ └── testdata/ │ │ └── src/ │ │ └── txread/ │ │ ├── complex_test.go │ │ ├── original_bug_test.go │ │ └── txread.go │ ├── nx-release/ │ │ └── renderer.cjs │ ├── spec-lib/ │ │ ├── eslint.config.ts │ │ ├── package.json │ │ ├── project.json │ │ ├── src/ │ │ │ ├── ai-tools/ │ │ │ │ ├── emitter.tsx │ │ │ │ ├── field-schema.ts │ │ │ │ ├── index.ts │ │ │ │ ├── lib.ts │ │ │ │ └── main.tsp │ │ │ ├── common.ts │ │ │ ├── core/ │ │ │ │ ├── index.tsx │ │ │ │ └── main.tsp │ │ │ ├── protobuf/ │ │ │ │ ├── emitter.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── lib.ts │ │ │ │ └── main.tsp │ │ │ ├── tanstack-db/ │ │ │ │ ├── emitter.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── lib.ts │ │ │ │ └── main.tsp │ │ │ └── utils.ts │ │ ├── tsconfig.json │ │ └── tsconfig.lib.json │ └── storybook/ │ ├── .storybook/ │ │ ├── Introduction.mdx │ │ └── main.ts │ ├── eslint.config.ts │ ├── package.json │ ├── project.json │ ├── tsconfig.json │ └── tsconfig.lib.json ├── tsconfig.base.json └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # editorconfig.org root = true [*] charset = utf-8 end_of_line = lf indent_size = 2 indent_style = space max_line_length = 120 insert_final_newline = true trim_trailing_whitespace = true ================================================ FILE: .envrc ================================================ use flake ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.yaml ================================================ name: Report a bug description: Have you found a bug or issue? Create a bug report labels: ['bug'] body: - type: markdown attributes: value: | **Please do not report security vulnerabilities here, but contact us directly at [help@dev.tools](mailto:help@dev.tools) instead.** - type: checkboxes id: checklist attributes: label: Checklist options: - label: I have searched the repository issues and have not found a suitable solution or answer. required: true - label: I agree to the terms within the [Contributor Covenant Code of Conduct](https://github.com/the-dev-tools/dev-tools/blob/main/docs/CODE-OF-CONDUCT.md). required: true - type: textarea id: description attributes: label: Description description: Provide a clear and concise description of the issue, including what you expected to happen. validations: required: true - type: textarea id: reproduction attributes: label: Reproduction description: Detail the steps taken to reproduce this error, and whether this issue can be reproduced consistently or if it is intermittent. placeholder: | 1. Step 1... 2. Step 2... 3. ... validations: required: true - type: textarea id: additional-context attributes: label: Additional context description: Any other relevant information you think would be useful. validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Security vulnerability url: https://dev.tools/ about: Please contact us directly via our support email at help@dev.tools ================================================ FILE: .github/ISSUE_TEMPLATE/feature-request.yaml ================================================ name: Feature request description: Suggest an idea or a feature labels: ['feature request'] body: - type: checkboxes id: checklist attributes: label: Checklist options: - label: I have searched the repository issues and have not found a suitable solution or answer. required: true - label: I agree to the terms within the [Contributor Covenant Code of Conduct](https://github.com/the-dev-tools/dev-tools/blob/main/docs/CODE-OF-CONDUCT.md). required: true - type: textarea id: description attributes: label: Describe the problem you'd like to have solved description: A clear and concise description of what the problem is. placeholder: I'm always frustrated when... validations: required: true - type: textarea id: ideal-solution attributes: label: Describe the ideal solution description: A clear and concise description of what you want to happen. validations: required: true - type: textarea id: alternatives-and-workarounds attributes: label: Alternatives and current workarounds description: A clear and concise description of any alternatives you've considered or any workarounds that are currently in place. validations: required: false - type: textarea id: additional-context attributes: label: Additional context description: Add any other context or screenshots about the feature request here. validations: required: false ================================================ FILE: .github/actions/dependencies-unix/action.yaml ================================================ name: Setup Unix dependencies description: '' runs: using: composite steps: - uses: nixbuild/nix-quick-install-action@v30 with: nix_conf: | keep-env-derivations = true keep-outputs = true # Restore and save Nix store cache - uses: nix-community/cache-nix-action@v6 with: # Restore and save a cache using this key primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} # If there's no cache hit, restore a cache by this prefix restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }}- # Do purge caches purge: true # Purge all versions of the cache purge-prefixes: build-${{ runner.os }}-${{ runner.arch }}- # Created more than 0 seconds ago relative to the start of the `Post Restore` phase purge-created: 0 # Except the version with the `primary-key`, if it exists purge-primary-key: never # And collect garbage in the Nix store until it reaches this size in bytes gc-max-store-size: 0 # Save flake attributes from garbage collection - shell: bash run: nix profile install .#gha-save-from-gc - shell: bash run: nix run .#gha-nix-develop -- .#runner ================================================ FILE: .github/actions/dependencies-windows/action.yaml ================================================ name: Setup Windows dependencies description: '' runs: using: composite steps: - id: cache uses: actions/cache@v4 with: path: ~\scoop key: scoop-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.\scoop.json') }} - uses: MinoruSekine/setup-scoop@v4.0.1 with: install_scoop: ${{ steps.cache.outputs.cache-hit != 'true' }} scoop_update: false - shell: powershell run: scoop import .\scoop.json # Export additional paths which the setup action does not account for # # Why in reverse? Appending a directory to $GITHUB_PATH causes that # directory to be prepended to $PATH. Thus we preserve their order by # reversing them before they are reversed again - shell: powershell run: | scoop list | %{ scoop info $_.Name --verbose } | %{ $_."Path Added" -Split "`n" } | Where { $_ } | &{ [Collections.Stack]@($input) } | Out-File -FilePath $env:GITHUB_PATH -Encoding ascii -Force - shell: powershell run: scoop shim add gha-scripts pnpm '--' run --filter="*/gha-scripts" cli - shell: powershell run: | Invoke-WebRequest ` https://github.com/vcsjones/AzureSignTool/releases/download/v6.0.1/AzureSignTool-x64.exe ` -OutFile $env:RUNNER_TEMP/AzureSignTool.exe - shell: powershell run: echo $env:RUNNER_TEMP >> $env:GITHUB_PATH ================================================ FILE: .github/actions/setup/action.yaml ================================================ name: Setup runner description: '' inputs: shell: description: INTERNAL default: ${{ runner.os == 'Windows' && 'powershell' || 'bash' }} runs: using: composite steps: - if: runner.os != 'Windows' uses: ./.github/actions/dependencies-unix - if: runner.os == 'Windows' uses: ./.github/actions/dependencies-windows - uses: actions/setup-node@v4 with: cache: pnpm - uses: actions/setup-go@v5 with: cache-dependency-path: '**/*.sum' - shell: ${{ inputs.shell }} run: pnpm install - shell: ${{ inputs.shell }} run: go install tool ================================================ FILE: .github/workflows/benchmark-generic.yml ================================================ name: Generic Benchmark CI on: workflow_dispatch: inputs: packages: description: 'Packages to benchmark (e.g. ./packages/server/...)' required: true default: './packages/server/pkg/flow/simulation' type: string count: description: 'Number of benchmark runs' required: false default: '5' type: string force_comparison: description: 'Force comparison even if no previous artifacts' required: false default: false type: boolean pull_request: paths: - 'packages/server/**' - 'tools/benchmark/**' - '.github/workflows/benchmark-generic.yml' push: paths: - 'packages/server/**' - 'tools/benchmark/**' - '.github/workflows/benchmark-generic.yml' branches: [main] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: benchmark: name: Run Benchmarks runs-on: ubuntu-latest outputs: comparison: ${{ steps.compare-results.outputs.comparison }} has-regressions: ${{ steps.compare-results.outputs.has-regressions }} has-changes: ${{ steps.check-changes.outputs.changed }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup runner environment uses: ./.github/actions/setup - name: Check if relevant files changed id: check-changes run: | if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then echo "changed=true" >> $GITHUB_OUTPUT else if [ "${{ github.event_name }}" == "pull_request" ]; then BASE="${{ github.event.pull_request.base.sha }}" else BASE="${{ github.event.before }}" fi CHANGED=$(git diff --name-only $BASE...HEAD | grep -E "^packages/server/|^tools/benchmark/|^.github/workflows/benchmark-generic.yml" || true) if [ -n "$CHANGED" ]; then echo "changed=true" >> $GITHUB_OUTPUT else echo "changed=false" >> $GITHUB_OUTPUT fi fi - name: Determine Target Packages id: target if: steps.check-changes.outputs.changed == 'true' || github.event_name == 'workflow_dispatch' run: | if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then echo "packages=${{ github.event.inputs.packages }}" >> $GITHUB_OUTPUT echo "count=${{ github.event.inputs.count }}" >> $GITHUB_OUTPUT else # Default for PR/Push: simulation package which is most critical echo "packages=./packages/server/pkg/flow/simulation" >> $GITHUB_OUTPUT echo "count=5" >> $GITHUB_OUTPUT fi - name: Run Current Benchmarks id: run-current if: steps.check-changes.outputs.changed == 'true' || github.event_name == 'workflow_dispatch' run: | mkdir -p bench_data # Run benchmarks using standard go test go test -bench=. -benchmem -run=^$ \ -count=${{ steps.target.outputs.count }} \ -timeout=30m \ ${{ steps.target.outputs.packages }} | tee bench_data/current.txt # Parse to JSON for artifact storage and comparison go run tools/benchmark/*.go parse \ --input=bench_data/current.txt \ --output=bench_data/current.json - name: Download Baseline Artifacts if: (steps.check-changes.outputs.changed == 'true' || github.event_name == 'workflow_dispatch') env: GH_TOKEN: ${{ github.token }} run: | echo "Looking for latest successful run on main..." LATEST_RUN_ID=$(gh run list --workflow="benchmark-generic.yml" --branch=main --status=success --event=push --limit=1 --json databaseId --jq '.[0].databaseId') if [ -n "$LATEST_RUN_ID" ]; then echo "Found run ID: $LATEST_RUN_ID. Downloading artifact..." mkdir -p bench_data/previous gh run download $LATEST_RUN_ID -n benchmark-results-main -D bench_data/previous || echo "⚠️ Failed to download artifact (it might not exist or expired)." else echo "⚠️ No successful previous runs found on main." fi - name: Run Baseline (If Artifact Missing) if: (steps.check-changes.outputs.changed == 'true' || github.event_name == 'workflow_dispatch') run: | if [ ! -f "bench_data/previous/current.json" ]; then echo "⚠️ No baseline artifact found. Running baseline on current checkout (approximate)..." # Ideally we would checkout 'main' here, but for simplicity in this V1 we might skip or warn. # A robust implementation would fetch the base commit. # For now, let's skip comparison if missing, UNLESS forced. if [ "${{ github.event.inputs.force_comparison }}" == "true" ]; then echo "Running baseline benchmarks..." go test -bench=. -benchmem -run=^$ \ -count=${{ steps.target.outputs.count }} \ -timeout=30m \ ${{ steps.target.outputs.packages }} | tee bench_data/baseline.txt go run tools/benchmark/*.go parse \ --input=bench_data/baseline.txt \ --output=bench_data/baseline.json else echo "Skipping baseline generation." fi else mv bench_data/previous/current.json bench_data/baseline.json fi - name: Compare Results id: compare-results if: steps.check-changes.outputs.changed == 'true' || github.event_name == 'workflow_dispatch' run: | if [ -f "bench_data/baseline.json" ] && [ -f "bench_data/current.json" ]; then go run tools/benchmark/*.go compare \ --baseline=bench_data/baseline.json \ --current=bench_data/current.json \ --output-md=bench_data/comparison.md \ --output-json=bench_data/comparison.json || { echo "has-regressions=true" >> $GITHUB_OUTPUT } # Output to Job Summary cat bench_data/comparison.md >> $GITHUB_STEP_SUMMARY # Read markdown for PR comment COMPARISON=$(cat bench_data/comparison.md | sed 's/`/\\`/g' | sed 's/$/\\n/' | tr -d '\n') echo "comparison<> $GITHUB_OUTPUT echo "$COMPARISON" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT else echo "Skipping comparison (missing files)." echo "has-regressions=false" >> $GITHUB_OUTPUT fi - name: Upload Artifacts if: steps.check-changes.outputs.changed == 'true' || github.event_name == 'workflow_dispatch' uses: actions/upload-artifact@v4 with: name: benchmark-results-${{ github.sha }} path: bench_data/ retention-days: 14 - name: Upload Main Baseline (Push Only) if: github.ref == 'refs/heads/main' && github.event_name == 'push' uses: actions/upload-artifact@v4 with: name: benchmark-results-main path: bench_data/current.json retention-days: 30 comment: name: Post Results Comment runs-on: ubuntu-latest needs: benchmark if: | always() && needs.benchmark.result == 'success' && needs.benchmark.outputs.has-changes == 'true' && github.com.event_name == 'pull_request' steps: - name: Find and update PR comment uses: actions/github-script@v7 with: script: | const comparison = `${{ needs.benchmark.outputs.comparison }}`; const hasRegressions = `${{ needs.benchmark.outputs.has-regressions }}` === 'true'; if (!comparison) return; // Find existing performance comment const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, }); const existingComment = comments.find(comment => comment.user.type === 'Bot' && (comment.body.includes('📊 Performance Comparison')) ); let commentBody = comparison; if (hasRegressions) { commentBody = '\n⚠️ **Performance regressions detected!**\n\n' + commentBody; } if (existingComment) { await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: existingComment.id, body: commentBody, }); } else { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body: commentBody, }); } ================================================ FILE: .github/workflows/check.yaml ================================================ name: Check on: workflow_dispatch: pull_request: branches: [main] push: branches: [main] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: check: name: Check runs-on: ubuntu-latest outputs: go-test-modules: ${{ steps.go-test-modules.outputs.value }} go-test-upload: ${{ steps.go-test-upload.outcome }} steps: - uses: actions/checkout@v4 - name: Setup runner environment uses: ./.github/actions/setup - name: Lint run: task lint - id: test name: Test run: task test:ci - id: go-test-modules name: Find modules with Go test results if: steps.test.outcome == 'success' || steps.test.outcome == 'failure' run: | shopt -s nullglob jq --null-input --raw-output \ '$ARGS.positional | map(capture("(?<_>.*)\/dist")._) as $mods | ["value=\($mods)", "length=\($mods | length)"] | join("\n")' \ --args */*/dist/go-test.json | tee $GITHUB_OUTPUT - id: go-test-upload name: Upload Go test results uses: actions/upload-artifact@v4 if: steps.go-test-modules.outputs.length > 0 with: name: go-test path: '*/*/dist/go-test.json' retention-days: 1 cli-integration: name: CLI Integration Tests runs-on: ubuntu-latest needs: check steps: - uses: actions/checkout@v4 - name: Setup runner environment uses: ./.github/actions/setup - name: Run CLI integration tests run: pnpm nx run cli:test:integration go-test-summary: name: Test runs-on: ubuntu-latest needs: check if: needs.check.outputs.go-test-upload == 'success' strategy: matrix: value: ${{ fromJSON(needs.check.outputs.go-test-modules) }} steps: - uses: actions/download-artifact@v4 with: name: go-test - uses: robherley/go-test-action@v0 with: moduleDirectory: ${{ matrix.value }} fromJSONFile: ${{ matrix.value }}/dist/go-test.json ================================================ FILE: .github/workflows/goci.yml ================================================ name: goci on: workflow_dispatch: env: GO_VERSION: 1.23 GOLANGCI_LINT_VERSION: v1.60 jobs: detect-modules: runs-on: ubuntu-latest outputs: modules: ${{ steps.set-modules.outputs.modules }} steps: - uses: actions/checkout@v4 - name: Setup runner environment uses: ./.github/actions/setup - id: set-modules run: | modules=$(go list -m -json | jq -s '.' | jq -c '[.[].Dir | select(index("node_modules") | not)]') echo "modules=$modules" >> $GITHUB_OUTPUT go-test: needs: detect-modules runs-on: ubuntu-latest strategy: matrix: module: ${{ fromJSON(needs.detect-modules.outputs.modules) }} steps: - uses: actions/checkout@v4 - name: Setup runner environment uses: ./.github/actions/setup - name: Test uses: robherley/go-test-action@v0 with: moduleDirectory: ${{ matrix.module }} golangci-lint: needs: detect-modules runs-on: ubuntu-latest strategy: matrix: module: ${{ fromJSON(needs.detect-modules.outputs.modules) }} steps: - uses: actions/checkout@v4 - name: Setup runner environment uses: ./.github/actions/setup - name: golangci-lint ${{ matrix.module }} uses: golangci/golangci-lint-action@v6 with: version: ${{ env.GOLANGCI_LINT_VERSION }} working-directory: ${{ matrix.module }} ================================================ FILE: .github/workflows/release-chrome-extension.yaml ================================================ name: Release / Chrome Extension on: workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} jobs: build: name: Build runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup runner environment uses: ./.github/actions/setup - id: info name: Get project information run: gha-scripts export-project-info - name: Build run: pnpm nx run ${{ steps.info.outputs.NAME }}:build - uses: actions/upload-artifact@v4 with: name: build path: ${{ steps.info.outputs.ROOT }}/dist/*.zip if-no-files-found: error publish: name: Publish to Chrome Webstore needs: build runs-on: ubuntu-latest steps: - uses: actions/download-artifact@v4 with: name: build pattern: chrome-mv3-prod.zip - uses: PlasmoHQ/bpp@v3 with: keys: ${{ secrets.BPP_KEYS }} chrome-file: chrome-mv3-prod.zip ================================================ FILE: .github/workflows/release-cloudflare-pages.yaml ================================================ name: Release / Cloudflare Pages on: workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} jobs: publish: name: Publish to Cloudflare Pages runs-on: ubuntu-latest permissions: contents: read deployments: write steps: - uses: actions/checkout@v4 - name: Setup runner environment uses: ./.github/actions/setup - id: info name: Get project information run: gha-scripts export-project-info - name: Build run: pnpm nx run ${{ steps.info.outputs.NAME }}:build - name: Publish uses: cloudflare/pages-action@v1 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} projectName: ${{ steps.info.outputs.NAME }} directory: ${{ steps.info.outputs.ROOT }}/dist gitHubToken: ${{ secrets.GITHUB_TOKEN }} branch: ${{ env.NODE_ENV }} wranglerVersion: '3' ================================================ FILE: .github/workflows/release-electron-builder.yaml ================================================ name: Release / Electron Builder on: workflow_dispatch: jobs: build: name: Build continue-on-error: true strategy: matrix: runner: - macos-15-intel # x64 - macos-latest # arm64 - ubuntu-latest # x64 - windows-latest # x64 runs-on: ${{ matrix.runner }} permissions: contents: write env: PUBLIC_UMAMI__ENABLE: ${{ vars.UMAMI_ENABLE }} PUBLIC_UMAMI__HOST: ${{ vars.UMAMI_HOST }} PUBLIC_UMAMI__ID: ${{ vars.UMAMI_ID }} steps: - uses: actions/checkout@v4 - name: Setup runner environment uses: ./.github/actions/setup - id: info name: Get project information run: gha-scripts export-project-info - name: Build (macOS) if: runner.os == 'macOS' env: APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} CSC_LINK: ${{ secrets.CSC_LINK }} run: pnpm nx run ${{ steps.info.outputs.NAME }}:build - name: Build (Linux) if: runner.os == 'Linux' run: pnpm nx run ${{ steps.info.outputs.NAME }}:build - name: Build (Windows) if: runner.os == 'Windows' env: AZURE_KEY_VAULT_CERTIFICATE: ${{ secrets.AZURE_KEY_VAULT_CERTIFICATE }} AZURE_KEY_VAULT_CLIENT_ID: ${{ secrets.AZURE_KEY_VAULT_CLIENT_ID }} AZURE_KEY_VAULT_CLIENT_SECRET: ${{ secrets.AZURE_KEY_VAULT_CLIENT_SECRET }} AZURE_KEY_VAULT_TENANT_ID: ${{ secrets.AZURE_KEY_VAULT_TENANT_ID }} AZURE_KEY_VAULT_URL: ${{ secrets.AZURE_KEY_VAULT_URL }} run: pnpm nx run ${{ steps.info.outputs.NAME }}:build - name: Publish env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gha-scripts upload-electron-release-assets ================================================ FILE: .github/workflows/release-go.yaml ================================================ name: Release / Go on: workflow_dispatch: jobs: build: name: Build Go Binary continue-on-error: true strategy: matrix: include: - runner: macos-15-intel platform: darwin-x64 - runner: macos-latest platform: darwin-arm64 - runner: ubuntu-latest platform: linux-x64 - runner: ubuntu-latest platform: linux-arm64 - runner: windows-latest platform: win32-x64 - runner: windows-latest platform: win32-ia32 runs-on: ${{ matrix.runner }} permissions: contents: write steps: - uses: actions/checkout@v4 - name: Setup runner environment uses: ./.github/actions/setup - id: info name: Get project information run: gha-scripts export-project-info - name: Build Binary env: VERSION: ${{ steps.info.outputs.VERSION }} PLATFORM: ${{ matrix.platform }} CGO_ENABLED: '0' run: pnpm nx run ${{ steps.info.outputs.NAME }}:build:release - name: Publish env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gha-scripts upload-go-release-assets ================================================ FILE: .github/workflows/release.yaml ================================================ name: Release on: workflow_dispatch: inputs: api-recorder-extension: { type: boolean } cli: { type: boolean } desktop: { type: boolean } web: { type: boolean } jobs: release: name: Release runs-on: ubuntu-latest permissions: actions: write contents: write steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup runner environment uses: ./.github/actions/setup - name: Setup Git user # Use public GitHub bot user: # https://api.github.com/users/github-actions[bot] run: | git config user.name github-actions[bot] git config user.email 41898282+github-actions[bot]@users.noreply.github.com - name: Version projects and trigger specialized release workflows env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gha-scripts release \ ${{ inputs.api-recorder-extension && 'api-recorder-extension' || '' }} \ ${{ inputs.cli && 'cli' || '' }} \ ${{ inputs.desktop && 'desktop' || '' }} \ ${{ inputs.web && 'web' || '' }} \ ================================================ FILE: .github/workflows/sql.yml ================================================ name: sql on: push: branches: - 'main' paths: - '**.sql' jobs: sql-vet: name: SQL Vet runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: sqlc-dev/setup-sqlc@v4 with: sqlc-version: '1.30.0' - run: sqlc vet working-directory: './packages/db/pkg/sqlc' ================================================ FILE: .github/workflows/update-scoop.yaml ================================================ name: Update / Scoop on: schedule: - cron: '0 0 * * 1' workflow_dispatch: jobs: build: name: Build runs-on: windows-latest permissions: contents: write pull-requests: write steps: - uses: actions/checkout@v4 - name: Setup Windows dependencies uses: ./.github/actions/dependencies-windows - name: Setup Git user # Use public GitHub bot user: # https://api.github.com/users/github-actions[bot] run: | git config user.name github-actions[bot] git config user.email 41898282+github-actions[bot]@users.noreply.github.com - name: Update Scoop dependencies run: | scoop update --all scoop export > .\scoop.json - name: Create Pull Request uses: peter-evans/create-pull-request@v7 with: commit-message: Update Scoop dependencies title: Update Scoop dependencies ================================================ FILE: .gitignore ================================================ .ai .bench/ .claude .direnv .env.*.local .env.keys .env.local .next .nx/cache .nx/workspace-data .plasmo .task .vite *.db *.db-journal *.db-shm *.db-wal *.embed apps/cli/devtoolscli conductor data dist next-env.d.ts node_modules out packages/auth/schema.json storybook-static tmp/ tsconfig.tsbuildinfo tsp-output .bench/ .ralph/ .ralphrc # Coverage files *.out coverage.out **/.gocache/ .code/ packages/server/server packages/server/authadapter-testserver tools/collect_go_failures.py /server ================================================ FILE: .golangci.yml ================================================ version: "2" run: allow-parallel-runners: false modules-download-mode: readonly allow-serial-runners: true go: "1.24" linters: settings: staticcheck: checks: - all - "-ST1000" - "-ST1003" - "-ST1016" - "-ST1020" - "-ST1021" - "-ST1022" - "-S1016" ## False Positives - "-SA5011" ## False Positives ================================================ FILE: .prettierignore ================================================ .ai/ .golangci.yml *.har AGENTS.md CHANGELOG.md conductor/ dist flake.lock GEMINI.md LICENSE pnpm-lock.yaml route-tree.gen.ts scoop.json ================================================ FILE: .sqlfluff ================================================ [sqlfluff] dialect = sqlite templater = jinja sql_file_exts = .sql,.sql.j2,.dml,.ddl [sqlfluff:indentation] indented_joins = False indented_using_on = True template_blocks_indent = False [sqlfluff:templater] unwrap_wrapped_queries = True [sqlfluff:templater:jinja] apply_dbt_builtins = True ================================================ FILE: .vscode/extensions.json ================================================ { "recommendations": [ "bradlc.vscode-tailwindcss", "dbaeumer.vscode-eslint", "editorconfig.editorconfig", "esbenp.prettier-vscode", "jnoortheen.nix-ide", "mkhl.direnv", "nrwl.angular-console", "redhat.vscode-yaml", "tamuratak.vscode-lezer", "typespec.typespec-vscode" ] } ================================================ FILE: .vscode/settings.json ================================================ { "eslint.options": { "flags": ["v10_config_lookup_from_file"] }, "eslint.nodeEnv": "IDE", "files.associations": { "*.css": "tailwindcss" }, "nix.enableLanguageServer": true, "nix.serverPath": "nixd", "nix.serverSettings": { "nixd": { "formatting": { "command": ["alejandra"] } } }, "tailwindCSS.experimental.configFile": "packages/ui/src/styles/index.css", "tailwindCSS.classAttributes": [" "], "tailwindCSS.classFunctions": ["tw"], "tailwindCSS.lint.cssConflict": "ignore", "typescript.enablePromptUseWorkspaceTsdk": true, "typescript.tsdk": "node_modules/typescript/lib" } ================================================ FILE: AGENTS.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Core Mandates 1. **Environment:** This project uses a Nix flake environment with `direnv`. Use `pnpm nx` for project tasks and `task` (Taskfile) for orchestrated workflows. 2. **Context Awareness:** Read `README.md` for domain-specific vocabulary (flow nodes, delta system) before starting complex tasks. 3. **File Editing:** Verify files exist before editing. Use `git status` and `git diff` to verify changes. **Never** revert changes you didn't author unless instructed. **Never** commit changes unless explicitly asked. 4. **Verification:** Always test, lint, and compile after making changes. ## Common Commands ### Build & Run ```bash task dev:desktop # Full desktop app (Electron + React + Go Server) pnpm nx run server:dev # Go server with hot reload pnpm nx run client:dev # React frontend pnpm nx run spec:build # Regenerate from TypeSpec (after editing .tsp files) pnpm nx run db:generate # Regenerate sqlc (after editing .sql files) cd apps/cli && task build:release # Build CLI binary ``` ### Testing ```bash task test # All unit tests pnpm nx run server:test # Server tests only pnpm nx run db:test # DB tests only pnpm nx run cli:test # CLI tests only # Single Go test (use -run for specific functions) cd packages/server && go test -run TestFunctionName ./path/to/package/ -v -timeout 30s cd packages/db && go test -run TestFunctionName ./path/to/package/ -v -timeout 10s cd apps/cli && go test -run TestFunctionName ./path/to/package/ -v -timeout 30s ``` ### Lint & Fix ```bash task lint # ESLint + format checks + golangci-lint task fix # Prettier + Syncpack auto-fix pnpm nx run server:lint # Go linters (golangci-lint + norawsql + notxread) ``` ### Benchmarks ```bash task benchmark:run # Run benchmarks task benchmark:baseline # Save baseline task benchmark:compare # Compare against baseline ``` ### Releasing **Never** manually edit version numbers in `package.json`. Use Nx version plans: ```bash # 1. Create a version plan (commits a .nx/version-plans/.md file) # Bump types: patch, minor, major # Projects: desktop, cli, api-recorder-extension task version-plan project=desktop # Interactive — prompts for bump type + message # Or create the file directly (non-interactive): # .nx/version-plans/.md # --- # desktop: patch # --- # Changelog message here. # 2. Commit & push the version plan file # 3. Trigger the release workflow (via GitHub Actions): gh workflow run release.yaml -f desktop=true # -f cli=true, -f web=true, etc. ``` The release workflow reads version plans, bumps versions, creates git tags + GitHub releases, and dispatches platform-specific build workflows (Electron Builder, Go binaries, etc.). ## Project Overview DevTools is a local-first, open-source API testing platform (Postman alternative) — desktop app, CLI, and Chrome extension. Features request recording, visual flow building, and CI/CD integration. ## Architecture ### Monorepo Structure - **`apps/desktop`** — Electron app (TypeScript/React, electron-vite) - **`apps/cli`** — Go CLI (cobra). Embeds `packages/worker-js` (TypeScript worker bundled via tsup) - **`packages/server`** — Go backend (Connect RPC, SQLite/LibSQL) - **`packages/client`** — React frontend (TanStack Router/Query, Effect-TS, React Flow) - **`packages/ui`** — Shared React component library (React Aria, Tailwind Variants, Storybook) - **`packages/db`** — Go database package (`devtoolsdb`), sqlc generated code, SQLite drivers - **`packages/spec`** — TypeSpec definitions → Protobuf → Go/TypeScript codegen (single source of truth) - **`packages/worker-js`** — TypeScript worker bundled into CLI binary - **`tools/`** — Custom Go linters (`norawsql`, `notxread`), benchmarking, spec emitter, ESLint config ### Go Workspace `go.work` with Go 1.25. Modules: `apps/cli`, `packages/db`, `packages/server`, `packages/spec`, and tools. ### Naming Conventions - **Services:** `s` prefix (`shttp`, `senv`, `sflow`, `suser`, `sworkspace`) - **Models:** `m` prefix (`mhttp`, `mflow`, `menv`, `muser`) - **RPC handlers:** `r` prefix (`rhttp`, `rflowv2`, `renv`) - **IDs:** All use `idwrap.IDWrap` (ULID-based) from `packages/server/pkg/idwrap` ### Backend Layers (Server) 1. **RPC Layer** (`packages/server/internal/api`) — Connect RPC handlers. All follow **Fetch-Check-Act**: - **FETCH**: Read data via Reader services (non-blocking, parallel) - **CHECK**: Validate permissions/rules (pure Go, in memory) - **ACT**: Write via Writer services inside a short transaction - Publishes events to `eventstream` after successful transactions 2. **Service Layer** (`packages/server/pkg/service`) — Domain logic, split into Reader (read-only, `*sql.DB` pool) and Writer (write-only, `*sql.Tx`, per-transaction) 3. **Model Layer** (`packages/server/pkg/model`) — Pure Go domain structs bridging API (Proto) and DB (sqlc) types 4. **Data Access** (`packages/db/pkg/sqlc`) — sqlc-generated code. Schema in `schema/`, queries in `queries/`, output in `gen/` Large RPC handlers are split by concern: `rhttp_crud.go`, `rhttp_exec.go`, `rhttp_delta_*.go`, etc. ### Codegen Pipeline `pnpm nx run spec:build` runs: TypeSpec compile → buf generate → post-process. Output in `packages/spec/dist/`: - `buf/go/` — Go protobuf + Connect RPC - `buf/typescript/` — TypeScript protobuf types - `tanstack-db/typescript/` — TanStack DB types ### TypeScript/React - **Strictness:** `@tsconfig/strictest`, no `any` - **Styling:** Tailwind CSS v4 - **State:** Effect-TS + TanStack Query - **Formatting:** Prettier (single quotes, JSX single quotes). ESLint with perfectionist import sorting - **Dependencies:** Pnpm catalog mode (strict) — all versions centralized in `pnpm-workspace.yaml` - **No TS unit tests** — quality enforced via ESLint + strict TypeScript ## Go Patterns ### Testing - **Isolated service tests:** `sqlitemem.NewSQLiteMem(ctx)` — single-connection in-memory SQLite - **RPC/integration tests:** `testutil.CreateBaseDB` / `dbtest.GetTestDB(ctx)` — shared-cache in-memory SQLite - **One DB per test.** Never share DB instances across tests - **Seeding:** `BaseTestServices.CreateTempCollection` for workspace/user/collection state - **`t.Parallel()`** only if each subtest creates its own independent DB - **Transactions:** Keep short. Use `devtoolsdb.TxnRollback` in defer. Commit before reading from a different connection - **Server tests run with `-p 8`** (8 parallel test packages) ### Integration Tests For tests requiring external services (APIs, cloud): - File naming: `integration_*.go` - Build tags: `//go:build ai_integration` - Env var guard: `if os.Getenv("RUN_XX_INTEGRATION") != "true" { t.Skip() }` ### Linting Server lint (`pnpm nx run server:lint`) includes: - `golangci-lint` with extensive rules (govet, gosec, errorlint, revive, exhaustive, etc.) - `go tool norawsql` — enforces sqlc usage, no raw SQL strings - `go tool notxread` — prevents reads inside transactions (SQLite deadlock prevention) ### Best Practices - Functional design, lean packages. No complex OOP hierarchies - SQLite reads **before** transactions. Transactions as short as possible - Map errors to Connect RPC codes (`connect.CodeNotFound`, not `CodePermissionDenied` for missing resources — prevents ID enumeration) - Strict model separation: API types (Proto) ↔ Domain types (model) ↔ Storage types (sqlc gen) ## Domain Documentation - **Flow Engine & Nodes:** `packages/server/docs/specs/FLOW.md` - **HTTP & Proxy:** `packages/server/docs/specs/HTTP.md` - **Real-time Sync:** `packages/server/docs/specs/SYNC.md` - **Mutation System:** `packages/server/docs/specs/MUTATION.md` - **Service Architecture:** `packages/server/docs/specs/BACKEND_ARCHITECTURE_V2.md` - **Bulk Operations:** `packages/server/docs/specs/BULK_SYNC_TRANSACTION_WRAPPERS.md` ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2026 DevTools Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================

DevTools

A free, open-source Postman-style API tester you run locally. Record browser requests, auto-generate chained tests, and ship them straight to your CI—no sign-ups, no cloud, just code.

Table of Contents
  1. About the Project
  2. Installation
  3. Chrome Extension
  4. Contributing
  5. License
## About the Project DevTools gives developers complete control over their API testing workflows: - **Browser Request Recording**: Automatically capture all API requests and responses from your browser sessions. - **No-code Test Generation**: Transform your recorded API requests into reusable test collections. - **Request Chaining**: Easily create complex test flows with automatic data extraction and variable chaining between requests. - **100% Local Execution**: Run tests on your local machine without sending data to third-party services. - **CI/CD Integration**: Seamlessly integrate your API tests into any CI/CD pipeline. - **Zero Configuration**: Start testing in seconds without complex setup or authentication. - **Privacy First**: Keep your sensitive API data and credentials secure on your own machine. - **Postman-Compatible**: Import from Postman-like JSON files and export collections for cross-platform compatibility. DevTools combines the best aspects of Postman's visual interface with the security and flexibility of local-first, code-driven development. No sign-ups, no cloud dependencies - just powerful API testing tools that integrate perfectly with your development workflow. ### Postman-Style Request Interface ![Postman-style UI with form data](https://dev.tools/_next/static/media/first-request-jsonplaceholder-users-1.3351fec7.webp) The DevTools interface provides a familiar Postman-like experience for working with API requests. The screenshot above shows the request editor interface with form data input, allowing you to easily build, test, and organize your API requests without the cloud dependency. ### Visual Flow Builder ![Flow builder with request chaining](https://dev.tools/_next/static/media/flow-canvas-overview.d549cd67.webp) The flow builder allows you to visually chain API requests and create complex test scenarios. Connect different nodes to build your workflow: - **Request nodes**: Execute API calls in sequence - **Conditional nodes**: Add if-statement logic based on response data - **Loop nodes**: Iterate through data sets with for-each loops - **Data nodes**: Import data from Excel sheets and other sources This visual approach makes it easy to create sophisticated API workflows without writing code. ## Installation ### CLI Tool Install the DevTools CLI with a single command: ```bash curl -fsSL https://sh.dev.tools/install.sh | bash ``` Or if you prefer wget: ```bash wget -qO- https://sh.dev.tools/install.sh | bash ``` The installer will: - Automatically detect your platform (Linux, macOS, Windows) - Download the appropriate binary from the latest release - Install it to `/usr/local/bin` (customizable with `INSTALL_DIR` environment variable) #### Manual Installation You can also download pre-built binaries directly from the [releases page](https://github.com/the-dev-tools/dev-tools/releases). ### Desktop Application Download the desktop application for your platform from the [releases page](https://github.com/the-dev-tools/dev-tools/releases): - **macOS**: DevTools-{version}-darwin-{arch}.dmg - **Windows**: DevTools-{version}-win32-{arch}.exe - **Linux**: DevTools-{version}-linux-{arch}.AppImage ## Chrome Extension [![Chrome Web Store Version](https://img.shields.io/chrome-web-store/v/bcnbbkdpnoeaaedhhnlefgpijlpbmije?logo=googlechrome&logoColor=white&label=API%20Recorder%20Extension)](https://chromewebstore.google.com/detail/api-recorder/bcnbbkdpnoeaaedhhnlefgpijlpbmije) The DevTools API Recorder extension captures your API interactions in real-time: - **One-Click Recording**: Start and pause API recording with a single click in any browser tab - **Request Organization**: Automatically categorizes requests by domain and endpoint - **Complete Request Details**: Capture headers, query parameters, body content, and responses - **Response Inspection**: Examine API responses with syntax highlighting - **Secure & Private**: All captured data remains in your browser—nothing is transmitted to external servers The extension works seamlessly with the main DevTools application, allowing you to record APIs in your browser and then use them to build sophisticated test flows and documentation. ## Contributing We appreciate feedback and contribution to this repo! Before you get started, please see the following: - [Contribution guidelines](./docs/CONTRIBUTING.md) - [Code of conduct guidelines](./docs/CODE-OF-CONDUCT.md) ## License Distributed under the Apache 2.0 License. See `LICENSE` for more information. ================================================ FILE: apps/api-recorder-extension/eslint.config.ts ================================================ import { ConfigArray } from 'typescript-eslint'; import base from '@the-dev-tools/eslint-config'; const config: ConfigArray = [ ...base, { rules: { // https://github.com/typescript-eslint/typescript-eslint/issues/9902 // https://github.com/typescript-eslint/typescript-eslint/issues/9899 // https://github.com/microsoft/TypeScript/issues/59792 '@typescript-eslint/no-deprecated': 'off', 'import-x/no-unresolved': 'off', }, }, ]; export default config; ================================================ FILE: apps/api-recorder-extension/package.disabled.json ================================================ { "name": "@the-dev-tools/api-recorder-extension", "displayName": "API Recorder", "author": "dev.tools", "version": "0.4.10", "private": true, "type": "module", "scripts": { "build": "plasmo build --build-path=dist --zip", "dev": "plasmo dev --build-path=dist" }, "dependencies": { "@plasmohq/storage": "1.15.0", "effect": "3.17.9", "magic-sdk": "29.4.2", "plasmo": "0.90.5", "react": "19.1.1", "react-aria-components": "1.12.1", "react-dom": "19.1.1", "react-icons": "5.5.0", "tailwind-merge": "3.3.1", "tailwind-variants": "2.1.0", "uuid": "11.1.0" }, "devDependencies": { "@tailwindcss/postcss": "~4.1.11", "@the-dev-tools/eslint-config": "workspace:^", "@the-dev-tools/ui": "workspace:^", "@types/chrome": "~0.1.1", "@types/node": "~24.3.0", "@types/react": "~19.1.8", "@types/react-dom": "~19.1.5", "devtools-protocol": "~0.0.1490591", "postcss": "~8.5.6", "tailwindcss": "~4.1.11", "typescript": "~5.9.2", "typescript-eslint": "~8.40.0" }, "manifest": { "host_permissions": ["https://*/*"], "permissions": ["debugger", "tabs", "unlimitedStorage"], "web_accessible_resources": [ { "resources": ["tabs/auth-callback.html"], "matches": ["*://*.magic.link/*"] } ] } } ================================================ FILE: apps/api-recorder-extension/postcss.config.js ================================================ /** * @type {import('postcss').ProcessOptions} */ module.exports = { plugins: { '@tailwindcss/postcss': {}, }, }; ================================================ FILE: apps/api-recorder-extension/project.disabled.json ================================================ { "$schema": "../../node_modules/nx/schemas/project-schema.json", "name": "api-recorder-extension", "projectType": "application", "targets": { "build": { "command": "echo", "metadata": { "description": "Target is disabled due to broken build. Plasmo framework seems to not be well maintained anymore, and doesn't work with Tailwind CSS v4 out of the box. Needs to be investigated more and potentially switched to a different framework." } } } } ================================================ FILE: apps/api-recorder-extension/src/auth.ts ================================================ import { Effect, Option, Schema } from 'effect'; import { Magic } from 'magic-sdk'; import * as Storage from '~storage'; const magicLink = new Magic('pk_live_75E3754872D9F513', { deferPreload: true, useStorageCache: true, }); const LoggedInTag = 'LoggedInTag'; const LoggedIn = Schema.Boolean; const setLoggedIn = Storage.set(Storage.Local, LoggedInTag, LoggedIn); export const useLoggedIn = () => Storage.useState(Storage.Local, LoggedInTag, LoggedIn); const EmailTag = 'EmailTag'; const Email = Schema.Option(Schema.String); const setEmail = Storage.set(Storage.Local, EmailTag, Email); export const useEmail = () => Storage.useState(Storage.Local, EmailTag, Email); const CallbackTab = 'auth-callback'; export const loginInit = (email: string) => Effect.gen(function* () { yield* setEmail(Option.some(email)); const result = yield* Effect.promise(() => magicLink.auth.loginWithMagicLink({ email, redirectURI: `chrome-extension://${chrome.runtime.id}/tabs/${CallbackTab}.html`, }), ); if (result === null) return false; yield* setLoggedIn(true); return true; }); export const loginConfirm = (token: string) => Effect.gen(function* () { const result = yield* Effect.promise(() => magicLink.auth.loginWithCredential({ credentialOrQueryString: token })); if (result === null) return false; yield* setLoggedIn(true); return true; }); export const logout = Effect.gen(function* () { const result = yield* Effect.promise(() => magicLink.user.logout()); if (!result) return false; yield* setLoggedIn(false); yield* setEmail(Option.none()); return true; }); ================================================ FILE: apps/api-recorder-extension/src/background.ts ================================================ import type { Protocol } from 'devtools-protocol'; import type { ProtocolMapping } from 'devtools-protocol/types/protocol-mapping'; import { Array, Effect, flow, Option, Predicate, String, Struct } from 'effect'; import * as Recorder from '~recorder'; import { Runtime } from '~runtime'; // PlasmoHQ implements a workaround to keep the background service worker alive // in Chrome Extension Manifest V3, so doing it manually is not needed (for now) // https://github.com/PlasmoHQ/plasmo/tree/main/api/persistent // https://stackoverflow.com/questions/66618136/persistent-service-worker-in-chrome-extension const sendDebuggerCommand = ( target: chrome.debugger.Debuggee, method: Command, ...commandParams: ProtocolMapping.Commands[Command]['paramsType'] ) => Effect.tryPromise(() => chrome.debugger.sendCommand(target, method, ...commandParams), ); const isDebuggerEvent = ( match: Method, method: string, _params: unknown, ): _params is ProtocolMapping.Events[Method][0] => match === method; const resourceTypes = ['XHR', 'Fetch'] as const satisfies Protocol.Network.ResourceType[]; void Effect.gen(function* () { let collection = yield* Recorder.getCollection; const indexMap = Recorder.makeIndexMap(); // Debugger control Recorder.watch({ onReset: Effect.gen(function* () { collection = yield* Recorder.reset(indexMap); }).pipe(Effect.ignoreLogged), onStart: (tabId) => Effect.gen(function* () { yield* Effect.tryPromise(() => chrome.debugger.attach({ tabId }, '1.0')); yield* sendDebuggerCommand({ tabId }, 'Network.enable'); const tab = yield* Effect.tryPromise(() => chrome.tabs.get(tabId)); collection = yield* Recorder.addNavigation(collection, tab); }).pipe( Effect.catchIf(flow(Struct.get('message'), String.startsWith('Cannot access')), () => Recorder.stop), Effect.ignoreLogged, ), onStop: (tabId) => Effect.gen(function* () { yield* sendDebuggerCommand({ tabId }, 'Network.disable'); yield* Effect.tryPromise(() => chrome.debugger.detach({ tabId })); }).pipe( Effect.catchIf( flow( Struct.get('message'), Predicate.some([ String.startsWith('Debugger is not attached'), String.startsWith('No tab with given id'), String.startsWith('Cannot access'), ]), ), () => Effect.void, ), Effect.ignoreLogged, ), }); // URL updates chrome.tabs.onUpdated.addListener((tabId, { url }, tab) => Effect.gen(function* () { if (url === undefined) return; const recorderTabId = yield* Recorder.getTabId; if (!Option.contains(recorderTabId, tabId)) return; collection = yield* Recorder.addNavigation(collection, tab); }).pipe(Effect.ignoreLogged, Runtime.runPromise), ); // Stop recording on debugger detach chrome.debugger.onDetach.addListener((source) => Effect.gen(function* () { const recorderTabId = yield* Recorder.getTabId; if (!Option.contains(recorderTabId, source.tabId)) return; yield* Recorder.stop; }).pipe(Effect.ignoreLogged, Runtime.runPromise), ); // Debugger events chrome.debugger.onEvent.addListener((source, method, params) => Effect.gen(function* () { const recorderTabId = yield* Recorder.getTabId; if (!Option.contains(recorderTabId, source.tabId)) return; // Request if (isDebuggerEvent('Network.requestWillBeSent', method, params)) { if (!Array.contains(resourceTypes, params.type)) return; const { requestId } = params; const data = yield* sendDebuggerCommand(source, 'Network.getRequestPostData', { requestId }).pipe( Effect.catchAll(() => Effect.succeed(undefined)), ); collection = yield* Recorder.addRequest(collection, indexMap, params, data); } // Response if (isDebuggerEvent('Network.responseReceived', method, params)) { if (!Array.contains(resourceTypes, params.type)) return; const { requestId } = params; const body = yield* sendDebuggerCommand(source, 'Network.getResponseBody', { requestId }).pipe( Effect.catchAll(() => Effect.succeed(undefined)), ); collection = yield* Recorder.addResponse(collection, indexMap, params, body); } }).pipe(Effect.ignoreLogged, Runtime.runPromise), ); // Sync collection yield* Effect.gen(function* () { yield* Effect.sleep('1 second'); yield* Recorder.setCollection(collection); }).pipe(Effect.forever); }).pipe(Effect.ignoreLogged, Runtime.runPromise); ================================================ FILE: apps/api-recorder-extension/src/layout.tsx ================================================ import backgroundImage from 'data-base64:~/../assets/background.png'; import { twMerge } from 'tailwind-merge'; export interface LayoutProps extends React.ComponentPropsWithoutRef<'div'> { innerClassName?: string; } export const Layout = ({ children, className, innerClassName, ...props }: LayoutProps) => (
Background
{children}
); ================================================ FILE: apps/api-recorder-extension/src/popup.tsx ================================================ import '~styles.css'; import { Array, Clock, Duration, Effect, flow, HashMap, Match, Option, pipe, Schema, String, Struct, Tuple, } from 'effect'; import * as React from 'react'; import * as RAC from 'react-aria-components'; import * as FeatherIcons from 'react-icons/fi'; import { twMerge } from 'tailwind-merge'; import { focusVisibleRingStyles } from '@the-dev-tools/ui/focus-ring'; import { EmptyCollectionIllustration, IntroIcon, Logo } from '@the-dev-tools/ui/illustrations'; import { tw } from '@the-dev-tools/ui/tailwind-literal'; import { Button } from '~/ui/button'; import * as Auth from '~auth'; import { Layout as BaseLayout, type LayoutProps } from '~layout'; import * as Postman from '~postman'; import * as Recorder from '~recorder'; import { Runtime } from '~runtime'; import * as Storage from '~storage'; import { keyValue } from './utils'; const Layout = (props: Omit) => ( ); class LoginFormData extends Schema.Class('LoginFormData')({ email: Schema.String, }) {} const LoginPage = () => { const [loading, setLoading] = React.useState(false); return ( Effect.gen(function* () { event.preventDefault(); const { email } = yield* pipe( new FormData(event.currentTarget), Object.fromEntries, Schema.decode(LoginFormData), ); setLoading(true); yield* Auth.loginInit(email); setLoading(false); }).pipe(Runtime.runPromise) } >

DevTools

Create your account and get your APIs call in seconds

Email focusVisibleRingStyles({ className: [ tw`w-full rounded-lg border bg-white px-3 py-2 text-sm leading-tight text-slate-500`, !renderProps.isFocused && tw`border-slate-300`, ], }) } />
); }; interface RecorderLayoutProps { children: React.ReactNode; headerSlot?: React.ReactNode; } const RecorderLayout = ({ children, headerSlot }: RecorderLayoutProps) => (

DevTools

{headerSlot}
{children} ); const IntroPage = () => (

Get your API quicker

Click the record to start the record

); const SelectionSchema = Schema.Union(Schema.Literal('all'), Schema.Set(Schema.Union(Schema.String, Schema.Number))); const RecorderPage = () => { const collection = Recorder.useCollection(); const tabId = Recorder.useTabId(); const [searchTerm, setSearchTerm] = React.useState(''); const filteredNavigations = (() => { if (searchTerm === '') return collection.item; const filterHosts = (navigation: Postman.Item) => (hosts: Postman.Item['item']) => Array.filter(hosts ?? [], (host) => { if (!navigation.name || !host.name) return false; const searchString = String.toLowerCase(searchTerm); return pipe(navigation.name + host.name, String.toLowerCase, String.includes(searchString)); }); return pipe( collection.item, Array.map((_) => Struct.evolve(_, { item: filterHosts(_) })), Array.filter((navigation) => (navigation.item?.length ?? 0) > 0), ); })(); const indexMap = React.useMemo(() => { const itemTuples = (key: Key, previousIndex: PreviousIndex) => (items: Postman.Item['item']) => Array.map(items ?? [], (item, index) => Tuple.make(item.id, { index: { ...previousIndex, ...keyValue(key, index) }, item, }), ); const mapItemTuples = (key: Key) => ( input: ReturnType>>, ) => pipe(input, Array.flatMap(flow(Tuple.getSecond, ({ index, item }) => itemTuples(key, index)(item.item)))); const hostTuples = pipe(collection.item, itemTuples('navigation', {}), mapItemTuples('host')); return { hosts: pipe(hostTuples, HashMap.fromIterable), requests: pipe(hostTuples, mapItemTuples('request'), HashMap.fromIterable), }; }, [collection.item]); const [hostsSelectionMaybe, setHostsSelection] = Storage.useState(Storage.Local, 'HostsSelection', SelectionSchema); const hostsSelection = Option.getOrElse(hostsSelectionMaybe, () => new Set()); const selectedHost = pipe( hostsSelection, Option.liftPredicate((_) => _ !== 'all'), Option.map((_) => _.values().next().value as Postman.Item['id']), Option.flatMap((_) => HashMap.get(indexMap.hosts, _)), Option.map(Struct.get('item')), ); const filteredRequests = pipe( selectedHost, Option.flatMap(flow(Struct.get('item'), Option.fromNullable)), Option.map((requests) => { if (searchTerm === '') return requests; return Array.filter(requests, (request: Postman.Item) => { if (!request.name) return false; const searchString = String.toLowerCase(searchTerm); return pipe(request.name, String.toLowerCase, String.includes(searchString)); }); }), Option.getOrElse(() => []), ); const [requestsSelectionMaybe, setRequestsSelection] = Storage.useState( Storage.Local, 'RequestsSelection', SelectionSchema, ); const requestsSelection = Option.getOrElse(requestsSelectionMaybe, () => new Set()); const selectedCollection = (): Postman.Collection => { if (requestsSelection === 'all') return collection; interface SelectedItem extends Omit { readonly item?: Option.Option[]; } // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters const emptySelectedItems = >(item: T) => Struct.evolve(item, { item: (): Option.Option[] => Array.makeBy(item.item?.length ?? 0, () => Option.none()), }); const selectCollection = (requests: HashMap.HashMap.Value<(typeof indexMap)['requests']>[]) => Array.reduce(requests, emptySelectedItems(collection), (selectedCollection, request) => Option.gen(function* () { const navigation = yield* Array.get(collection.item, request.index.navigation); const host = yield* Array.get(navigation.item ?? [], request.index.host); let selectedNavigation: SelectedItem = pipe( selectedCollection.item, Array.get(request.index.navigation), Option.flatten, Option.getOrElse(() => emptySelectedItems(navigation)), ); const selectedHost: SelectedItem = pipe( selectedNavigation.item ?? [], Array.get(request.index.host), Option.flatten, Option.getOrElse(() => emptySelectedItems(host)), Struct.evolve({ item: (_) => Array.replace(_ ?? [], request.index.request, pipe(request.item, Struct.omit('item'), Option.some)), }), ); selectedNavigation = Struct.evolve(selectedNavigation, { item: (_) => Array.replace(_ ?? [], request.index.host, Option.some(selectedHost)), }); return Struct.evolve(selectedCollection, { item: (_) => Array.replace(_, request.index.navigation, Option.some(selectedNavigation)), }); }).pipe(Option.getOrElse(() => selectedCollection)), ); const evolveItems = (map: (item: SelectedItem) => K) => (item: A) => Struct.evolve(item, { item: (_) => pipe(_ ?? [], Array.getSomes, (_) => Array.map(_ as SelectedItem[], map)), }); return pipe( requestsSelection.values(), Array.fromIterable, Array.map((_) => pipe(_ as Postman.Item['id'], (_) => HashMap.get(indexMap.requests, _))), Array.getSomes, selectCollection, evolveItems(evolveItems(evolveItems(Struct.omit('item')))), ); }; const exportCollection = Effect.gen(function* () { const file = yield* pipe( selectedCollection(), Schema.encode(Postman.Collection), Effect.map(JSON.stringify), Effect.map((_) => new Blob([_], { type: 'text/json' })), ); const link = document.createElement('a'); link.href = URL.createObjectURL(file); link.download = `postman-collection.json`; link.click(); URL.revokeObjectURL(link.href); }); const currentTimeMillis = pipe(Clock.currentTimeMillis, Runtime.runSync); if (Option.isNone(tabId) && collection.item.length === 0) return ; return ( focusVisibleRingStyles({ className: [ tw`flex items-center rounded-lg border bg-white px-3 text-slate-500`, !renderProps.isFocusWithin && tw`border-slate-300`, ], }) } > } >

Visited pages

{(navigation) => ( {navigation.name} {(host) => ( focusVisibleRingStyles({ className: [ tw` group relative -mt-px flex cursor-pointer items-center gap-2.5 overflow-auto border bg-slate-50 px-4 py-6 text-sm transition-[border-color,outline-color,outline-width,background-color] last:rounded-b-lg odd:bg-white selected:bg-indigo-100 `, !renderProps.isFocused && tw`border-slate-200`, ], }) } id={host.id ?? ''} textValue={host.name ?? ''} >
{host.name}
{host.item?.length ?? 0} calls
)} )}

API Calls

focusVisibleRingStyles({ className: [tw`w-full`, renderProps.isEmpty && tw`min-h-0 flex-1`] }) } items={filteredRequests} onSelectionChange={flow(setRequestsSelection, Runtime.runPromise)} renderEmptyState={() => (

No calls yet

{"Let's try another one"}
)} selectedKeys={requestsSelection} selectionMode='multiple' > {(request) => ( focusVisibleRingStyles({ className: [ tw` -mt-px grid cursor-pointer grid-cols-[auto_auto_1fr_auto] grid-rows-[auto_auto] items-center gap-y-1.5 border bg-slate-50 p-4 text-slate-500 transition-[border-color,outline-color,outline-width,background-color] first:mt-0 first:rounded-t-lg first:border-t last:rounded-b-lg even:bg-white selected:bg-indigo-100 `, !renderProps.isFocused && tw`border-slate-200`, ], }) } id={request.id ?? ''} textValue={request.name ?? ''} > {({ isSelected }) => ( <>
{isSelected && }
{pipe( request.request, Option.liftPredicate(Schema.is(Postman.RequestClass)), Option.map(({ method }) => pipe( method, Match.value, Match.when('GET', () => tw`border-orange-200 bg-orange-50 text-orange-900`), Match.when('POST', () => tw`border-green-200 bg-green-50 text-green-900`), Match.orElse(() => tw`border-slate-200 bg-slate-50 text-slate-700`), (_) => [method ?? 'ETC', _] as const, ), ), Option.map(([method, className]) => (
{pipe(method, String.toLowerCase, String.capitalize)}
)), Option.getOrElse(() => null), )} {pipe( request.name ?? '', Schema.decode(Schema.URL), Effect.map((url) => ( <> {url.host} {url.pathname} )), Runtime.runSync, )} {Effect.gen(function* () { const variable = yield* Array.findFirst(request.variable ?? [], (_) => _.key === 'timestamp'); const timestamp = yield* pipe(variable, Struct.get('value'), Schema.decodeUnknown(Schema.Number)); const duration = Duration.subtract(currentTimeMillis, Duration.seconds(timestamp)); const sec = Math.floor(Duration.toSeconds(duration)); if (sec < 60) return `${sec.toString()} sec`; const min = Math.floor(sec / 60); if (min < 60) return `${min.toString()} min`; const hr = Math.floor(min / 60); if (hr < 24) return `${hr.toString()} hr`; const days = Math.floor(hr / 24); return `${days.toString()} days`; }).pipe( Effect.match({ onFailure: () => null, onSuccess: (_) => ( {_} ago ), }), Runtime.runSync, )} )}
)}
{Option.match(tabId, { onNone: () => ( <>

Recording paused

), onSome: () => ( <>

Recording API Calls

), })}
{Option.match(tabId, { onNone: () => ( ), onSome: () => ( ), })}
); }; const PopupPage = () => { const [loggedInMaybe] = Auth.useLoggedIn(); const loggedIn = Option.getOrElse(loggedInMaybe, () => false); return loggedIn ? : ; }; export default PopupPage; ================================================ FILE: apps/api-recorder-extension/src/postman.ts ================================================ import * as S from 'effect/Schema'; // Generated using: https://app.quicktype.io // Documentation: https://learning.postman.com/collection-format // JSON Schema: https://schema.postman.com/collection/json/v2.1.0/draft-07/collection.json const DEFAULT_NAME = 'API Recorder Collection'; const DEFAULT_SCHEMA = 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'; export const AuthType = S.Literal( 'apikey', 'awsv4', 'basic', 'bearer', 'digest', 'edgegrid', 'hawk', 'noauth', 'ntlm', 'oauth1', 'oauth2', ); export type AuthType = S.Schema.Type; export const VariableType = S.Literal('any', 'boolean', 'number', 'string'); export type VariableType = S.Schema.Type; export const FormParameterType = S.Literal('file', 'text'); export type FormParameterType = S.Schema.Type; export const Mode = S.Literal('file', 'formdata', 'graphql', 'raw', 'urlencoded'); export type Mode = S.Schema.Type; export class Cookie extends S.Class('Cookie')({ domain: S.String, expires: S.optional(S.Union(S.Null, S.String)), extensions: S.optional(S.Union(S.Array(S.Any), S.Null)), hostOnly: S.optional(S.Union(S.Boolean, S.Null)), httpOnly: S.optional(S.Union(S.Boolean, S.Null)), maxAge: S.optional(S.Union(S.Null, S.String)), name: S.optional(S.Union(S.Null, S.String)), path: S.String, secure: S.optional(S.Union(S.Boolean, S.Null)), session: S.optional(S.Union(S.Boolean, S.Null)), value: S.optional(S.Union(S.Null, S.String)), }) {} export class Response extends S.Class('Response')({ body: S.optional(S.Union(S.Null, S.String)), code: S.optional(S.Union(S.Number, S.Null)), cookie: S.optional(S.Union(S.Array(Cookie), S.Null)), header: S.optional( S.Union( S.Array( S.Union( S.suspend(() => Header), S.String, ), ), S.Null, S.String, ), ), id: S.optional(S.Union(S.Null, S.String)), originalRequest: S.optional( S.Union( S.suspend(() => RequestClass), S.Null, S.String, ), ), responseTime: S.optional(S.Union(S.Number, S.Null, S.String)), status: S.optional(S.Union(S.Null, S.String)), timings: S.optional(S.Union(S.Record({ key: S.String, value: S.Any }), S.Null)), }) {} export class ProxyConfig extends S.Class('ProxyConfig')({ disabled: S.optional(S.Union(S.Boolean, S.Null)), host: S.optional(S.Union(S.Null, S.String)), match: S.optional(S.Union(S.Null, S.String)), port: S.optional(S.Union(S.Number, S.Null)), tunnel: S.optional(S.Union(S.Boolean, S.Null)), }) {} export class Header extends S.Class
('Header')({ description: S.optional( S.Union( S.suspend(() => Description), S.Null, S.String, ), ), disabled: S.optional(S.Union(S.Boolean, S.Null)), key: S.String, value: S.String, }) {} export class Key extends S.Class('Key')({ src: S.optional(S.Any), }) {} export class Cert extends S.Class('Cert')({ src: S.optional(S.Any), }) {} export class Certificate extends S.Class('Certificate')({ cert: S.optional(S.Union(Cert, S.Null)), key: S.optional(S.Union(Key, S.Null)), matches: S.optional(S.Union(S.Array(S.String), S.Null)), name: S.optional(S.Union(S.Null, S.String)), passphrase: S.optional(S.Union(S.Null, S.String)), }) {} export class UrlEncodedParameter extends S.Class('UrlEncodedParameter')({ description: S.optional( S.Union( S.suspend(() => Description), S.Null, S.String, ), ), disabled: S.optional(S.Union(S.Boolean, S.Null)), key: S.String, value: S.optional(S.Union(S.Null, S.String)), }) {} export class FormParameter extends S.Class('FormParameter')({ contentType: S.optional(S.Union(S.Null, S.String)), description: S.optional( S.Union( S.suspend(() => Description), S.Null, S.String, ), ), disabled: S.optional(S.Union(S.Boolean, S.Null)), key: S.String, src: S.optional(S.Union(S.Array(S.Any), S.Null, S.String)), type: S.optional(S.Union(FormParameterType, S.Null)), value: S.optional(S.Union(S.Null, S.String)), }) {} export class File extends S.Class('File')({ content: S.optional(S.Union(S.Null, S.String)), src: S.optional(S.Union(S.Null, S.String)), }) {} export class Body extends S.Class('Body')({ disabled: S.optional(S.Union(S.Boolean, S.Null)), file: S.optional(S.Union(File, S.Null)), formdata: S.optional(S.Union(S.Array(FormParameter), S.Null)), graphql: S.optional(S.Union(S.Record({ key: S.String, value: S.Any }), S.Null)), mode: S.optional(S.Union(Mode, S.Null)), options: S.optional(S.Union(S.Record({ key: S.String, value: S.Any }), S.Null)), raw: S.optional(S.Union(S.Null, S.String)), urlencoded: S.optional(S.Union(S.Array(UrlEncodedParameter), S.Null)), }) {} export class RequestClass extends S.Class('RequestClass')({ auth: S.optional( S.Union( S.suspend(() => Auth), S.Null, ), ), body: S.optional(S.Union(Body, S.Null)), certificate: S.optional(S.Union(Certificate, S.Null)), description: S.optional( S.Union( S.suspend(() => Description), S.Null, S.String, ), ), header: S.optional(S.Union(S.Array(Header), S.Null, S.String)), method: S.optional(S.Union(S.Null, S.String)), proxy: S.optional(S.Union(ProxyConfig, S.Null)), url: S.optional( S.Union( S.suspend(() => UrlClass), S.Null, S.String, ), ), }) {} export class Item extends S.Class('Item')({ auth: S.optional( S.Union( S.suspend(() => Auth), S.Null, ), ), description: S.optional( S.Union( S.suspend(() => Description), S.Null, S.String, ), ), event: S.optional(S.Union(S.Array(S.suspend(() => Event)), S.Null)), id: S.optional(S.Union(S.Null, S.String)), item: S.optional(S.Union(S.Array(S.suspend((): S.Schema => Item)), S.Null)), name: S.optional(S.Union(S.Null, S.String)), protocolProfileBehavior: S.optional(S.Union(S.Record({ key: S.String, value: S.Any }), S.Null)), request: S.optional(S.Union(RequestClass, S.Null, S.String)), response: S.optional(S.Union(S.Array(Response), S.Null)), variable: S.optional(S.Union(S.Array(S.suspend(() => Variable)), S.Null)), }) {} export class CollectionVersionClass extends S.Class('CollectionVersionClass')({ identifier: S.optional(S.Union(S.Null, S.String)), major: S.Number, meta: S.optional(S.Any), minor: S.Number, patch: S.Number, }) {} export class Information extends S.Class('Information')({ _postman_id: S.optional(S.Union(S.Null, S.String)), description: S.optional( S.Union( S.suspend(() => Description), S.Null, S.String, ), ), name: S.String, schema: S.String, version: S.optional(S.Union(CollectionVersionClass, S.Null, S.String)), }) {} export class Variable extends S.Class('Variable')({ description: S.optional( S.Union( S.suspend(() => Description), S.Null, S.String, ), ), disabled: S.optional(S.Union(S.Boolean, S.Null)), id: S.optional(S.Union(S.Null, S.String)), key: S.optional(S.Union(S.Null, S.String)), name: S.optional(S.Union(S.Null, S.String)), system: S.optional(S.Union(S.Boolean, S.Null)), type: S.optional(S.Union(VariableType, S.Null)), value: S.optional(S.Any), }) {} export class Description extends S.Class('Description')({ content: S.optional(S.Union(S.Null, S.String)), type: S.optional(S.Union(S.Null, S.String)), version: S.optional(S.Any), }) {} export class QueryParam extends S.Class('QueryParam')({ description: S.optional(S.Union(Description, S.Null, S.String)), disabled: S.optional(S.Union(S.Boolean, S.Null)), key: S.optional(S.Union(S.Null, S.String)), value: S.optional(S.Union(S.Null, S.String)), }) {} export class PathClass extends S.Class('PathClass')({ type: S.optional(S.Union(S.Null, S.String)), value: S.optional(S.Union(S.Null, S.String)), }) {} export class UrlClass extends S.Class('UrlClass')({ hash: S.optional(S.Union(S.Null, S.String)), host: S.optional(S.Union(S.Array(S.String), S.Null, S.String)), path: S.optional(S.Union(S.Array(S.Union(PathClass, S.String)), S.Null, S.String)), port: S.optional(S.Union(S.Null, S.String)), protocol: S.optional(S.Union(S.Null, S.String)), query: S.optional(S.Union(S.Array(QueryParam), S.Null)), raw: S.optional(S.Union(S.Null, S.String)), variable: S.optional(S.Union(S.Array(Variable), S.Null)), }) {} export class Script extends S.Class ================================================ FILE: apps/desktop/src/renderer/main.tsx ================================================ import { Atom, Result, useAtomValue } from '@effect-atom/atom-react'; import { HttpClient, HttpClientResponse } from '@effect/platform'; import { Cause, Effect, Layer, pipe, Schema } from 'effect'; import { useEffect, useState } from 'react'; import { createRoot } from 'react-dom/client'; import Markdown from 'react-markdown'; import { addGlobalLayer, App as Client, configProviderFromMetaEnv, runtimeAtom } from '@the-dev-tools/client'; import { Button } from '@the-dev-tools/ui/button'; import { Logo } from '@the-dev-tools/ui/illustrations'; import { ProgressBar } from '@the-dev-tools/ui/progress-bar'; import { tw } from '@the-dev-tools/ui/tailwind-literal'; import { setTheme } from '@the-dev-tools/ui/theme'; import packageJson from '../../package.json'; import './styles.css'; setTheme(); pipe(configProviderFromMetaEnv({ VERSION: packageJson.version }), Layer.setConfigProvider, addGlobalLayer); // Trigger cleanup of old agent log files (7-day retention) window.electron.agentLog.cleanup(); const updateCheckAtom = runtimeAtom.atom( Effect.gen(function* () { const client = pipe( yield* HttpClient.HttpClient, HttpClient.followRedirects(3), HttpClient.withTracerPropagation(false), ); const version = yield* Effect.tryPromise(() => window.electron.update.check()); if (!version) return yield* new Cause.NoSuchElementException(); const { body } = yield* pipe( client.get(`https://api.github.com/repos/the-dev-tools/dev-tools/releases/tags/desktop@${version}`), Effect.flatMap( HttpClientResponse.schemaBodyJson( Schema.Struct({ body: Schema.String, }), ), ), ); return body; }), ); interface UpdateAvailableProps { children: string; } const UpdateAvailable = ({ children }: UpdateAvailableProps) => { const [state, setState] = useState<'init' | 'skip' | 'update'>('init'); if (state === 'skip') return ; return (
DevTools Studio
Update available!
{/* eslint-disable-next-line better-tailwindcss/no-unknown-classes */}
{children}
{state === 'init' && (
)} {state === 'update' && }
); }; const UpdateProgress = () => { const [percent, setPercent] = useState(0); useEffect(() => { window.electron.update.onProgress((_) => { setPercent(_.percent); }); }, []); return ; }; const LoadingScreen = () => (
Starting DevTools Studio...
); const StartupError = () => { const [isWiping, setIsWiping] = useState(false); return (
Failed to connect to the server
The server took too long to start. This can happen on first launch or if the database is corrupted.
This will delete all local data and start fresh.
); }; const renderError = () => ; const finalizerAtom = Atom.make((_) => void _.addFinalizer(() => void window.electron.onCloseDone())); const App = () => { useAtomValue(finalizerAtom); const updateCheck = useAtomValue(updateCheckAtom); return Result.match(updateCheck, { onFailure: () => , onInitial: () => , onSuccess: (_) => {_.value}, }); }; const root = createRoot(document.getElementById('root')!); window.electron.onClose(() => void root.unmount()); root.render(); ================================================ FILE: apps/desktop/src/renderer/styles.css ================================================ @import '@the-dev-tools/client/styles'; @plugin '@tailwindcss/typography'; @source '.'; ================================================ FILE: apps/desktop/tsconfig.json ================================================ { "extends": ["../../tsconfig.base.json"], "files": [], "references": [ { "path": "./tsconfig.lib.json" } ] } ================================================ FILE: apps/desktop/tsconfig.lib.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "dist", "jsx": "react-jsx", "types": ["vite/client"] }, "include": [".", "package.json"], "exclude": ["node_modules", "dist"], "references": [ { "path": "../../packages/worker-js" }, { "path": "../../packages/ui/tsconfig.lib.json" }, { "path": "../../packages/client/tsconfig.lib.json" }, { "path": "../../tools/eslint/tsconfig.lib.json" } ] } ================================================ FILE: docs/CODE-OF-CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: - Demonstrating empathy and kindness toward other people - Being respectful of differing opinions, viewpoints, and experiences - Giving and gracefully accepting constructive feedback - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience - Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: - The use of sexualized language or imagery, and sexual attention or advances of any kind - Trolling, insulting or derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or email address, without their explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [help@dev.tools](mailto:help@dev.tools). All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations ================================================ FILE: docs/CONTRIBUTING.md ================================================ # Contributing Guidelines Pull requests, bug reports, and all other forms of contribution are welcomed and highly encouraged! Reading and following these guidelines will help us make the contribution process easy and effective for everyone involved. It also communicates that you agree to respect the time of the developers managing and developing these open source projects. In return, we will reciprocate that respect by addressing your issue, assessing changes, and helping you finalize your pull requests.
Table of Contents
  1. Code of Conduct
  2. Getting Started
  3. Issues
  4. Pull Requests
  5. Environment Setup
  6. Tooling References
## Code of Conduct We take our open source community seriously and hold ourselves and other contributors to high standards of communication. By participating and contributing to this project, you agree to uphold the [Contributor Covenant Code of Conduct](CODE-OF-CONDUCT.md). ## Getting Started Contributions are made to this repo via Issues and Pull Requests (PRs). A few general guidelines that cover both: - To report security vulnerabilities, please contact us directly at [help@dev.tools](mailto:help@dev.tools) - Search for existing Issues and PRs before creating your own - We work hard to makes sure issues are handled in a timely manner but, depending on the impact, it could take a while to investigate the root cause. A friendly ping in the comment thread to the submitter or a contributor can help draw attention if your issue is blocking ## Issues Issues should be used to report a problem, request a new feature, or to discuss potential changes before a PR is created. When you create a new Issue, a template will be loaded that will guide you through collecting and providing the information we need to investigate. If you find an Issue that addresses the problem you're having, please add your own reproduction information to the existing issue rather than creating a new one. Adding a [reaction](https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) can also help be indicating to our maintainers that a particular problem is affecting more than just the reporter. ## Pull Requests PRs are always welcome and can be a quick way to get your fix or improvement slated for the next release. In general, PRs should: - Only fix/add the functionality in question - Address a single concern in the least number of changed lines as possible - Include a [Version Plan](https://nx.dev/recipes/nx-release/file-based-versioning-version-plans#create-version-plans) describing the changes and semantic versioning information. You can run `nx release plan` to generate it with an interactive guide - Be accompanied by a complete Pull Request template (loaded automatically when a PR is created) For changes that address core functionality or would require breaking changes (e.g. a major release), it's best to open an Issue to discuss your proposal first. This is not required but can save time creating and reviewing changes. In general, we follow the ["fork-and-pull" Git workflow](https://github.com/susam/gitpr) 1. Fork the repository to your own GitHub account 2. Clone the project to your machine 3. Create a branch locally with a succinct but descriptive name 4. Commit changes to the branch 5. Ensure all formatting and testing checks pass by running `task lint` and `task test` 6. Push changes to your fork 7. Open a PR in our repository and follow the PR template so that we can efficiently review the changes ## Environment Setup The development environment for this project is set up using Nix Flakes for full reproducibility. You may chooose to set your environment manually, but we won't be able to help you if issues arise. For best results set up the following software: 1. [Nix](https://nixos.org/) with Flakes enabled. We recommend using [Determinate Nix Installer](https://github.com/DeterminateSystems/nix-installer) for good defaults 2. [Direnv](https://direnv.net/), see [installation instructions](https://direnv.net/docs/installation.html). Run `direnv allow` in project root to activate the environment 3. [Visual Studio Code](https://code.visualstudio.com/) with [recommended extensions](https://code.visualstudio.com/docs/editor/extension-marketplace#_recommended-extensions) Make sure to update project dependencies by running `pnpm install` and `go install tool` before making any changes. ## Tooling References This is a list of tools that we frequently use throughout the project, along with accompanying references. It is helpful for quickly looking up certain information during development. ### General - Nix Flakes - [Wiki](https://wiki.nixos.org/wiki/Flakes) - [Install](https://github.com/DeterminateSystems/nix-installer#readme) - direnv - [Docs](https://direnv.net/) - [Install](https://direnv.net/docs/installation.html) - pnpm - [Docs](https://pnpm.io/motivation) - Nx - [Docs](https://nx.dev/getting-started/intro) - [Plugins](https://nx.dev/plugin-registry) - [API](https://nx.dev/nx-api) ### RPC - Connect RPC - [Docs](https://connectrpc.com/docs/introduction) - Protobuf - [Docs](https://protobuf.dev/) ### Server #### Database - Libsql - Unix like - [Website](https://turso.tech/libsql) - [Github](https://github.com/tursodatabase/go-libsql) - Sqlite - Windows - [Website](https://www.sqlite.org/) - [Go Reference](https://pkg.go.dev/github.com/mattn/go-sqlite3) #### RPC - Connect for Go [Docs](https://connectrpc.com/docs/go/getting-started) #### General - Ulid - [Go Reference](https://pkg.go.dev/github.com/oklog/ulid) - GVal - [Go Reference](https://pkg.go.dev/github.com/PaesslerAG/gval) - Compressed - [Go Reference](https://pkg.go.dev/github.com/klauspost/compress) ### Client #### General - Effect - [Docs](https://effect.website/docs/) - [API](https://effect-ts.github.io/effect/docs/effect) - Schema - [Docs](https://effect.website/docs/schema/introduction/) - [API](https://effect-ts.github.io/effect/effect/Schema.ts.html) - Platform - [Docs](https://effect.website/docs/platform/introduction/) - [API](https://effect-ts.github.io/effect/docs/platform) - Faker - [API](https://fakerjs.dev/api/) - React Email - [Docs](https://react.email/docs/introduction) - [Components](https://react.email/components) - [Templates](https://react.email/templates) - Electron Vite - [Docs](https://electron-vite.org/guide/) #### RPC - Connect for Web - [Docs](https://connectrpc.com/docs/web/getting-started) - Connect for TanStack Query - [Docs](https://github.com/connectrpc/connect-query-es) - Protobuf ES - [Docs](https://github.com/bufbuild/protobuf-es/blob/main/MANUAL.md) #### React - TanStack Router - [Docs](https://tanstack.com/router/latest/docs/framework/react/overview) - TanStack Query - [Docs](https://tanstack.com/query/latest/docs/framework/react/overview) - TanStack Table - [Docs](https://tanstack.com/table/latest/docs/introduction) - normy - [Core](https://github.com/klis87/normy#readme) - [TanStack Query](https://github.com/klis87/normy/tree/master/packages/normy-react-query#readme) - React Flow - [Docs](https://reactflow.dev/learn) - [API](https://reactflow.dev/api-reference) - [Components](https://reactflow.dev/components) - [Examples](https://reactflow.dev/examples) #### UI - Tailwind CSS - [Docs](https://tailwindcss.com/docs/installation) - Tailwind Variants - [Docs](https://www.tailwind-variants.org/docs/introduction) - React Aria - [Docs](https://react-spectrum.adobe.com/react-aria/components.html) - Tailwind Starter - [GitHub](https://github.com/adobe/react-spectrum/tree/main/starters/tailwind) - [Storybook](https://react-spectrum.adobe.com/react-aria-tailwind-starter/) - React Icons - [Docs](https://react-icons.github.io/react-icons) #### Design - Figma - [Team](https://www.figma.com/files/team/1400037238435055305/all-projects) - [File](https://www.figma.com/design/psOxuc1CnTJTklIvga49To/DevTools) - Token Studio - [Docs](https://docs.tokens.studio/) ================================================ FILE: docs/PULL_REQUEST_TEMPLATE.md ================================================ By submitting a PR to this repository, you agree to the terms within the [Contributor Covenant Code of Conduct](https://github.com/the-dev-tools/dev-tools/blob/main/docs/CODE-OF-CONDUCT.md). Please see the [contributing guidelines](https://github.com/the-dev-tools/dev-tools/blob/main/docs/CONTRIBUTING.md) for how to create and submit a high-quality PR for this repo. ### Description > Describe the purpose of this PR along with any background information and the impacts of the proposed change. For the benefit of the community, please do not assume prior context. > > Provide details that support your chosen implementation, including: breaking changes, alternatives considered, changes to the API, etc. > > If the UI is being changed, please provide screenshots. ### References > Include any links supporting this change such as a: > > - GitHub Issue/PR number addressed or fixed > - StackOverflow post > - Related pull requests/issues from other repos > > If there are no references, simply delete this section. ### Testing > Describe how this can be tested by reviewers. Be specific about anything not tested and reasons why. > > Please include any manual steps for testing end-to-end or functionality not covered by unit/integration tests. > > Also include details of the environment this PR was developed in (platform/browser version). ### Checklist - [ ] I have added a Version Plan for new/changed functionality in this PR - [ ] All checks for formatting and tests are passing ================================================ FILE: docs/cli.md ================================================ # DevTools CLI Guide ## Overview The DevTools CLI (`devtoolscli`) is the command-line companion to the desktop application. It lets you execute exported workspaces, validate flows in continuous integration pipelines, and produce machine-readable reports. The CLI runs everything locally against an in-memory SQLite database, mirroring the behaviour of the server so you can rely on consistent results between manual testing and automated checks. ## Installation The preferred installation method is the published release bundle. On macOS and Linux you can use the helper script: ``` curl -fsSL https://raw.githubusercontent.com/the-dev-tools/dev-tools/main/apps/cli/install.sh | bash ``` By default the script installs the binary to `/usr/local/bin`. Set `INSTALL_DIR` if you need another location. The CLI is cross-platform; Windows users can download the corresponding `.exe` from the releases page and place it somewhere on the `PATH`. If you are hacking locally, run `pnpm install` and then `pnpm nx run cli:build` from the repo root. The compiled binary will appear under `apps/cli/dist`. Regardless of how you install, you can confirm your version with `devtoolscli version`. ## Running Flows from YAML Export your workspace from the desktop app to produce a `.yamlflow.yaml` file. The CLI consumes that file with: ``` devtoolscli flow run path/to/workspace.yamlflow.yaml FlowName ``` If you omit the flow name the CLI reads the `run:` section and executes each entry in order, honouring `depends_on`. You can also point it at a simplified YAML using the same command; the importer handles both the legacy and the new structure transparently. The CLI spins up a temporary SQLite database, imports every collection, endpoint, example, and flow, then runs the requested flow(s) through the same runner used by the server. Names, node types, assertions, scripts, and loop semantics all match what you see in the application. ## Environment Variable Overrides Workspace environments travel with the exported data, but CI pipelines often need runtime overrides. Declare overrides in the `env:` block at the top level of your YAML: ```yaml env: LOGIN_EMAIL: '#env:LOGIN_EMAIL' LOGIN_PASSWORD: '#env:LOGIN_PASSWORD' ``` `#env:NAME` instructs the CLI to read the process environment (`os.Getenv("NAME")`). If the variable is missing, the CLI falls back to whatever value is stored in the workspace. You can mix literal fallbacks and template references: ```yaml env: API_KEY: 'plain-text-fallback' API_SECRET: '{{ secrets.MY_SECRET }}' ``` The importer normalises `${{ secrets.MY_SECRET }}` (and other `$` forms) to `#env:MY_SECRET`, so in GitHub Actions you can expose secrets with: ```yaml steps: - run: devtoolscli flow run workspace.yamlflow.yaml FlowA env: LOGIN_EMAIL: ${{ secrets.LOGIN_EMAIL }} LOGIN_PASSWORD: ${{ secrets.LOGIN_PASSWORD }} ``` Inside the flow you continue to reference `{{ env.LOGIN_EMAIL }}` exactly as you would in the desktop app. ## Reports By default the CLI prints a console report showing node order, duration, and status. You can request additional outputs with `--report format[:path]`. Supported formats are `console`, `json`, and `junit`. Examples: ``` devtoolscli flow run workspace.yamlflow.yaml FlowA --report json:flow.json devtoolscli flow run workspace.yamlflow.yaml FlowA --report console --report junit:flow.xml ``` You can specify the flag multiple times. When writing JSON or JUnit reports, the CLI appends flow results after each run and flushes them on exit. This is useful for CI systems that collect test artifacts. ## Continuous Integration Tips 1. Check in your YAML flows and run them on every pull request. Combine `--report junit:…` with the CI system’s test report collector. 2. Use the `env:` block together with project secrets to avoid storing plaintext credentials in the repository. 3. If your flows depend on external APIs, run them against staging environments or mock servers to keep CI stable. 4. Consider adding `devtoolscli version` to your pipeline logs so you can diagnose regressions quickly. A minimal GitHub Actions job looks like: ```yaml jobs: flow-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v3 with: version: 9 - run: pnpm install --frozen-lockfile - run: pnpm nx run cli:build - run: ./apps/cli/dist/devtoolscli flow run flows.yamlflow.yaml env: LOGIN_EMAIL: ${{ secrets.LOGIN_EMAIL }} LOGIN_PASSWORD: ${{ secrets.LOGIN_PASSWORD }} ``` ## Unified Binary & CLI Mode The binary is primarily a **server**. It can optionally act as a CLI when the `cli` build tag is included and the `DEVTOOLS_MODE` environment variable is set. ### Build Variants | Variant | Build Command | Includes CLI | Typical Use | | ----------- | ---------------------------------- | ------------ | ------------------------------------------ | | Server-only | `go build -o devtools .` | No | Desktop app backend, production deployment | | Unified | `go build -tags cli -o devtools .` | Yes | All-in-one distribution, CI pipelines | The server-only build excludes CLI dependencies (Cobra, Viper, config management) and produces a smaller binary. The unified build links the CLI in via the `cli` build tag. ### Runtime Mode Selection Set the `DEVTOOLS_MODE` environment variable to switch modes: | Value | Behaviour | | --------- | ----------------------------------------------------- | | `server` | Starts the HTTP server (Connect RPC) | | `cli` | Runs the CLI (Cobra commands: flow run, import, etc.) | | _(unset)_ | Defaults to `server` | Any other value is rejected with an error. ```bash # Run as server (default, DEVTOOLS_MODE unset) ./devtools # Run as server (explicit) DEVTOOLS_MODE=server ./devtools # Run as CLI DEVTOOLS_MODE=cli ./devtools flow run workspace.yamlflow.yaml FlowA ``` If `DEVTOOLS_MODE=cli` is set on a server-only build (compiled without `-tags cli`), the binary prints an error and exits. ### Desktop App Integration The desktop Electron app spawns the binary as its backend. No `DEVTOOLS_MODE` is needed since server is the default: ```typescript Command.env({ DB_MODE: 'local', DB_NAME: 'state', DB_PATH: app.getPath('userData'), DB_ENCRYPTION_KEY: 'secret', HMAC_SECRET: 'secret', }); ``` The server requires the same environment variables as before (`DB_MODE`, `DB_NAME`, `DB_PATH`, `DB_ENCRYPTION_KEY`, `HMAC_SECRET`). ### Source Layout - `apps/cli/main.go` — Entry point with mode switch and constants (`EnvDevToolsMode`, `ModeServer`, `ModeCLI`) - `apps/cli/mode_cli.go` — Build-tagged file (`//go:build cli`) that wires the CLI commands - `packages/server/cmd/serverrun/serverrun.go` — Extracted server startup logic, importable from any module in the workspace ## Debugging and Troubleshooting - **Flow not found**: Ensure the `run` entry or flow name matches the exported data exactly (case-sensitive). Use `devtoolscli flow run workspace.yamlflow.yaml` without a name to list flows. - **Missing environment variable**: When a `#env:NAME` override cannot resolve, the CLI logs the placeholder but continues with the stored value. Set the value explicitly in your CI environment or provide a literal fallback in the YAML. - **Node failures**: The console report shows the first error encountered. Re-run with `LOG_LEVEL=DEBUG` to see detailed HTTP preparation and assertion logs. - **External dependencies**: The CLI does not stub network calls. If you need deterministic runs, point your environment variables at mock servers or wrap the flows with conditionals. ## Getting Help For bugs or feature requests file an issue on GitHub with the CLI version (`devtoolscli version`), the flow snippet that fails, and the console report. Pull requests are welcome; consult `docs/CONTRIBUTING.md` for coding standards and testing expectations. The CLI lives under `apps/cli/`; tests are in `apps/cli/cmd` and sample flows in `apps/cli/test/yamlflow/`. ================================================ FILE: eslint.config.ts ================================================ import { ConfigArray } from 'typescript-eslint'; import base from '@the-dev-tools/eslint-config'; const config: ConfigArray = [ ...base, { ignores: ['apps', 'tools', 'packages'], }, ]; export default config; ================================================ FILE: flake.nix ================================================ { inputs = { cache-nix-action.url = "github:nix-community/cache-nix-action"; flake-parts.url = "github:hercules-ci/flake-parts"; gha-nix-develop.url = "github:nicknovitski/nix-develop"; # Switch back to unstable once this PR lands # https://github.com/NixOS/nixpkgs/pull/465669 # https://github.com/NixOS/nixpkgs/issues/376958 nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05"; systems.url = "github:nix-systems/default"; # Follows flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs"; gha-nix-develop.inputs.nixpkgs.follows = "nixpkgs"; # Meta cache-nix-action.flake = false; }; outputs = inputs @ {flake-parts, ...}: flake-parts.lib.mkFlake {inherit inputs;} { systems = import inputs.systems; perSystem = { config, inputs', pkgs, self', ... }: { packages.gha-nix-develop = inputs'.gha-nix-develop.packages.default; packages.gha-save-from-gc = (import "${inputs.cache-nix-action}/saveFromGC.nix" { inherit pkgs inputs; derivations = [config.devShells.runner]; }).package; devShells.runner = let gha-scripts = pkgs.writeShellApplication { name = "gha-scripts"; runtimeInputs = with pkgs; [pnpm jq]; runtimeEnv.NODE_OPTIONS = "--disable-warning=ExperimentalWarning"; text = ''pnpm run --filter="*/gha-scripts" cli "$@"''; }; in pkgs.mkShell { shellHook = let export = { path, check ? path, }: '' [ -n "${check}" ] && mkdir --parent "${path}" && export PATH="${path}:$PATH" ''; in '' # Export Go and PNPM paths ${export {path = "$(go env GOBIN)";}} ${export { path = "$(go env GOPATH)/bin"; check = "$(go env GOPATH)"; }} ${export {path = "$(pnpm bin)";}} ''; nativeBuildInputs = with pkgs; [ gcc gh gha-scripts go_1_25 go-task jq nodejs_latest pnpm protoc-gen-connect-go ]; }; devShells.default = pkgs.mkShell { # Specify Nixpkgs path for improved nixd intellisense NIX_PATH = ["nixpkgs=${inputs.nixpkgs}"]; # Use Electron binary from Nixpkgs in development for NixOS compatibility ELECTRON_SKIP_BINARY_DOWNLOAD = 1; ELECTRON_EXEC_PATH = "${pkgs.electron}/bin/electron"; shellHook = '' ${self'.devShells.runner.shellHook} if [ -n "''${GEMINI_CLI-}" ]; then export NX_TUI=false export TASK_OUTPUT=prefixed fi ''; nativeBuildInputs = self'.devShells.runner.nativeBuildInputs ++ (with pkgs; [ alejandra gopls nixd ]); }; }; }; } ================================================ FILE: go.work ================================================ go 1.25 use ( ./apps/cli ./packages/auth-lib ./packages/db ./packages/server ./packages/spec ./tools/benchmark ./tools/go-tool ./tools/modmigrate ./tools/norawsql ./tools/notxread ) ================================================ FILE: go.work.sum ================================================ cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= cel.dev/expr v0.16.2/go.mod h1:gXngZQMkWJoSbE8mOzehJlXQyubn/Vg0vR9/F3W7iw8= cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.51.0 h1:PvKAVQWCtlGUSlZkGW3QLelKaWq7KYv/MW1EboG8bfM= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms= cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= cloud.google.com/go v0.118.3/go.mod h1:Lhs3YLnBlwJ4KA6nuObNMZ/fCbOQBPuWKPoE0Wa/9Vc= cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q= cloud.google.com/go/accessapproval v1.8.6/go.mod h1:FfmTs7Emex5UvfnnpMkhuNkRCP85URnBFt5ClLxhZaQ= cloud.google.com/go/accessapproval v1.8.8/go.mod h1:RFwPY9JDKseP4gJrX1BlAVsP5O6kI8NdGlTmaeDefmk= cloud.google.com/go/accesscontextmanager v1.9.6/go.mod h1:884XHwy1AQpCX5Cj2VqYse77gfLaq9f8emE2bYriilk= cloud.google.com/go/accesscontextmanager v1.9.7/go.mod h1:i6e0nd5CPcrh7+YwGq4bKvju5YB9sgoAip+mXU73aMM= cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w= cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE= cloud.google.com/go/aiplatform v1.109.0/go.mod h1:4rwKOMdubQOND81AlO3EckcskvEFCYSzXKfn42GMm8k= cloud.google.com/go/alloydb v1.14.0/go.mod h1:OTBY1HoL0Z8PsHoMMVhkaUPKyY8oP7hzIAe/Dna6UHk= cloud.google.com/go/alloydbconn v1.13.2/go.mod h1:0wlYQAOr2XuvxYsvNNVckmG2v17WVUKzMD+gmTOibSU= cloud.google.com/go/analytics v0.28.1/go.mod h1:iPaIVr5iXPB3JzkKPW1JddswksACRFl3NSHgVHsuYC4= cloud.google.com/go/analytics v0.30.1/go.mod h1:V/FnINU5kMOsttZnKPnXfKi6clJUHTEXUKQjHxcNK8A= cloud.google.com/go/apigateway v1.7.6/go.mod h1:SiBx36VPjShaOCk8Emf63M2t2c1yF+I7mYZaId7OHiA= cloud.google.com/go/apigateway v1.7.7/go.mod h1:j1bCmrUK1BzVHpiIyTApxB7cRyhivKzltqLmp6j6i7U= cloud.google.com/go/apigeeconnect v1.7.6/go.mod h1:zqDhHY99YSn2li6OeEjFpAlhXYnXKl6DFb/fGu0ye2w= cloud.google.com/go/apigeeconnect v1.7.7/go.mod h1:ftGK3nca0JePiVLl0A6alaMjKdOc5C+sAkFMyH2RH8U= cloud.google.com/go/apigeeregistry v0.9.6/go.mod h1:AFEepJBKPtGDfgabG2HWaLH453VVWWFFs3P4W00jbPs= cloud.google.com/go/apigeeregistry v0.10.0/go.mod h1:SAlF5OhKvyLDuwWAaFAIVJjrEqKRrGTPkJs+TWNnSqg= cloud.google.com/go/appengine v1.9.6/go.mod h1:jPp9T7Opvzl97qytaRGPwoH7pFI3GAcLDaui1K8PNjY= cloud.google.com/go/appengine v1.9.7/go.mod h1:y1XpGVeAhbsNzHida79cHbr3pFRsym0ob8xnC8yphbo= cloud.google.com/go/area120 v0.9.6/go.mod h1:qKSokqe0iTmwBDA3tbLWonMEnh0pMAH4YxiceiHUed4= cloud.google.com/go/area120 v0.9.7/go.mod h1:5nJ0yksmjOMfc4Zpk+okWfJ3A1004FvB82rfia+ZLaY= cloud.google.com/go/artifactregistry v1.17.1/go.mod h1:06gLv5QwQPWtaudI2fWO37gfwwRUHwxm3gA8Fe568Hc= cloud.google.com/go/artifactregistry v1.17.2/go.mod h1:h4CIl9TJZskg9c9u1gC9vTsOTo1PrAnnxntprqS3AjM= cloud.google.com/go/asset v1.21.1/go.mod h1:7AzY1GCC+s1O73yzLM1IpHFLHz3ws2OigmCpOQHwebk= cloud.google.com/go/asset v1.22.0/go.mod h1:q80JP2TeWWzMCazYnrAfDf36aQKf1QiKzzpNLflJwf8= cloud.google.com/go/assuredworkloads v1.12.6/go.mod h1:QyZHd7nH08fmZ+G4ElihV1zoZ7H0FQCpgS0YWtwjCKo= cloud.google.com/go/assuredworkloads v1.13.0/go.mod h1:o/oHEOnUlribR+uJWTKQo8A5RhSl9K9FNeMOew4TJ3M= cloud.google.com/go/auth v0.3.0/go.mod h1:lBv6NKTWp8E3LPzmO1TbiiRKc4drLOfHsgmlH9ogv5w= cloud.google.com/go/auth v0.6.0/go.mod h1:b4acV+jLQDyjwm4OXHYjNvRi4jvGBzHWJRtJcy+2P4g= cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= cloud.google.com/go/auth v0.16.0/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4= cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA= cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/automl v1.14.7/go.mod h1:8a4XbIH5pdvrReOU72oB+H3pOw2JBxo9XTk39oljObE= cloud.google.com/go/automl v1.15.0/go.mod h1:U9zOtQb8zVrFNGTuW3BfxeqmLyeleLgT9B12EaXfODg= cloud.google.com/go/baremetalsolution v1.3.6/go.mod h1:7/CS0LzpLccRGO0HL3q2Rofxas2JwjREKut414sE9iM= cloud.google.com/go/baremetalsolution v1.4.0/go.mod h1:K6C6g4aS8LW95I0fEHZiBsBlh0UxwDLGf+S/vyfXbvg= cloud.google.com/go/batch v1.12.2/go.mod h1:tbnuTN/Iw59/n1yjAYKV2aZUjvMM2VJqAgvUgft6UEU= cloud.google.com/go/batch v1.13.0/go.mod h1:yHFeqBn8wUjmJs4sYbwZ7N3HdeGA+FkPAXjoCKMwGak= cloud.google.com/go/beyondcorp v1.1.6/go.mod h1:V1PigSWPGh5L/vRRmyutfnjAbkxLI2aWqJDdxKbwvsQ= cloud.google.com/go/beyondcorp v1.2.0/go.mod h1:sszcgxpPPBEfLzbI0aYCTg6tT1tyt3CmKav3NZIUcvI= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/bigquery v1.69.0/go.mod h1:TdGLquA3h/mGg+McX+GsqG9afAzTAcldMjqhdjHTLew= cloud.google.com/go/bigquery v1.72.0/go.mod h1:GUbRtmeCckOE85endLherHD9RsujY+gS7i++c1CqssQ= cloud.google.com/go/bigtable v1.37.0/go.mod h1:HXqddP6hduwzrtiTCqZPpj9ij4hGZb4Zy1WF/dT+yaU= cloud.google.com/go/bigtable v1.40.1/go.mod h1:LtPzCcrAFaGRZ82Hs8xMueUeYW9Jw12AmNdUTMfDnh4= cloud.google.com/go/billing v1.20.4/go.mod h1:hBm7iUmGKGCnBm6Wp439YgEdt+OnefEq/Ib9SlJYxIU= cloud.google.com/go/billing v1.21.0/go.mod h1:ZGairB3EVnb3i09E2SxFxo50p5unPaMTuo1jh6jW9js= cloud.google.com/go/binaryauthorization v1.9.5/go.mod h1:CV5GkS2eiY461Bzv+OH3r5/AsuB6zny+MruRju3ccB8= cloud.google.com/go/binaryauthorization v1.10.0/go.mod h1:WOuiaQkI4PU/okwrcREjSAr2AUtjQgVe+PlrXKOmKKw= cloud.google.com/go/certificatemanager v1.9.5/go.mod h1:kn7gxT/80oVGhjL8rurMUYD36AOimgtzSBPadtAeffs= cloud.google.com/go/certificatemanager v1.9.6/go.mod h1:vWogV874jKZkSRDFCMM3r7wqybv8WXs3XhyNff6o/Zo= cloud.google.com/go/channel v1.19.5/go.mod h1:vevu+LK8Oy1Yuf7lcpDbkQQQm5I7oiY5fFTn3uwfQLY= cloud.google.com/go/channel v1.20.0/go.mod h1:nBR1Lz+/1TjSA16HTllvW9Y+QULODj3o3jEKrNNeOp4= cloud.google.com/go/cloudbuild v1.22.2/go.mod h1:rPyXfINSgMqMZvuTk1DbZcbKYtvbYF/i9IXQ7eeEMIM= cloud.google.com/go/cloudbuild v1.23.1/go.mod h1:Gh/k1NnFRw1DkhekO2BaR4MTg30Op6EQQHCUZCIyTAg= cloud.google.com/go/clouddms v1.8.7/go.mod h1:DhWLd3nzHP8GoHkA6hOhso0R9Iou+IGggNqlVaq/KZ4= cloud.google.com/go/clouddms v1.8.8/go.mod h1:QtCyw+a73dlkDb2q20aTAPvfaTZCepDDi6Gb1AKq0a4= cloud.google.com/go/cloudsqlconn v1.14.1/go.mod h1:pM5Xp20GsQosQ/cP9awtha5SMgmzbLubb/dbVsTg3Fo= cloud.google.com/go/cloudtasks v1.13.6/go.mod h1:/IDaQqGKMixD+ayM43CfsvWF2k36GeomEuy9gL4gLmU= cloud.google.com/go/cloudtasks v1.13.7/go.mod h1:H0TThOUG+Ml34e2+ZtW6k6nt4i9KuH3nYAJ5mxh7OM4= cloud.google.com/go/compute v1.6.1 h1:2sMmt8prCn7DPaG4Pmh0N3Inmc8cT8ae5k1M6VJ9Wqc= cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.38.0 h1:MilCLYQW2m7Dku8hRIIKo4r0oKastlD74sSu16riYKs= cloud.google.com/go/compute v1.38.0/go.mod h1:oAFNIuXOmXbK/ssXm3z4nZB8ckPdjltJ7xhHCdbWFZM= cloud.google.com/go/compute v1.49.1/go.mod h1:1uoZvP8Avyfhe3Y4he7sMOR16ZiAm2Q+Rc2P5rrJM28= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= cloud.google.com/go/contactcenterinsights v1.17.3/go.mod h1:7Uu2CpxS3f6XxhRdlEzYAkrChpR5P5QfcdGAFEdHOG8= cloud.google.com/go/contactcenterinsights v1.17.4/go.mod h1:kZe6yOnKDfpPz2GphDHynxk/Spx+53UX/pGf+SmWAKM= cloud.google.com/go/container v1.43.0/go.mod h1:ETU9WZ1KM9ikEKLzrhRVao7KHtalDQu6aPqM34zDr/U= cloud.google.com/go/container v1.45.0/go.mod h1:eB6jUfJLjne9VsTDGcH7mnj6JyZK+KOUIA6KZnYE/ds= cloud.google.com/go/containeranalysis v0.14.1/go.mod h1:28e+tlZgauWGHmEbnI5UfIsjMmrkoR1tFN0K2i71jBI= cloud.google.com/go/containeranalysis v0.14.2/go.mod h1:FjppROiUtP9cyMegdWdY/TsBSGc6kqh1GjA2NOJXXL8= cloud.google.com/go/datacatalog v1.26.0/go.mod h1:bLN2HLBAwB3kLTFT5ZKLHVPj/weNz6bR0c7nYp0LE14= cloud.google.com/go/datacatalog v1.26.1/go.mod h1:2Qcq8vsHNxMDgjgadRFmFG47Y+uuIVsyEGUrlrKEdrg= cloud.google.com/go/dataflow v0.11.0/go.mod h1:gNHC9fUjlV9miu0hd4oQaXibIuVYTQvZhMdPievKsPk= cloud.google.com/go/dataflow v0.11.1/go.mod h1:3s6y/h5Qz7uuxTmKJKBifkYZ3zs63jS+6VGtSu8Cf7Y= cloud.google.com/go/dataform v0.12.0/go.mod h1:PuDIEY0lSVuPrZqcFji1fmr5RRvz3DGz4YP/cONc8g4= cloud.google.com/go/dataform v0.12.1/go.mod h1:atGS8ReRjfNDUQib0X/o/7Gi2bqHI2G7/J86LKiGimE= cloud.google.com/go/datafusion v1.8.6/go.mod h1:fCyKJF2zUKC+O3hc2F9ja5EUCAbT4zcH692z8HiFZFw= cloud.google.com/go/datafusion v1.8.7/go.mod h1:4dkFb1la41qCEXh1AzYtFwl842bu2ikTUXyKhjvFCb0= cloud.google.com/go/datalabeling v0.9.6/go.mod h1:n7o4x0vtPensZOoFwFa4UfZgkSZm8Qs0Pg/T3kQjXSM= cloud.google.com/go/datalabeling v0.9.7/go.mod h1:EEUVn+wNn3jl19P2S13FqE1s9LsKzRsPuuMRq2CMsOk= cloud.google.com/go/dataplex v1.25.3/go.mod h1:wOJXnOg6bem0tyslu4hZBTncfqcPNDpYGKzed3+bd+E= cloud.google.com/go/dataplex v1.28.0/go.mod h1:VB+xlYJiJ5kreonXsa2cHPj0A3CfPh/mgiHG4JFhbUA= cloud.google.com/go/dataproc/v2 v2.11.2/go.mod h1:xwukBjtfiO4vMEa1VdqyFLqJmcv7t3lo+PbLDcTEw+g= cloud.google.com/go/dataproc/v2 v2.15.0/go.mod h1:tSdkodShfzrrUNPDVEL6MdH9/mIEvp/Z9s9PBdbsZg8= cloud.google.com/go/dataqna v0.9.7/go.mod h1:4ac3r7zm7Wqm8NAc8sDIDM0v7Dz7d1e/1Ka1yMFanUM= cloud.google.com/go/dataqna v0.9.8/go.mod h1:2lHKmGPOqzzuqCc5NI0+Xrd5om4ulxGwPpLB4AnFgpA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/datastore v1.20.0/go.mod h1:uFo3e+aEpRfHgtp5pp0+6M0o147KoPaYNaPAKpfh8Ew= cloud.google.com/go/datastore v1.21.0/go.mod h1:9l+KyAHO+YVVcdBbNQZJu8svF17Nw5sMKuFR0LYf1nY= cloud.google.com/go/datastream v1.14.1/go.mod h1:JqMKXq/e0OMkEgfYe0nP+lDye5G2IhIlmencWxmesMo= cloud.google.com/go/datastream v1.15.1/go.mod h1:aV1Grr9LFon0YvqryE5/gF1XAhcau2uxN2OvQJPpqRw= cloud.google.com/go/deploy v1.27.2/go.mod h1:4NHWE7ENry2A4O1i/4iAPfXHnJCZ01xckAKpZQwhg1M= cloud.google.com/go/deploy v1.27.3/go.mod h1:7LFIYYTSSdljYRqY3n+JSmIFdD4lv6aMD5xg0crB5iw= cloud.google.com/go/dialogflow v1.68.2/go.mod h1:E0Ocrhf5/nANZzBju8RX8rONf0PuIvz2fVj3XkbAhiY= cloud.google.com/go/dialogflow v1.71.0/go.mod h1:mP4XrpgDvPYBP+cdLxFC1WJJlkwuy0H8L1Lada9No/M= cloud.google.com/go/dlp v1.23.0/go.mod h1:vVT4RlyPMEMcVHexdPT6iMVac3seq3l6b8UPdYpgFrg= cloud.google.com/go/dlp v1.27.0/go.mod h1:PY4DMzV7lqRC5JvpxL05fXNeL8dknxYpFp4WjxmE22M= cloud.google.com/go/documentai v1.37.0/go.mod h1:qAf3ewuIUJgvSHQmmUWvM3Ogsr5A16U2WPHmiJldvLA= cloud.google.com/go/documentai v1.39.0/go.mod h1:KmlLO93F7GRU8dENXRxvt+7V8o7eCG6Y6WDitKbcYJs= cloud.google.com/go/domains v0.10.6/go.mod h1:3xzG+hASKsVBA8dOPc4cIaoV3OdBHl1qgUpAvXK7pGY= cloud.google.com/go/domains v0.10.7/go.mod h1:T3WG/QUAO/52z4tUPooKS8AY7yXaFxPYn1V3F0/JbNQ= cloud.google.com/go/edgecontainer v1.4.3/go.mod h1:q9Ojw2ox0uhAvFisnfPRAXFTB1nfRIOIXVWzdXMZLcE= cloud.google.com/go/edgecontainer v1.4.4/go.mod h1:yyNVHsCKtsX/0mqFdbljQw0Uo660q2dlMPaiqYiC2Tg= cloud.google.com/go/errorreporting v0.3.2/go.mod h1:s5kjs5r3l6A8UUyIsgvAhGq6tkqyBCUss0FRpsoVTww= cloud.google.com/go/essentialcontacts v1.7.6/go.mod h1:/Ycn2egr4+XfmAfxpLYsJeJlVf9MVnq9V7OMQr9R4lA= cloud.google.com/go/essentialcontacts v1.7.7/go.mod h1:ytycWAEn/aKUMRKQPMVgMrAtphEMgjbzL8vFwM3tqXs= cloud.google.com/go/eventarc v1.15.5/go.mod h1:vDCqGqyY7SRiickhEGt1Zhuj81Ya4F/NtwwL3OZNskg= cloud.google.com/go/eventarc v1.17.0/go.mod h1:wB3NTIQ+l4QPirJiTMeU+YpSc5+iyoDYWV4n2/Vmh78= cloud.google.com/go/filestore v1.10.2/go.mod h1:w0Pr8uQeSRQfCPRsL0sYKW6NKyooRgixCkV9yyLykR4= cloud.google.com/go/filestore v1.10.3/go.mod h1:94ZGyLTx9j+aWKozPQ6Wbq1DuImie/L/HIdGMshtwac= cloud.google.com/go/firestore v1.6.1 h1:8rBq3zRjnHx8UtBvaOWqBB1xq9jH6/wltfQLlTMh2Fw= cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= cloud.google.com/go/firestore v1.18.0/go.mod h1:5ye0v48PhseZBdcl0qbl3uttu7FIEwEYVaWm0UIEOEU= cloud.google.com/go/firestore v1.20.0/go.mod h1:jqu4yKdBmDN5srneWzx3HlKrHFWFdlkgjgQ6BKIOFQo= cloud.google.com/go/functions v1.19.6/go.mod h1:0G0RnIlbM4MJEycfbPZlCzSf2lPOjL7toLDwl+r0ZBw= cloud.google.com/go/functions v1.19.7/go.mod h1:xbcKfS7GoIcaXr2FSwmtn9NXal1JR4TV6iYZlgXffwA= cloud.google.com/go/gkebackup v1.8.0/go.mod h1:FjsjNldDilC9MWKEHExnK3kKJyTDaSdO1vF0QeWSOPU= cloud.google.com/go/gkebackup v1.8.1/go.mod h1:GAaAl+O5D9uISH5MnClUop2esQW4pDa2qe/95A4l7YQ= cloud.google.com/go/gkeconnect v0.12.4/go.mod h1:bvpU9EbBpZnXGo3nqJ1pzbHWIfA9fYqgBMJ1VjxaZdk= cloud.google.com/go/gkeconnect v0.12.5/go.mod h1:wMD2RXcsAWlkREZWJDVeDV70PYka1iEb9stFmgpw+5o= cloud.google.com/go/gkehub v0.15.6/go.mod h1:sRT0cOPAgI1jUJrS3gzwdYCJ1NEzVVwmnMKEwrS2QaM= cloud.google.com/go/gkehub v0.16.0/go.mod h1:ADp27Ucor8v81wY+x/5pOxTorxkPj/xswH3AUpN62GU= cloud.google.com/go/gkemulticloud v1.5.3/go.mod h1:KPFf+/RcfvmuScqwS9/2MF5exZAmXSuoSLPuaQ98Xlk= cloud.google.com/go/gkemulticloud v1.5.4/go.mod h1:7l9+6Tp4jySSGj4PStO8CE6RrHFdcRARK4ScReHX1bU= cloud.google.com/go/grafeas v0.3.15/go.mod h1:irwcwIQOBlLBotGdMwme8PipnloOPqILfIvMwlmu8Pk= cloud.google.com/go/gsuiteaddons v1.7.7/go.mod h1:zTGmmKG/GEBCONsvMOY2ckDiEsq3FN+lzWGUiXccF9o= cloud.google.com/go/gsuiteaddons v1.7.8/go.mod h1:DBKNHH4YXAdd/rd6zVvtOGAJNGo0ekOh+nIjTUDEJ5U= cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= cloud.google.com/go/iap v1.11.2/go.mod h1:Bh99DMUpP5CitL9lK0BC8MYgjjYO4b3FbyhgW1VHJvg= cloud.google.com/go/iap v1.11.3/go.mod h1:+gXO0ClH62k2LVlfhHzrpiHQNyINlEVmGAE3+DB4ShU= cloud.google.com/go/ids v1.5.6/go.mod h1:y3SGLmEf9KiwKsH7OHvYYVNIJAtXybqsD2z8gppsziQ= cloud.google.com/go/ids v1.5.7/go.mod h1:N3ZQOIgIBwwOu2tzyhmh3JDT+kt8PcoKkn2BRT9Qe4A= cloud.google.com/go/iot v1.8.6/go.mod h1:MThnkiihNkMysWNeNje2Hp0GSOpEq2Wkb/DkBCVYa0U= cloud.google.com/go/iot v1.8.7/go.mod h1:HvVcypV8LPv1yTXSLCNK+YCtqGHhq+p0F3BXETfpN+U= cloud.google.com/go/kms v1.22.0/go.mod h1:U7mf8Sva5jpOb4bxYZdtw/9zsbIjrklYwPcvMk34AL8= cloud.google.com/go/kms v1.23.2/go.mod h1:rZ5kK0I7Kn9W4erhYVoIRPtpizjunlrfU4fUkumUp8g= cloud.google.com/go/language v1.14.5/go.mod h1:nl2cyAVjcBct1Hk73tzxuKebk0t2eULFCaruhetdZIA= cloud.google.com/go/language v1.14.6/go.mod h1:7y3J9OexQsfkWNGCxhT+7lb64pa60e12ZCoWDOHxJ1M= cloud.google.com/go/lifesciences v0.10.6/go.mod h1:1nnZwaZcBThDujs9wXzECnd1S5d+UiDkPuJWAmhRi7Q= cloud.google.com/go/lifesciences v0.10.7/go.mod h1:v3AbTki9iWttEls/Wf4ag3EqeLRHofploOcpsLnu7iY= cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw= cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn9GqyjaBT8/mA= cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= cloud.google.com/go/longrunning v0.6.6/go.mod h1:hyeGJUrPHcx0u2Uu1UFSoYZLn4lkMrccJig0t4FI7yw= cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY= cloud.google.com/go/managedidentities v1.7.6/go.mod h1:pYCWPaI1AvR8Q027Vtp+SFSM/VOVgbjBF4rxp1/z5p4= cloud.google.com/go/managedidentities v1.7.7/go.mod h1:nwNlMxtBo2YJMvsKXRtAD1bL41qiCI9npS7cbqrsJUs= cloud.google.com/go/maps v1.21.0/go.mod h1:cqzZ7+DWUKKbPTgqE+KuNQtiCRyg/o7WZF9zDQk+HQs= cloud.google.com/go/maps v1.26.0/go.mod h1:+auempdONAP8emtm48aCfNo1ZC+3CJniRA1h8J4u7bY= cloud.google.com/go/mediatranslation v0.9.6/go.mod h1:WS3QmObhRtr2Xu5laJBQSsjnWFPPthsyetlOyT9fJvE= cloud.google.com/go/mediatranslation v0.9.7/go.mod h1:mz3v6PR7+Fd/1bYrRxNFGnd+p4wqdc/fyutqC5QHctw= cloud.google.com/go/memcache v1.11.6/go.mod h1:ZM6xr1mw3F8TWO+In7eq9rKlJc3jlX2MDt4+4H+/+cc= cloud.google.com/go/memcache v1.11.7/go.mod h1:AU1jYlUqCihxapcJ1GGMtlMWDVhzjbfUWBXqsXa4rBg= cloud.google.com/go/metastore v1.14.7/go.mod h1:0dka99KQofeUgdfu+K/Jk1KeT9veWZlxuZdJpZPtuYU= cloud.google.com/go/metastore v1.14.8/go.mod h1:h1XI2LpD4ohJhQYn9TwXqKb5sVt6KSo47ft96SiFF1s= cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU= cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= cloud.google.com/go/monitoring v1.24.0/go.mod h1:Bd1PRK5bmQBQNnuGwHBfUamAV1ys9049oEPHnn4pcsc= cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= cloud.google.com/go/networkconnectivity v1.17.1/go.mod h1:DTZCq8POTkHgAlOAAEDQF3cMEr/B9k1ZbpklqvHEBtg= cloud.google.com/go/networkconnectivity v1.19.1/go.mod h1:Q5v6uNNNz8BP232uuXM66XgWML9m379xhwv58Y+8Kb0= cloud.google.com/go/networkmanagement v1.19.1/go.mod h1:icgk265dNnilxQzpr6rO9WuAuuCmUOqq9H6WBeM2Af4= cloud.google.com/go/networkmanagement v1.21.0/go.mod h1:clG/5Yt0wQ57qSH6Yh7oehQYlobHw3F6nb3Pn4ig5hU= cloud.google.com/go/networksecurity v0.10.6/go.mod h1:FTZvabFPvK2kR/MRIH3l/OoQ/i53eSix2KA1vhBMJec= cloud.google.com/go/networksecurity v0.10.7/go.mod h1:FgoictpfaJkeBlM1o2m+ngPZi8mgJetbFDH4ws1i2fQ= cloud.google.com/go/notebooks v1.12.6/go.mod h1:3Z4TMEqAKP3pu6DI/U+aEXrNJw9hGZIVbp+l3zw8EuA= cloud.google.com/go/notebooks v1.12.7/go.mod h1:uR9pxAkKmlNloibMr9Q1t8WhIu4P2JeqJs7c064/0Mo= cloud.google.com/go/optimization v1.7.6/go.mod h1:4MeQslrSJGv+FY4rg0hnZBR/tBX2awJ1gXYp6jZpsYY= cloud.google.com/go/optimization v1.7.7/go.mod h1:OY2IAlX23o52qwMAZ0w65wibKuV12a4x6IHDTCq6kcU= cloud.google.com/go/orchestration v1.11.9/go.mod h1:KKXK67ROQaPt7AxUS1V/iK0Gs8yabn3bzJ1cLHw4XBg= cloud.google.com/go/orchestration v1.11.10/go.mod h1:tz7m1s4wNEvhNNIM3JOMH0lYxBssu9+7si5MCPw/4/0= cloud.google.com/go/orgpolicy v1.15.0/go.mod h1:NTQLwgS8N5cJtdfK55tAnMGtvPSsy95JJhESwYHaJVs= cloud.google.com/go/orgpolicy v1.15.1/go.mod h1:bpvi9YIyU7wCW9WiXL/ZKT7pd2Ovegyr2xENIeRX5q0= cloud.google.com/go/osconfig v1.14.6/go.mod h1:LS39HDBH0IJDFgOUkhSZUHFQzmcWaCpYXLrc3A4CVzI= cloud.google.com/go/osconfig v1.15.1/go.mod h1:NegylQQl0+5m+I+4Ey/g3HGeQxKkncQ1q+Il4DZ8PME= cloud.google.com/go/oslogin v1.14.6/go.mod h1:xEvcRZTkMXHfNSKdZ8adxD6wvRzeyAq3cQX3F3kbMRw= cloud.google.com/go/oslogin v1.14.7/go.mod h1:NB6NqBHfDMwznePdBVX+ILllc1oPCdNSGp5u/WIyndY= cloud.google.com/go/phishingprotection v0.9.6/go.mod h1:VmuGg03DCI0wRp/FLSvNyjFj+J8V7+uITgHjCD/x4RQ= cloud.google.com/go/phishingprotection v0.9.7/go.mod h1:JTI4HNGyAbWolBoNOoCyCF0e3cqPNrYnlievHU49EwE= cloud.google.com/go/policytroubleshooter v1.11.6/go.mod h1:jdjYGIveoYolk38Dm2JjS5mPkn8IjVqPsDHccTMu3mY= cloud.google.com/go/policytroubleshooter v1.11.7/go.mod h1:JP/aQ+bUkt4Gz6lQXBi/+A/6nyNRZ0Pvxui5Xl9ieyk= cloud.google.com/go/privatecatalog v0.10.7/go.mod h1:Fo/PF/B6m4A9vUYt0nEF1xd0U6Kk19/Je3eZGrQ6l60= cloud.google.com/go/privatecatalog v0.10.8/go.mod h1:BkLHi+rtAGYBt5DocXLytHhF0n6F03Tegxgty40Y7aA= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/pubsub v1.49.0/go.mod h1:K1FswTWP+C1tI/nfi3HQecoVeFvL4HUOB1tdaNXKhUY= cloud.google.com/go/pubsub v1.50.1/go.mod h1:6YVJv3MzWJUVdvQXG081sFvS0dWQOdnV+oTo++q/xFk= cloud.google.com/go/pubsub/v2 v2.0.0/go.mod h1:0aztFxNzVQIRSZ8vUr79uH2bS3jwLebwK6q1sgEub+E= cloud.google.com/go/pubsublite v1.8.2/go.mod h1:4r8GSa9NznExjuLPEJlF1VjOPOpgf3IT6k8x/YgaOPI= cloud.google.com/go/recaptchaenterprise/v2 v2.20.4/go.mod h1:3H8nb8j8N7Ss2eJ+zr+/H7gyorfzcxiDEtVBDvDjwDQ= cloud.google.com/go/recaptchaenterprise/v2 v2.20.5/go.mod h1:TCHn8+vtwgygBOwwbUJgRi6R9qglIpTeImsWsWDr5Lo= cloud.google.com/go/recommendationengine v0.9.6/go.mod h1:nZnjKJu1vvoxbmuRvLB5NwGuh6cDMMQdOLXTnkukUOE= cloud.google.com/go/recommendationengine v0.9.7/go.mod h1:snZ/FL147u86Jqpv1j95R+CyU5NvL/UzYiyDo6UByTM= cloud.google.com/go/recommender v1.13.5/go.mod h1:v7x/fzk38oC62TsN5Qkdpn0eoMBh610UgArJtDIgH/E= cloud.google.com/go/recommender v1.13.6/go.mod h1:y5/5womtdOaIM3xx+76vbsiA+8EBTIVfWnxHDFHBGJM= cloud.google.com/go/redis v1.18.2/go.mod h1:q6mPRhLiR2uLf584Lcl4tsiRn0xiFlu6fnJLwCORMtY= cloud.google.com/go/redis v1.18.3/go.mod h1:x8HtXZbvMBDNT6hMHaQ022Pos5d7SP7YsUH8fCJ2Wm4= cloud.google.com/go/resourcemanager v1.10.6/go.mod h1:VqMoDQ03W4yZmxzLPrB+RuAoVkHDS5tFUUQUhOtnRTg= cloud.google.com/go/resourcemanager v1.10.7/go.mod h1:rScGkr6j2eFwxAjctvOP/8sqnEpDbQ9r5CKwKfomqjs= cloud.google.com/go/resourcesettings v1.8.3/go.mod h1:BzgfXFHIWOOmHe6ZV9+r3OWfpHJgnqXy8jqwx4zTMLw= cloud.google.com/go/retail v1.21.0/go.mod h1:LuG+QvBdLfKfO+7nnF3eA3l1j4TQw3Sg+UqlUorquRc= cloud.google.com/go/retail v1.25.1/go.mod h1:J75G8pd+DH0SHueL9IJw7Y5d2VhTsjFsk+F1t9f8jXc= cloud.google.com/go/run v1.10.0/go.mod h1:z7/ZidaHOCjdn5dV0eojRbD+p8RczMk3A7Qi2L+koHg= cloud.google.com/go/run v1.12.1/go.mod h1:DdMsf2m0/n3WHNDcyoqZmfE+LMd/uEJ7j1yIooDrgXU= cloud.google.com/go/scheduler v1.11.7/go.mod h1:gqYs8ndLx2M5D0oMJh48aGS630YYvC432tHCnVWN13s= cloud.google.com/go/scheduler v1.11.8/go.mod h1:bNKU7/f04eoM6iKQpwVLvFNBgGyJNS87RiFN73mIPik= cloud.google.com/go/secretmanager v1.14.7/go.mod h1:uRuB4F6NTFbg0vLQ6HsT7PSsfbY7FqHbtJP1J94qxGc= cloud.google.com/go/secretmanager v1.16.0/go.mod h1://C/e4I8D26SDTz1f3TQcddhcmiC3rMEl0S1Cakvs3Q= cloud.google.com/go/security v1.18.5/go.mod h1:D1wuUkDwGqTKD0Nv7d4Fn2Dc53POJSmO4tlg1K1iS7s= cloud.google.com/go/security v1.19.2/go.mod h1:KXmf64mnOsLVKe8mk/bZpU1Rsvxqc0Ej0A6tgCeN93w= cloud.google.com/go/securitycenter v1.36.2/go.mod h1:80ocoXS4SNWxmpqeEPhttYrmlQzCPVGaPzL3wVcoJvE= cloud.google.com/go/securitycenter v1.38.1/go.mod h1:Ge2D/SlG2lP1FrQD7wXHy8qyeloRenvKXeB4e7zO6z0= cloud.google.com/go/servicedirectory v1.12.6/go.mod h1:OojC1KhOMDYC45oyTn3Mup08FY/S0Kj7I58dxUMMTpg= cloud.google.com/go/servicedirectory v1.12.7/go.mod h1:gOtN+qbuCMH6tj2dqlDY3qQL7w3V0+nkWaZElnJK8Ps= cloud.google.com/go/shell v1.8.6/go.mod h1:GNbTWf1QA/eEtYa+kWSr+ef/XTCDkUzRpV3JPw0LqSk= cloud.google.com/go/shell v1.8.7/go.mod h1:OTke7qc3laNEW5Jr5OV9VR3IwU5x5VqGOE6705zFex4= cloud.google.com/go/spanner v1.82.0/go.mod h1:BzybQHFQ/NqGxvE/M+/iU29xgutJf7Q85/4U9RWMto0= cloud.google.com/go/spanner v1.86.1/go.mod h1:bbwCXbM+zljwSPLZ44wZOdzcdmy89hbUGmM/r9sD0ws= cloud.google.com/go/speech v1.27.1/go.mod h1:efCfklHFL4Flxcdt9gpEMEJh9MupaBzw3QiSOVeJ6ck= cloud.google.com/go/speech v1.28.1/go.mod h1:+EN8Zuy6y2BKe9P1RAmMaFPAgBns6m+XMgXAfkYtSSE= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= cloud.google.com/go/storage v1.49.0 h1:zenOPBOWHCnojRd9aJZAyQXBYqkJkdQS42dxL55CIMw= cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU= cloud.google.com/go/storage v1.53.0/go.mod h1:7/eO2a/srr9ImZW9k5uufcNahT2+fPb8w5it1i5boaA= cloud.google.com/go/storagetransfer v1.13.0/go.mod h1:+aov7guRxXBYgR3WCqedkyibbTICdQOiXOdpPcJCKl8= cloud.google.com/go/storagetransfer v1.13.1/go.mod h1:S858w5l383ffkdqAqrAA+BC7KlhCqeNieK3sFf5Bj4Y= cloud.google.com/go/talent v1.8.3/go.mod h1:oD3/BilJpJX8/ad8ZUAxlXHCslTg2YBbafFH3ciZSLQ= cloud.google.com/go/talent v1.8.4/go.mod h1:3yukBXUTVFNyKcJpUExW/k5gqEy8qW6OCNj7WdN0MWo= cloud.google.com/go/texttospeech v1.13.0/go.mod h1:g/tW/m0VJnulGncDrAoad6WdELMTes8eb77Idz+4HCo= cloud.google.com/go/texttospeech v1.16.0/go.mod h1:AeSkoH3ziPvapsuyI07TWY4oGxluAjntX+pF4PJ2jy0= cloud.google.com/go/tpu v1.8.3/go.mod h1:Do6Gq+/Jx6Xs3LcY2WhHyGwKDKVw++9jIJp+X+0rxRE= cloud.google.com/go/tpu v1.8.4/go.mod h1:ul0cyWSHr6jHGZYElZe6HvQn35VY93RAlwpDiSBRnPA= cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s= cloud.google.com/go/translate v1.10.3/go.mod h1:GW0vC1qvPtd3pgtypCv4k4U8B7EdgK9/QEF2aJEUovs= cloud.google.com/go/translate v1.12.5/go.mod h1:o/v+QG/bdtBV1d1edmtau0PwTfActvxPk/gtqdSDBi4= cloud.google.com/go/translate v1.12.7/go.mod h1:wwJp14NZyWvcrFANhIXutXj0pOBkYciBHwSlUOykcjI= cloud.google.com/go/video v1.24.0/go.mod h1:h6Bw4yUbGNEa9dH4qMtUMnj6cEf+OyOv/f2tb70G6Fk= cloud.google.com/go/video v1.27.1/go.mod h1:xzfAC77B4vtnbi/TT3UUxEjCa/+Ehy5EA8w470ytOig= cloud.google.com/go/videointelligence v1.12.6/go.mod h1:/l34WMndN5/bt04lHodxiYchLVuWPQjCU6SaiTswrIw= cloud.google.com/go/videointelligence v1.12.7/go.mod h1:XAk5hCMY+GihxJ55jNoMdwdXSNZnCl3wGs2+94gK7MA= cloud.google.com/go/vision/v2 v2.9.5/go.mod h1:1SiNZPpypqZDbOzU052ZYRiyKjwOcyqgGgqQCI/nlx8= cloud.google.com/go/vision/v2 v2.9.6/go.mod h1:lJC+vP15D5znJvHQYjEoTKnpToX1L93BUlvBmzM0gyg= cloud.google.com/go/vmmigration v1.8.6/go.mod h1:uZ6/KXmekwK3JmC8PzBM/cKQmq404TTfWtThF6bbf0U= cloud.google.com/go/vmmigration v1.9.1/go.mod h1:jI3lBlhQn9+BKIWE/MmMsOzGekCXCc34b1M0CihL3zY= cloud.google.com/go/vmwareengine v1.3.5/go.mod h1:QuVu2/b/eo8zcIkxBYY5QSwiyEcAy6dInI7N+keI+Jg= cloud.google.com/go/vmwareengine v1.3.6/go.mod h1:ps0rb+Skgpt9ppHYC0o5DqtJ5ld2FyS8sAqtbHH8t9s= cloud.google.com/go/vpcaccess v1.8.6/go.mod h1:61yymNplV1hAbo8+kBOFO7Vs+4ZHYI244rSFgmsHC6E= cloud.google.com/go/vpcaccess v1.8.7/go.mod h1:9RYw5bVvk4Z51Rc8vwXT63yjEiMD/l7XyEaDyrNHgmk= cloud.google.com/go/webrisk v1.11.1/go.mod h1:+9SaepGg2lcp1p0pXuHyz3R2Yi2fHKKb4c1Q9y0qbtA= cloud.google.com/go/webrisk v1.11.2/go.mod h1:yH44GeXz5iz4HFsIlGeoVvnjwnmfbni7Lwj1SelV4f0= cloud.google.com/go/websecurityscanner v1.7.6/go.mod h1:ucaaTO5JESFn5f2pjdX01wGbQ8D6h79KHrmO2uGZeiY= cloud.google.com/go/websecurityscanner v1.7.7/go.mod h1:ng/PzARaus3Bj4Os4LpUnyYHsbtJky1HbBDmz148v1o= cloud.google.com/go/workflows v1.14.2/go.mod h1:5nqKjMD+MsJs41sJhdVrETgvD5cOK3hUcAs8ygqYvXQ= cloud.google.com/go/workflows v1.14.3/go.mod h1:CC9+YdVI2Kvp0L58WajHpEfKJxhrtRh3uQ0SYWcmAk4= codeberg.org/go-fonts/liberation v0.5.0/go.mod h1:zS/2e1354/mJ4pGzIIaEtm/59VFCFnYC7YV6YdGl5GU= codeberg.org/go-latex/latex v0.1.0/go.mod h1:LA0q/AyWIYrqVd+A9Upkgsb+IqPcmSTKc9Dny04MHMw= codeberg.org/go-pdf/fpdf v0.10.0/go.mod h1:Y0DGRAdZ0OmnZPvjbMp/1bYxmIPxm0ws4tfoPOc4LjU= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= git.sr.ht/~sbinet/gg v0.6.0/go.mod h1:uucygbfC9wVPQIfrmwM2et0imr8L7KQWywX0xpFMm94= github.com/99designs/gqlgen v0.17.44/go.mod h1:UTCu3xpK2mLI5qcMNw+HKDiEL77it/1XtAjisC4sLwM= github.com/AssemblyAI/assemblyai-go-sdk v1.3.0/go.mod h1:H0naZbvpIW49cDA5ZZ/gggeXqi7ojSGB1mqshRk6kNE= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Code-Hex/go-generics-cache v1.3.1/go.mod h1:qxcC9kRVrct9rHeiYpFWSoW1vxyillCVzX13KZG8dl4= github.com/Crocmagnon/fatcontext v0.7.2 h1:BY5/dUhs2kuD3sDn7vZrgOneRib5EHk9GOiyK8Vg+14= github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.2 h1:cZpsGsWTIFKymTA0je7IIvi1O7Es7apb9CF3EQlOcfE= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.2/go.mod h1:itPGVDKf9cC/ov4MdvJ2QZ0khw4bfoo9jzwTJlaxy2k= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 h1:8nn+rsCvTq9axyEh382S0PFLBeaFwNsT43IrPWzctRU= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= github.com/IBM/watsonx-go v1.0.0/go.mod h1:8lzvpe/158JkrzvcoIcIj6OdNty5iC9co5nQHfkhRtM= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/amikos-tech/chroma-go v0.1.4/go.mod h1:sT6uXOo/L5S/Q0v9jpYtoR1iOM68hUE2itWw8sOwLHY= github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/antchfx/htmlquery v1.3.0/go.mod h1:zKPDVTMhfOmcwxheXUsx4rKJy8KEY/PU6eXr/2SebQ8= github.com/antchfx/xmlquery v1.3.17/go.mod h1:Afkq4JIeXut75taLSuI31ISJ/zeq+3jG7TunF7noreA= github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo= github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14= github.com/aws/aws-sdk-go-v2/config v1.29.4/go.mod h1:j2/AF7j/qxVmsNIChw1tWfsVKOayJoGRDjg1Tgq7NPk= github.com/aws/aws-sdk-go-v2/credentials v1.17.57/go.mod h1:2kerxPUUbTagAr/kkaHiqvj/bcYHzi2qiJS/ZinllU0= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27/go.mod h1:w1BASFIPOPUae7AgaH4SbjNbfdkxuggLyGfNFTn8ITY= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs= github.com/aws/aws-sdk-go-v2/service/bedrockagent v1.40.0/go.mod h1:WlMBqEPeaBywfaXoMAfpitHvwezq555o8waYL3cCPqo= github.com/aws/aws-sdk-go-v2/service/bedrockagentruntime v1.41.0/go.mod h1:Kek1IWlEDT1bp8kO+soWZh37Cb13LppHUTbMiJunna0= github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.24.3/go.mod h1:PKGlRhLmSZuA6iCbRD1oZKrTJHdm6NWwWBvHxfDNHTA= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA= github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2/go.mod h1:U5SNqwhXB3Xe6F47kXvWihPl/ilGaEDe8HD/50Z9wxc= github.com/aws/aws-sdk-go-v2/service/sso v1.24.14/go.mod h1:+JJQTxB6N4niArC14YNtxcQtwEqzS3o9Z32n7q33Rfs= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13/go.mod h1:tvqlFoja8/s0o+UruA1Nrezo/df0PzdunMDDurUfg6U= github.com/aws/aws-sdk-go-v2/service/sts v1.33.12/go.mod h1:7Yn+p66q/jt38qMoVfNvjbm3D89mGBnkwDcijgtih8w= github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/bazelbuild/rules_go v0.49.0/go.mod h1:Dhcz716Kqg1RHNWos+N6MlXNkjNP2EwZQ0LukRKJfMs= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30= github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySeApCX4GeOjPl9qhRF3QuIZq+Q= github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k= github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/cohere-ai/tokenizer v1.1.2/go.mod h1:9MNFPd9j1fuiEK3ua2HSCUxxcrfGMlSqpa93livg/C0= github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/cristalhq/acmd v0.12.0 h1:RdlKnxjN+txbQosg8p/TRNZ+J1Rdne43MVQZ1zDhGWk= github.com/cristalhq/acmd v0.12.0/go.mod h1:LG5oa43pE/BbxtfMoImHCQN++0Su7dzipdgBjMCBVDQ= github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 h1:iwZdTE0PVqJCos1vaoKsclOGD3ADKpshg3SRtYBbwso= github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= github.com/cznic/sortutil v0.0.0-20181122101858-f5f958428db8 h1:LpMLYGyy67BoAFGda1NeOBQwqlv7nUXpm+rIVHGxZZ4= github.com/cznic/sortutil v0.0.0-20181122101858-f5f958428db8/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ= github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 h1:MZRmHqDBd0vxNwenEbKSQqRVT24d3C05ft8kduSwlqM= github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc= github.com/deepmap/oapi-codegen/v2 v2.1.0/go.mod h1:R1wL226vc5VmCNJUvMyYr3hJMm5reyv25j952zAVXZ8= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= github.com/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE= github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/gage-technologies/mistral-go v1.1.0/go.mod h1:tF++Xt7U975GcLlzhrjSQb8l/x+PrriO9QEdsgm9l28= github.com/getsentry/sentry-go v0.30.0/go.mod h1:WU9B9/1/sHDqeV8T+3VwwbjeR5MSXs/6aqG3mqZrezA= github.com/getzep/zep-go v1.0.4/go.mod h1:HC1Gz7oiyrzOTvzeKC4dQKUiUy87zpIJl0ZFXXdHuss= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= github.com/go-kit/log v0.1.0 h1:DGJh0Sm43HbOeYDNnVZFl8BvcYVvjD5bqYJvp0REbwQ= github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= github.com/go-openapi/runtime v0.24.2/go.mod h1:AKurw9fNre+h3ELZfk6ILsfvPN+bvvlaU/M9q/r9hpk= github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/goccmack/gocc v0.0.0-20230228185258-2292f9e40198/go.mod h1:DTh/Y2+NbnOVVoypCCQrovMPDKUGp4yZpSbWg5D0XIM= github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuueE0EA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY= github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/glog v1.2.3/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I= github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/modinfo v0.3.3 h1:YBQDZpDMJpe5mtd0klUFYL8tSVkmF3cmm0fZ48sc7+s= github.com/golangci/modinfo v0.3.3/go.mod h1:wytF1M5xl9u0ij8YSvhkEVPP3M5Mc7XLl1pxH3B2aUM= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/generative-ai-go v0.19.0 h1:R71szggh8wHMCUlEMsW2A/3T+5LdEIkiaHSYgSpUgdg= github.com/google/generative-ai-go v0.19.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-pkcs11 v0.3.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/cloud-bigtable-clients-test v0.0.3/go.mod h1:TWtDzrrAI70C3dNLDY+nZN3gxHtFdZIbpL9rCTFyxE0= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/enterprise-certificate-proxy v0.3.5/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0= github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18= github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk= github.com/hashicorp/consul/api v1.12.0 h1:k3y1FYv6nuKyNTqj6w9gXOx5r5CfLj/k/euUeBXj1OY= github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/serf v0.9.7 h1:hkdgbqizGQHuU5IPqYM1JdSMV8nKfpuOnZYXssk9muY= github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6 h1:UDMh68UUwekSh5iP2OMhRRZJiiBccgV7axzUG8vi56c= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b h1:ogbOPx86mIhFy764gGkqnkFC8m5PJA7sPzlk9ppLVQA= github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o= github.com/metaphorsystems/metaphor-go v0.0.0-20230816231421-43794c04824e/go.mod h1:mDz8kHE7x6Ja95drCQ2T1vLyPRc/t69Cf3wau91E3QU= github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0= github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= github.com/mgechev/dots v1.0.0 h1:o+4OJ3OjWzgQHGJXKfJ8rbH4dqDugu5BiEy84nxg0k4= github.com/mgechev/dots v1.0.0/go.mod h1:rykuMydC9t3wfkM+ccYH3U3ss03vZGg6h3hmOznXLH0= github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= github.com/milvus-io/milvus-proto/go-api/v2 v2.6.1-0.20250819024338-07695f709619/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs= github.com/milvus-io/milvus-sdk-go/v2 v2.4.0/go.mod h1:8IKyxVV+kd+RADMuMpo8GXnTDq5ZxrSSWpe9nJieboQ= github.com/milvus-io/milvus/client/v2 v2.6.0/go.mod h1:5ppFKT61Fh5Z1MkAhK7+nLnlh9C+ENBe/dpgFBH0te0= github.com/milvus-io/milvus/pkg/v2 v2.0.0-20250319085209-5a6b4e56d59e/go.mod h1:37AWzxVs2NS4QUJrkcbeLUwi+4Av0h5mEdjLI62EANU= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mozilla/tls-observatory v0.0.0-20210609171429-7bc42856d2e5 h1:0KqC6/sLy7fDpBdybhVkkv4Yz+PmB7c9Dz9z3dLW804= github.com/mozilla/tls-observatory v0.0.0-20210609171429-7bc42856d2e5/go.mod h1:FUqVoUPHSEdDR0MnFM3Dh8AU0pZHLXUD127SAJGER/s= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nikolalohinski/gonja v1.5.3 h1:GsA+EEaZDZPGJ8JtpeGN78jidhOlxeJROpqMT9fTj9c= github.com/nikolalohinski/gonja v1.5.3/go.mod h1:RmjwxNiXAEqcq1HeK5SSMmqFJvKOfTfXhkJv6YBtPa4= github.com/nlpodyssey/cybertron v0.2.1/go.mod h1:Vg9PeB8EkOTAgSKQ68B3hhKUGmB6Vs734dBdCyE4SVM= github.com/nlpodyssey/gopickle v0.2.0/go.mod h1:YIUwjJ2O7+vnBsxUN+MHAAI3N+adqEGiw+nDpwW95bY= github.com/nlpodyssey/gotokenizers v0.2.0/go.mod h1:SBLbuSQhpni9M7U+Ie6O46TXYN73T2Cuw/4eeYHYJ+s= github.com/nlpodyssey/spago v1.1.0/go.mod h1:jDWGZwrB4B61U6Tf3/+MVlWOtNsk3EUA7G13UDHlnjQ= github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0= github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU= github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opensearch-project/opensearch-go v1.1.0/go.mod h1:+6/XHCuTH+fwsMJikZEWsucZ4eZMma3zNSeLrTtVGbo= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI= github.com/otiai10/mint v1.3.1 h1:BCmzIS3n71sGfHB5NMNDB3lHYPz8fWSkCAErHed//qc= github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30 h1:BHT1/DKsYDGkUgQ2jmMaozVcdk+sVfz0+1ZJq4zkWgw= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pgvector/pgvector-go v0.1.1/go.mod h1:wLJgD/ODkdtd2LJK4l6evHXTuG+8PxymYAVomKHOWac= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d h1:CdDQnGF8Nq9ocOS/xlSptM1N3BbrA6/kmaep5ggwaIA= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pinecone-io/go-pinecone v0.4.1/go.mod h1:KwWSueZFx9zccC+thBk13+LDiOgii8cff9bliUI4tQs= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pkg/sftp v1.13.7 h1:uv+I3nNJvlKZIQGSr8JVQLNHFU9YhhNpvC14Y6KgmSM= github.com/pkg/sftp v1.13.7/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3DY= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= github.com/quasilyte/go-ruleguard/rules v0.0.0-20211022131956-028d6511ab71 h1:CNooiryw5aisadVfzneSZPswRWvnVW8hF1bS/vo8ReI= github.com/quasilyte/go-ruleguard/rules v0.0.0-20211022131956-028d6511ab71/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50= github.com/redis/rueidis v1.0.34/go.mod h1:g8nPmgR4C68N3abFiOc/gUOSEKw3Tom6/teYMehg4RE= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/sagikazarmark/crypt v0.6.0 h1:REOEXCs/NFY/1jOCEouMuT4zEniE5YoXbvpC5X/TLF8= github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/samber/lo v1.27.0/go.mod h1:it33p9UtPMS7z72fP4gw/EIfQB2eI8ke7GR2wc6+Rhg= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= github.com/shirou/gopsutil/v4 v4.25.2/go.mod h1:34gBYJzyqCDT11b6bMHP0XCvWeU3J61XRT7a2EmCRTA= github.com/shirou/gopsutil/v4 v4.25.3 h1:SeA68lsu8gLggyMbmCn8cmp97V1TI9ld9sVzAUcKcKE= github.com/shirou/gopsutil/v4 v4.25.3/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA= github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= github.com/shirou/gopsutil/v4 v4.25.8 h1:NnAsw9lN7587WHxjJA9ryDnqhJpFH6A+wagYWTOH970= github.com/shirou/gopsutil/v4 v4.25.8/go.mod h1:q9QdMmfAOVIw7a+eF86P7ISEU6ka+NLgkUxlopV4RwI= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e h1:MZM7FHLqUHYI0Y/mQAt3d2aYa0SiNms/hFqC9qJYolM= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 h1:llrF3Fs4018ePo4+G/HV/uQUqEI1HMDjCeOf2V6puPc= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sosodev/duration v1.2.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/temoto/robotstxt v1.1.2/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo= github.com/testcontainers/testcontainers-go v0.38.0/go.mod h1:C52c9MoHpWO+C4aqmgSU+hxlR5jlEayWtgYrb8Pzz1w= github.com/testcontainers/testcontainers-go/modules/chroma v0.37.0/go.mod h1:IWJavzQy7rxM40OqOgSN5iyckgAw21wDyE+NhSctatk= github.com/testcontainers/testcontainers-go/modules/mariadb v0.38.0/go.mod h1:26mrWngnaRhxmgy942aVfUihLnihbIGsuIds6gGBnIE= github.com/testcontainers/testcontainers-go/modules/milvus v0.37.0/go.mod h1:bCdLqxjPKax120BMl4aO/A0gs9+4FeJkLBVf9WpjFoQ= github.com/testcontainers/testcontainers-go/modules/mongodb v0.37.0/go.mod h1:e9/4dGJfSZW59/kXGf/ksrEvA+BqP/daax0Usp2cpsM= github.com/testcontainers/testcontainers-go/modules/mysql v0.37.0/go.mod h1:vHEEHx5Kf+uq5hveaVAMrTzPY8eeRZcKcl23MRw5Tkc= github.com/testcontainers/testcontainers-go/modules/opensearch v0.37.0/go.mod h1:2jEljlB96QHSHF7Vo9S8zEDisPPrfsddzSvsCR1ihNQ= github.com/testcontainers/testcontainers-go/modules/postgres v0.37.0/go.mod h1:Qj/eGbRbO/rEYdcRLmN+bEojzatP/+NS1y8ojl2PQsc= github.com/testcontainers/testcontainers-go/modules/redis v0.37.0/go.mod h1:Abu9g/25Qv+FkYVx3U4Voaynou1c+7D0HIhaQJXvk6E= github.com/testcontainers/testcontainers-go/modules/weaviate v0.37.0/go.mod h1:VdjCqOCJGzlGLS2p4NdLjN5rqN3/53mle+Gb+irCbOE= github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/quicktemplate v1.8.0 h1:zU0tjbIqTRgKQzFY1L42zq0qR3eh4WoQQdIdqCysW5k= github.com/valyala/quicktemplate v1.8.0/go.mod h1:qIqW8/igXt8fdrUln5kOSb+KWMaJ4Y8QUsfd1k6L2jM= github.com/weaviate/weaviate v1.29.0/go.mod h1:UsnbM1Kmm5Om+UPU6DTo421SDeMD8SqCJqsBs/nwgcI= github.com/weaviate/weaviate-go-client/v5 v5.0.2/go.mod h1:CwZehIL4s3VfkzTu12Wy8VAUtELRtQFUt2ZniBF/lQM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc= github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= gitlab.com/golang-commonmark/html v0.0.0-20191124015941-a22733972181/go.mod h1:dzYhVIwWCtzPAa4QP98wfB9+mzt33MSmM8wsKiMi2ow= gitlab.com/golang-commonmark/linkify v0.0.0-20191026162114-a0c2df6c8f82/go.mod h1:Gn+LZmCrhPECMD3SOKlE+BOHwhOYD9j7WT9NUtkCrC8= gitlab.com/golang-commonmark/markdown v0.0.0-20211110145824-bf3e522c626a/go.mod h1:LaSIs30YPGs1H5jwGgPhLzc8vkNc/k0rDX/fEZqiU/M= gitlab.com/golang-commonmark/mdurl v0.0.0-20191124015652-932350d1cb84/go.mod h1:IJZ+fdMvbW2qW6htJx7sLJ04FEs4Ldl/MDsJtMKywfw= gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f/go.mod h1:Tiuhl+njh/JIg0uS/sOJVYi0x2HEa5rc1OAaVsb5tAs= go.einride.tech/aip v0.68.1/go.mod h1:XaFtaj4HuA3Zwk9xoBtTWgNubZ0ZZXv9BZJCkuKuWbg= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.etcd.io/etcd/api/v3 v3.5.4 h1:OHVyt3TopwtUQ2GKdd5wu3PmmipR4FTwCqoEjSyRdIc= go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= go.etcd.io/etcd/api/v3 v3.5.5/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= go.etcd.io/etcd/client/pkg/v3 v3.5.4 h1:lrneYvz923dvC14R54XcA7FXoZ3mlGZAgmwhfm7HqOg= go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.5/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ= go.etcd.io/etcd/client/v2 v2.305.4 h1:Dcx3/MYyfKcPNLpR4VVQUP5KgYrBeJtktBwEKkw08Ao= go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU= go.etcd.io/etcd/client/v2 v2.305.5/go.mod h1:zQjKllfqfBVyVStbt4FaosoX2iYd8fV/GRy/PbowgP4= go.etcd.io/etcd/client/v3 v3.5.4 h1:p83BUL3tAYS0OT/r0qglgc3M1JjhM0diV8DSWAhVXv4= go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= go.etcd.io/etcd/client/v3 v3.5.5/go.mod h1:aApjR4WGlSumpnJ2kloS75h6aHUmAyaPLjHMxpc7E7c= go.etcd.io/etcd/pkg/v3 v3.5.5/go.mod h1:6ksYFxttiUGzC2uxyqiyOEvhAiD0tuIqSZkX3TyPdaE= go.etcd.io/etcd/raft/v3 v3.5.5/go.mod h1:76TA48q03g1y1VpTue92jZLr9lIHKUNcYdZOOGyx8rI= go.etcd.io/etcd/server/v3 v3.5.5/go.mod h1:rZ95vDw/jrvsbj9XpTqPrTAB9/kzchVdhRirySPkUBc= go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.mongodb.org/mongo-driver/v2 v2.0.0/go.mod h1:nSjmNq4JUstE8IRZKTktLgMHM4F1fccL6HGX1yh+8RA= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/detectors/gcp v1.31.0 h1:G1JQOreVrfhRkner+l4mrGxmfqYCAuy76asTDAo0xsA= go.opentelemetry.io/contrib/detectors/gcp v1.31.0/go.mod h1:tzQL6E1l+iV44YFTkcAeNQqzXUiekSYP9jjJjXwEd00= go.opentelemetry.io/contrib/detectors/gcp v1.32.0/go.mod h1:TVqo0Sda4Cv8gCIixd7LuLwW4EylumVWfhjZJjDD4DU= go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao= go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= go.opentelemetry.io/contrib/detectors/gcp v1.35.0 h1:bGvFt68+KTiAKFlacHW6AhA56GF2rS0bdD3aJYEnmzA= go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA= go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0/go.mod h1:GijYcYmNpX1KazD5JmWGsi4P7dDTTTnfv1UbGn84MnU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0/go.mod h1:vNUq47TGFioo+ffTSnKNdob241vePmtNZnAODKapKd0= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0/go.mod h1:U2R3XyVPzn0WX7wOIypPuptulsMcPDPs/oiSVOMVnHY= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.starlark.net v0.0.0-20230302034142-4b1e35fe2254 h1:Ss6D3hLXTM0KobyBYEAygXzFfGcjnmfEJOBgSbemCtg= go.starlark.net v0.0.0-20230302034142-4b1e35fe2254/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 h1:dHQOQddU4YHS5gY33/6klKjq7Gp3WwMyOXGNp5nzRj8= golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053/go.mod h1:+nZKN+XVh4LCiA9DV3ywrzN4gumyCnKjau3NGb9SGoE= golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/plot v0.15.2/go.mod h1:DX+x+DWso3LTha+AdkJEv5Txvi+Tql3KAGkehP0/Ubg= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.177.0/go.mod h1:srbhue4MLjkjbkux5p3dw/ocYOSZTaIEvf7bCOnFQDw= google.golang.org/api v0.186.0/go.mod h1:hvRbBmgoje49RV3xqVXrmP6w93n6ehGgIVPYrGtBFFc= google.golang.org/api v0.215.0/go.mod h1:fta3CVtuJYOEdugLNWm6WodzOS8KdFckABwN4I40hzY= google.golang.org/api v0.223.0/go.mod h1:C+RS7Z+dDwds2b+zoAk5hN/eSfsiCn0UDrYof/M4d2M= google.golang.org/api v0.228.0 h1:X2DJ/uoWGnY5obVjewbp8icSL5U4FzuCfy9OjbLSnLs= google.golang.org/api v0.228.0/go.mod h1:wNvRS1Pbe8r4+IfBIniV8fwCpGwTrYa+kMUDiC5z5a4= google.golang.org/api v0.229.0/go.mod h1:wyDfmq5g1wYJWn29O22FDWN48P7Xcz0xz+LBpptYvB0= google.golang.org/api v0.232.0/go.mod h1:p9QCfBWZk1IJETUdbTKloR5ToFdKbYh2fkjsUL6vNoY= google.golang.org/api v0.233.0/go.mod h1:TCIVLLlcwunlMpZIhIp7Ltk77W+vUSdUKAAIlbxY44c= google.golang.org/api v0.235.0/go.mod h1:QpeJkemzkFKe5VCE/PMv7GsUfn9ZF+u+q1Q7w6ckxTg= google.golang.org/api v0.237.0 h1:MP7XVsGZesOsx3Q8WVa4sUdbrsTvDSOERd3Vh4xj/wc= google.golang.org/api v0.237.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= google.golang.org/api v0.239.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f h1:2wh8dWY8959cBGQvk1RD+/eQBgRYYDaZ+hT0/zsARoA= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE= google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk= google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6/go.mod h1:10yRODfgim2/T8csjQsMPgZOMvtytXKTDRzH6HRGzRw= google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4/go.mod h1:px9SlOOZBg1wM1zdnr8jEL4CNGUBZ+ZKYtNPApNQc4c= google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e/go.mod h1:085qFyf2+XaZlRdCgKNCIZ3afY2p4HHZdoIRpId8F4A= google.golang.org/genproto/googleapis/api v0.0.0-20250425173222-7b384671a197/go.mod h1:Cd8IzgPo5Akum2c9R6FsXNaZbH3Jpa2gpHlW89FqlyQ= google.golang.org/genproto/googleapis/api v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:W3S/3np0/dPWsWLi1h/UymYctGXaGBM2StwzD0y140U= google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw= google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250728155136-f173205681a0/go.mod h1:h6yxum/C2qRb4txaZRLDHK8RyS0H/o2oEDeKY4onY/Y= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= google.golang.org/genproto/googleapis/rpc v0.0.0-20250227231956-55c901821b1e/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20/go.mod h1:Nr5H8+MlGWr5+xX/STzdoEqJrO+YteqFbMyCsrb6mH0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/apimachinery v0.28.6/go.mod h1:QFNX/kCl/EMT2WTSz8k4WLCv2XnkOLMaL8GAVRMdpsA= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic= modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/ccgo/v3 v3.17.0 h1:o3OmOqx4/OFnl4Vm3G8Bgmqxnvxnh0nbxeT5p/dWChA= modernc.org/ccgo/v3 v3.17.0/go.mod h1:Sg3fwVpmLvCUTaqEUjiBDAvshIaKDB0RXaf+zgqFu8I= modernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo= modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU= modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/ccorpus2 v1.5.2 h1:Ui+4tc58mf/W+2arcYCJR903y3zl3ecsI7Fpaaqozyw= modernc.org/ccorpus2 v1.5.2/go.mod h1:Wifvo4Q/qS/h1aRoC2TffcHsnxwTikmi1AuLANuucJQ= modernc.org/ccorpus2 v1.5.4 h1:k9A52f3NsUQzHStOav5ukvkdKz63CW5po4gaC5VH4Qc= modernc.org/ccorpus2 v1.5.4/go.mod h1:Wifvo4Q/qS/h1aRoC2TffcHsnxwTikmi1AuLANuucJQ= modernc.org/ccorpus2 v1.5.8/go.mod h1:Wifvo4Q/qS/h1aRoC2TffcHsnxwTikmi1AuLANuucJQ= modernc.org/ebnf v1.1.0/go.mod h1:CNIo7vuji3SyjIP/VhEumIKlAguC1g64mcdk/+VJW/w= modernc.org/ebnfutil v1.1.0/go.mod h1:hdAyhM1jZSq9ygKhEeYgerbagyuLxyxzXcakBPyNqUI= modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/golex v1.1.0 h1:dmSaksHMd+y6NkBsRsCShNPRaSNCNH+abrVm5/gZic8= modernc.org/golex v1.1.0/go.mod h1:2pVlfqApurXhR1m0N+WDYu6Twnc4QuvO4+U8HnwoiRA= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= modernc.org/lex v1.1.1 h1:prSCNTLw1R4rn7M/RzwsuMtAuOytfyR3cnyM07P+Pas= modernc.org/lex v1.1.1/go.mod h1:6r8o8DLJkAnOsQaGi8fMoi+Vt6LTbDaCrkUK729D8xM= modernc.org/lexer v1.0.4 h1:hU7xVbZsqwPphyzChc7nMSGrsuaD2PDNOmzrzkS5AlE= modernc.org/lexer v1.0.4/go.mod h1:tOajb8S4sdfOYitzCgXDFmbVJ/LE0v1fNJ7annTw36U= modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I= modernc.org/parser v1.1.0 h1:XoClYpoz2xHEDIteSQ7tICOTFcNwBI7XRCeghUS6SNI= modernc.org/parser v1.1.0/go.mod h1:CXl3OTJRZij8FeMpzI3Id/bjupHf0u9HSrCUP4Z9pbA= modernc.org/scannertest v1.0.2 h1:JPtfxcVdbRvzmRf2YUvsDibJsQRw8vKA/3jb31y7cy0= modernc.org/scannertest v1.0.2/go.mod h1:RzTm5RwglF/6shsKoEivo8N91nQIoWtcWI7ns+zPyGA= modernc.org/y v1.1.0 h1:JdIvLry+rKeSsVNRCdr6YWYimwwNm0GXtzxid77VfWc= modernc.org/y v1.1.0/go.mod h1:Iz3BmyIS4OwAbwGaUS7cqRrLsSsfp2sFWtpzX+P4CsE= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= ================================================ FILE: install.ps1 ================================================ # Issue Tracker: https://github.com/ScoopInstaller/Install/issues # Unlicense License: # # This is free and unencumbered software released into the public domain. # # Anyone is free to copy, modify, publish, use, compile, sell, or # distribute this software, either in source code form or as a compiled # binary, for any purpose, commercial or non-commercial, and by any # means. # # In jurisdictions that recognize copyright laws, the author or authors # of this software dedicate any and all copyright interest in the # software to the public domain. We make this dedication for the benefit # of the public at large and to the detriment of our heirs and # successors. We intend this dedication to be an overt act of # relinquishment in perpetuity of all present and future rights to this # software under copyright law. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. # # For more information, please refer to <# .SYNOPSIS Scoop installer. .DESCRIPTION The installer of Scoop. For details please check the website and wiki. .PARAMETER ScoopDir Specifies Scoop root path. If not specified, Scoop will be installed to '$env:USERPROFILE\scoop'. .PARAMETER ScoopGlobalDir Specifies directory to store global apps. If not specified, global apps will be installed to '$env:ProgramData\scoop'. .PARAMETER ScoopCacheDir Specifies cache directory. If not specified, caches will be downloaded to '$ScoopDir\cache'. .PARAMETER NoProxy Bypass system proxy during the installation. .PARAMETER Proxy Specifies proxy to use during the installation. .PARAMETER ProxyCredential Specifies credential for the given proxy. .PARAMETER ProxyUseDefaultCredentials Use the credentials of the current user for the proxy server that is specified by the -Proxy parameter. .PARAMETER RunAsAdmin Force to run the installer as administrator. .LINK https://scoop.sh .LINK https://github.com/ScoopInstaller/Scoop/wiki #> param( [String] $ScoopDir, [String] $ScoopGlobalDir, [String] $ScoopCacheDir, [Switch] $NoProxy, [Uri] $Proxy, [System.Management.Automation.PSCredential] $ProxyCredential, [Switch] $ProxyUseDefaultCredentials, [Switch] $RunAsAdmin ) # Disable StrictMode in this script Set-StrictMode -Off function Write-InstallInfo { param( [Parameter(Mandatory = $True, Position = 0)] [String] $String, [Parameter(Mandatory = $False, Position = 1)] [System.ConsoleColor] $ForegroundColor = $host.UI.RawUI.ForegroundColor ) $backup = $host.UI.RawUI.ForegroundColor if ($ForegroundColor -ne $host.UI.RawUI.ForegroundColor) { $host.UI.RawUI.ForegroundColor = $ForegroundColor } Write-Output "$String" $host.UI.RawUI.ForegroundColor = $backup } function Exit-Install { param( [Int] $ErrorCode = 1 ) if ($IS_EXECUTED_FROM_IEX) { # Don't abort with `exit` that would close the PS session if invoked # with iex, yet set `LASTEXITCODE` for the caller to check $Global:LASTEXITCODE = $ErrorCode break } else { exit $ErrorCode } } function Deny-Install { param( [String] $Message, [Int] $ErrorCode = 1 ) Write-InstallInfo -String $Message -ForegroundColor DarkRed Write-InstallInfo 'Abort.' Exit-Install -ErrorCode $ErrorCode } function Test-LanguageMode { if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage') { # `Write-InstallInfo` cannot be used here as it depends on FullLanguage mode Write-Output 'Scoop requires PowerShell FullLanguage mode to run, current PowerShell environment is restricted.' Write-Output 'Abort.' Exit-Install } } function Test-ValidateParameter { if ($null -eq $Proxy -and ($null -ne $ProxyCredential -or $ProxyUseDefaultCredentials)) { Deny-Install 'Provide a valid proxy URI for the -Proxy parameter when using the -ProxyCredential or -ProxyUseDefaultCredentials.' } if ($ProxyUseDefaultCredentials -and $null -ne $ProxyCredential) { Deny-Install "ProxyUseDefaultCredentials is conflict with ProxyCredential. Don't use the -ProxyCredential and -ProxyUseDefaultCredentials together." } } function Test-IsAdministrator { return ([Security.Principal.WindowsPrincipal]` [Security.Principal.WindowsIdentity]::GetCurrent()` ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } function Test-Prerequisite { # Scoop requires PowerShell 5 at least if (($PSVersionTable.PSVersion.Major) -lt 5) { Deny-Install 'PowerShell 5 or later is required to run Scoop. Go to https://microsoft.com/powershell to get the latest version of PowerShell.' } # Scoop requires TLS 1.2 SecurityProtocol, which exists in .NET Framework 4.5+ if ([System.Enum]::GetNames([System.Net.SecurityProtocolType]) -notcontains 'Tls12') { Deny-Install 'Scoop requires .NET Framework 4.5+ to work. Go to https://microsoft.com/net/download to get the latest version of .NET Framework.' } # Ensure Robocopy.exe is accessible if (!(Test-CommandAvailable('robocopy'))) { Deny-Install "Scoop requires 'C:\Windows\System32\Robocopy.exe' to work. Please make sure 'C:\Windows\System32' is in your PATH." } # Detect if RunAsAdministrator, there is no need to run as administrator when installing Scoop if (!$RunAsAdmin -and (Test-IsAdministrator)) { # Exception: Windows Sandbox, GitHub Actions CI $exception = ($env:USERNAME -eq 'WDAGUtilityAccount') -or ($env:GITHUB_ACTIONS -eq 'true' -and $env:CI -eq 'true') if (!$exception) { Deny-Install 'Running the installer as administrator is disabled by default, see https://github.com/ScoopInstaller/Install#for-admin for details.' } } # Show notification to change execution policy $allowedExecutionPolicy = @('Unrestricted', 'RemoteSigned', 'ByPass') if ((Get-ExecutionPolicy).ToString() -notin $allowedExecutionPolicy) { Deny-Install "PowerShell requires an execution policy in [$($allowedExecutionPolicy -join ', ')] to run Scoop. For example, to set the execution policy to 'RemoteSigned' please run 'Set-ExecutionPolicy RemoteSigned -Scope CurrentUser'." } # Test if scoop is installed, by checking if scoop command exists. if (Test-CommandAvailable('scoop')) { Deny-Install "Scoop is already installed. Run 'scoop update' to get the latest version." -ErrorCode 0 } } function Optimize-SecurityProtocol { # .NET Framework 4.7+ has a default security protocol called 'SystemDefault', # which allows the operating system to choose the best protocol to use. # If SecurityProtocolType contains 'SystemDefault' (means .NET4.7+ detected) # and the value of SecurityProtocol is 'SystemDefault', just do nothing on SecurityProtocol, # 'SystemDefault' will use TLS 1.2 if the webrequest requires. $isNewerNetFramework = ([System.Enum]::GetNames([System.Net.SecurityProtocolType]) -contains 'SystemDefault') $isSystemDefault = ([System.Net.ServicePointManager]::SecurityProtocol.Equals([System.Net.SecurityProtocolType]::SystemDefault)) # If not, change it to support TLS 1.2 if (!($isNewerNetFramework -and $isSystemDefault)) { # Set to TLS 1.2 (3072), then TLS 1.1 (768), and TLS 1.0 (192). Ssl3 has been superseded, # https://docs.microsoft.com/en-us/dotnet/api/system.net.securityprotocoltype?view=netframework-4.5 [System.Net.ServicePointManager]::SecurityProtocol = 3072 -bor 768 -bor 192 Write-Verbose 'SecurityProtocol has been updated to support TLS 1.2' } } function Get-Downloader { $downloadSession = New-Object System.Net.WebClient # Set proxy to null if NoProxy is specified if ($NoProxy) { $downloadSession.Proxy = $null } elseif ($Proxy) { # Prepend protocol if not provided if (!$Proxy.IsAbsoluteUri) { $Proxy = New-Object System.Uri('http://' + $Proxy.OriginalString) } $Proxy = New-Object System.Net.WebProxy($Proxy) if ($null -ne $ProxyCredential) { $Proxy.Credentials = $ProxyCredential.GetNetworkCredential() } elseif ($ProxyUseDefaultCredentials) { $Proxy.UseDefaultCredentials = $true } $downloadSession.Proxy = $Proxy } return $downloadSession } function Test-isFileLocked { param( [String] $path ) $file = New-Object System.IO.FileInfo $path if (!(Test-Path $path)) { return $false } try { $stream = $file.Open( [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None ) if ($stream) { $stream.Close() } return $false } catch { # The file is locked by a process. return $true } } function Expand-ZipArchive { param( [String] $path, [String] $to ) if (!(Test-Path $path)) { Deny-Install "Unzip failed: can't find $path to unzip." } # Check if the zip file is locked, by antivirus software for example $retries = 0 while ($retries -le 10) { if ($retries -eq 10) { Deny-Install "Unzip failed: can't unzip because a process is locking the file." } if (Test-isFileLocked $path) { Write-InstallInfo "Waiting for $path to be unlocked by another process... ($retries/10)" $retries++ Start-Sleep -Seconds 2 } else { break } } # Workaround to suspend Expand-Archive verbose output, # upstream issue: https://github.com/PowerShell/Microsoft.PowerShell.Archive/issues/98 $oldVerbosePreference = $VerbosePreference $global:VerbosePreference = 'SilentlyContinue' # Disable progress bar to gain performance $oldProgressPreference = $ProgressPreference $global:ProgressPreference = 'SilentlyContinue' # PowerShell 5+: use Expand-Archive to extract zip files Microsoft.PowerShell.Archive\Expand-Archive -Path $path -DestinationPath $to -Force $global:VerbosePreference = $oldVerbosePreference $global:ProgressPreference = $oldProgressPreference } function Out-UTF8File { param( [Parameter(Mandatory = $True, Position = 0)] [Alias('Path')] [String] $FilePath, [Switch] $Append, [Switch] $NoNewLine, [Parameter(ValueFromPipeline = $True)] [PSObject] $InputObject ) process { if ($Append) { [System.IO.File]::AppendAllText($FilePath, $InputObject) } else { if (!$NoNewLine) { # Ref: https://stackoverflow.com/questions/5596982 # Performance Note: `WriteAllLines` throttles memory usage while # `WriteAllText` needs to keep the complete string in memory. [System.IO.File]::WriteAllLines($FilePath, $InputObject) } else { # However `WriteAllText` does not add ending newline. [System.IO.File]::WriteAllText($FilePath, $InputObject) } } } } function Import-ScoopShim { Write-InstallInfo 'Creating shim...' # The scoop executable $path = "$SCOOP_APP_DIR\bin\scoop.ps1" if (!(Test-Path $SCOOP_SHIMS_DIR)) { New-Item -Type Directory $SCOOP_SHIMS_DIR | Out-Null } # The scoop shim $shim = "$SCOOP_SHIMS_DIR\scoop" # Convert to relative path Push-Location $SCOOP_SHIMS_DIR $relativePath = Resolve-Path -Relative $path Pop-Location $absolutePath = Resolve-Path $path # if $path points to another drive resolve-path prepends .\ which could break shims $ps1text = if ($relativePath -match '^(\.\\)?\w:.*$') { @( "# $absolutePath", "`$path = `"$path`"", "if (`$MyInvocation.ExpectingInput) { `$input | & `$path $arg @args } else { & `$path $arg @args }", "exit `$LASTEXITCODE" ) } else { @( "# $absolutePath", "`$path = Join-Path `$PSScriptRoot `"$relativePath`"", "if (`$MyInvocation.ExpectingInput) { `$input | & `$path $arg @args } else { & `$path $arg @args }", "exit `$LASTEXITCODE" ) } $ps1text -join "`r`n" | Out-UTF8File "$shim.ps1" # make ps1 accessible from cmd.exe @( "@rem $absolutePath", '@echo off', 'setlocal enabledelayedexpansion', 'set args=%*', ':: replace problem characters in arguments', "set args=%args:`"='%", "set args=%args:(=``(%", "set args=%args:)=``)%", "set invalid=`"='", 'if !args! == !invalid! ( set args= )', 'where /q pwsh.exe', 'if %errorlevel% equ 0 (', " pwsh -noprofile -ex unrestricted -file `"$absolutePath`" $arg %args%", ') else (', " powershell -noprofile -ex unrestricted -file `"$absolutePath`" $arg %args%", ')' ) -join "`r`n" | Out-UTF8File "$shim.cmd" @( '#!/bin/sh', "# $absolutePath", 'if command -v pwsh.exe > /dev/null 2>&1; then', " pwsh.exe -noprofile -ex unrestricted -file `"$absolutePath`" $arg `"$@`"", 'else', " powershell.exe -noprofile -ex unrestricted -file `"$absolutePath`" $arg `"$@`"", 'fi' ) -join "`n" | Out-UTF8File $shim -NoNewLine } function Get-Env { param( [String] $name, [Switch] $global ) $RegisterKey = if ($global) { Get-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' } else { Get-Item -Path 'HKCU:' } $EnvRegisterKey = $RegisterKey.OpenSubKey('Environment') $RegistryValueOption = [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames $EnvRegisterKey.GetValue($name, $null, $RegistryValueOption) } function Publish-Env { if (-not ('Win32.NativeMethods' -as [Type])) { Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @' [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern IntPtr SendMessageTimeout( IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult); '@ } $HWND_BROADCAST = [IntPtr] 0xffff $WM_SETTINGCHANGE = 0x1a $result = [UIntPtr]::Zero [Win32.Nativemethods]::SendMessageTimeout($HWND_BROADCAST, $WM_SETTINGCHANGE, [UIntPtr]::Zero, 'Environment', 2, 5000, [ref] $result ) | Out-Null } function Write-Env { param( [String] $name, [String] $val, [Switch] $global ) $RegisterKey = if ($global) { Get-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' } else { Get-Item -Path 'HKCU:' } $EnvRegisterKey = $RegisterKey.OpenSubKey('Environment', $true) if ($val -eq $null) { $EnvRegisterKey.DeleteValue($name) } else { $RegistryValueKind = if ($val.Contains('%')) { [Microsoft.Win32.RegistryValueKind]::ExpandString } elseif ($EnvRegisterKey.GetValue($name)) { $EnvRegisterKey.GetValueKind($name) } else { [Microsoft.Win32.RegistryValueKind]::String } $EnvRegisterKey.SetValue($name, $val, $RegistryValueKind) } Publish-Env } function Add-ShimsDirToPath { # Get $env:PATH of current user $userEnvPath = Get-Env 'PATH' if ($userEnvPath -notmatch [Regex]::Escape($SCOOP_SHIMS_DIR)) { $h = (Get-PSProvider 'FileSystem').Home if (!$h.EndsWith('\')) { $h += '\' } if (!($h -eq '\')) { $friendlyPath = "$SCOOP_SHIMS_DIR" -replace ([Regex]::Escape($h)), '~\' Write-InstallInfo "Adding $friendlyPath to your path." } else { Write-InstallInfo "Adding $SCOOP_SHIMS_DIR to your path." } # For future sessions Write-Env 'PATH' "$SCOOP_SHIMS_DIR;$userEnvPath" # For current session $env:PATH = "$SCOOP_SHIMS_DIR;$env:PATH" } } function Use-Config { if (!(Test-Path $SCOOP_CONFIG_FILE)) { return $null } try { return (Get-Content $SCOOP_CONFIG_FILE -Raw | ConvertFrom-Json -ErrorAction Stop) } catch { Deny-Install "ERROR loading $SCOOP_CONFIG_FILE`: $($_.Exception.Message)" } } function Add-Config { param ( [Parameter(Mandatory = $True, Position = 0)] [String] $Name, [Parameter(Mandatory = $True, Position = 1)] [String] $Value ) $scoopConfig = Use-Config if ($scoopConfig -is [System.Management.Automation.PSObject]) { if ($Value -eq [bool]::TrueString -or $Value -eq [bool]::FalseString) { $Value = [System.Convert]::ToBoolean($Value) } if ($null -eq $scoopConfig.$Name) { $scoopConfig | Add-Member -MemberType NoteProperty -Name $Name -Value $Value } else { $scoopConfig.$Name = $Value } } else { $baseDir = Split-Path -Path $SCOOP_CONFIG_FILE if (!(Test-Path $baseDir)) { New-Item -Type Directory $baseDir | Out-Null } $scoopConfig = New-Object PSObject $scoopConfig | Add-Member -MemberType NoteProperty -Name $Name -Value $Value } if ($null -eq $Value) { $scoopConfig.PSObject.Properties.Remove($Name) } ConvertTo-Json $scoopConfig | Set-Content $SCOOP_CONFIG_FILE -Encoding ASCII return $scoopConfig } function Add-DefaultConfig { # If user-level SCOOP env not defined, save to root_path if (!(Get-Env 'SCOOP')) { if ($SCOOP_DIR -ne "$env:USERPROFILE\scoop") { Write-Verbose "Adding config root_path: $SCOOP_DIR" Add-Config -Name 'root_path' -Value $SCOOP_DIR | Out-Null } } # Use system SCOOP_GLOBAL, or set system SCOOP_GLOBAL # with $env:SCOOP_GLOBAL if RunAsAdmin, otherwise save to global_path if (!(Get-Env 'SCOOP_GLOBAL' -global)) { if ((Test-IsAdministrator) -and $env:SCOOP_GLOBAL) { Write-Verbose "Setting System Environment Variable SCOOP_GLOBAL: $env:SCOOP_GLOBAL" [Environment]::SetEnvironmentVariable('SCOOP_GLOBAL', $env:SCOOP_GLOBAL, 'Machine') } else { if ($SCOOP_GLOBAL_DIR -ne "$env:ProgramData\scoop") { Write-Verbose "Adding config global_path: $SCOOP_GLOBAL_DIR" Add-Config -Name 'global_path' -Value $SCOOP_GLOBAL_DIR | Out-Null } } } # Use system SCOOP_CACHE, or set system SCOOP_CACHE # with $env:SCOOP_CACHE if RunAsAdmin, otherwise save to cache_path if (!(Get-Env 'SCOOP_CACHE' -global)) { if ((Test-IsAdministrator) -and $env:SCOOP_CACHE) { Write-Verbose "Setting System Environment Variable SCOOP_CACHE: $env:SCOOP_CACHE" [Environment]::SetEnvironmentVariable('SCOOP_CACHE', $env:SCOOP_CACHE, 'Machine') } else { if ($SCOOP_CACHE_DIR -ne "$SCOOP_DIR\cache") { Write-Verbose "Adding config cache_path: $SCOOP_CACHE_DIR" Add-Config -Name 'cache_path' -Value $SCOOP_CACHE_DIR | Out-Null } } } # save current datetime to last_update Add-Config -Name 'last_update' -Value ([System.DateTime]::Now.ToString('o')) | Out-Null } function Test-CommandAvailable { param ( [Parameter(Mandatory = $True, Position = 0)] [String] $Command ) return [Boolean](Get-Command $Command -ErrorAction SilentlyContinue) } function Install-Scoop { Write-InstallInfo 'Initializing...' # Validate install parameters Test-ValidateParameter # Check prerequisites Test-Prerequisite # Enable TLS 1.2 Optimize-SecurityProtocol # Download scoop from GitHub Write-InstallInfo 'Downloading...' $downloader = Get-Downloader [bool]$downloadZipsRequired = $True if (Test-CommandAvailable('git')) { $old_https = $env:HTTPS_PROXY $old_http = $env:HTTP_PROXY try { if ($downloader.Proxy) { #define env vars for git when behind a proxy $Env:HTTP_PROXY = $downloader.Proxy.Address $Env:HTTPS_PROXY = $downloader.Proxy.Address } Write-Verbose "Cloning $SCOOP_PACKAGE_GIT_REPO to $SCOOP_APP_DIR" git clone -q $SCOOP_PACKAGE_GIT_REPO $SCOOP_APP_DIR if (-not $?) { throw 'Cloning failed. Falling back to downloading zip files.' } Write-Verbose "Cloning $SCOOP_MAIN_BUCKET_GIT_REPO to $SCOOP_MAIN_BUCKET_DIR" git clone -q $SCOOP_MAIN_BUCKET_GIT_REPO $SCOOP_MAIN_BUCKET_DIR if (-not $?) { throw 'Cloning failed. Falling back to downloading zip files.' } $downloadZipsRequired = $False } catch { Write-Warning "$($_.Exception.Message)" $Global:LASTEXITCODE = 0 } finally { $env:HTTPS_PROXY = $old_https $env:HTTP_PROXY = $old_http } } if ($downloadZipsRequired) { # 1. download scoop $scoopZipfile = "$SCOOP_APP_DIR\scoop.zip" if (!(Test-Path $SCOOP_APP_DIR)) { New-Item -Type Directory $SCOOP_APP_DIR | Out-Null } Write-Verbose "Downloading $SCOOP_PACKAGE_REPO to $scoopZipfile" $downloader.downloadFile($SCOOP_PACKAGE_REPO, $scoopZipfile) # 2. download scoop main bucket $scoopMainZipfile = "$SCOOP_MAIN_BUCKET_DIR\scoop-main.zip" if (!(Test-Path $SCOOP_MAIN_BUCKET_DIR)) { New-Item -Type Directory $SCOOP_MAIN_BUCKET_DIR | Out-Null } Write-Verbose "Downloading $SCOOP_MAIN_BUCKET_REPO to $scoopMainZipfile" $downloader.downloadFile($SCOOP_MAIN_BUCKET_REPO, $scoopMainZipfile) # Extract files from downloaded zip Write-InstallInfo 'Extracting...' # 1. extract scoop $scoopUnzipTempDir = "$SCOOP_APP_DIR\_tmp" Write-Verbose "Extracting $scoopZipfile to $scoopUnzipTempDir" Expand-ZipArchive $scoopZipfile $scoopUnzipTempDir Copy-Item "$scoopUnzipTempDir\scoop-*\*" $SCOOP_APP_DIR -Recurse -Force # 2. extract scoop main bucket $scoopMainUnzipTempDir = "$SCOOP_MAIN_BUCKET_DIR\_tmp" Write-Verbose "Extracting $scoopMainZipfile to $scoopMainUnzipTempDir" Expand-ZipArchive $scoopMainZipfile $scoopMainUnzipTempDir Copy-Item "$scoopMainUnzipTempDir\Main-*\*" $SCOOP_MAIN_BUCKET_DIR -Recurse -Force # Cleanup Remove-Item $scoopUnzipTempDir -Recurse -Force Remove-Item $scoopZipfile Remove-Item $scoopMainUnzipTempDir -Recurse -Force Remove-Item $scoopMainZipfile } # Create the scoop shim Import-ScoopShim # Ensure scoop shims is in the PATH Add-ShimsDirToPath # Setup initial configuration of Scoop Add-DefaultConfig Write-InstallInfo 'Scoop was installed successfully!' -ForegroundColor DarkGreen Write-InstallInfo "Type 'scoop help' for instructions." } function Write-DebugInfo { param($BoundArgs) Write-Verbose '-------- PSBoundParameters --------' $BoundArgs.GetEnumerator() | ForEach-Object { Write-Verbose $_ } Write-Verbose '-------- Environment Variables --------' Write-Verbose "`$env:USERPROFILE: $env:USERPROFILE" Write-Verbose "`$env:ProgramData: $env:ProgramData" Write-Verbose "`$env:SCOOP: $env:SCOOP" Write-Verbose "`$env:SCOOP_CACHE: $SCOOP_CACHE" Write-Verbose "`$env:SCOOP_GLOBAL: $env:SCOOP_GLOBAL" Write-Verbose '-------- Selected Variables --------' Write-Verbose "SCOOP_DIR: $SCOOP_DIR" Write-Verbose "SCOOP_CACHE_DIR: $SCOOP_CACHE_DIR" Write-Verbose "SCOOP_GLOBAL_DIR: $SCOOP_GLOBAL_DIR" Write-Verbose "SCOOP_CONFIG_HOME: $SCOOP_CONFIG_HOME" } # Prepare variables $IS_EXECUTED_FROM_IEX = ($null -eq $MyInvocation.MyCommand.Path) # Abort when the language mode is restricted Test-LanguageMode # Scoop root directory $SCOOP_DIR = $ScoopDir, $env:SCOOP, "$env:USERPROFILE\scoop" | Where-Object { -not [String]::IsNullOrEmpty($_) } | Select-Object -First 1 # Scoop global apps directory $SCOOP_GLOBAL_DIR = $ScoopGlobalDir, $env:SCOOP_GLOBAL, "$env:ProgramData\scoop" | Where-Object { -not [String]::IsNullOrEmpty($_) } | Select-Object -First 1 # Scoop cache directory $SCOOP_CACHE_DIR = $ScoopCacheDir, $env:SCOOP_CACHE, "$SCOOP_DIR\cache" | Where-Object { -not [String]::IsNullOrEmpty($_) } | Select-Object -First 1 # Scoop shims directory $SCOOP_SHIMS_DIR = "$SCOOP_DIR\shims" # Scoop itself directory $SCOOP_APP_DIR = "$SCOOP_DIR\apps\scoop\current" # Scoop main bucket directory $SCOOP_MAIN_BUCKET_DIR = "$SCOOP_DIR\buckets\main" # Scoop config file location $SCOOP_CONFIG_HOME = $env:XDG_CONFIG_HOME, "$env:USERPROFILE\.config" | Select-Object -First 1 $SCOOP_CONFIG_FILE = "$SCOOP_CONFIG_HOME\scoop\config.json" # TODO: Use a specific version of Scoop and the main bucket $SCOOP_PACKAGE_REPO = 'https://github.com/ScoopInstaller/Scoop/archive/master.zip' $SCOOP_MAIN_BUCKET_REPO = 'https://github.com/ScoopInstaller/Main/archive/master.zip' $SCOOP_PACKAGE_GIT_REPO = 'https://github.com/ScoopInstaller/Scoop.git' $SCOOP_MAIN_BUCKET_GIT_REPO = 'https://github.com/ScoopInstaller/Main.git' # Quit if anything goes wrong $oldErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Stop' # Logging debug info Write-DebugInfo $PSBoundParameters # Bootstrap function Install-Scoop # Reset $ErrorActionPreference to original value $ErrorActionPreference = $oldErrorActionPreference ================================================ FILE: nx.json ================================================ { "$schema": "./node_modules/nx/schemas/nx-schema.json", "parallel": 8, "sync": { "applyChanges": true }, "targetDefaults": { "dev": { "dependsOn": ["^pre-dev", "pre-dev", "^dev"], "continuous": true, "cache": false, "configurations": { "development": {} }, "defaultConfiguration": "development" }, "build": { "dependsOn": ["^build"], "outputs": ["{projectRoot}/dist"], "cache": true }, "lint": { "dependsOn": ["^pre-lint", "pre-lint"] }, "test": { "dependsOn": ["^pre-test", "pre-test"] } }, "release": { "versionPlans": true, "projects": ["apps/*"], "projectsRelationship": "independent", "version": { "preserveLocalDependencyProtocols": true }, "changelog": { "projectChangelogs": { "createRelease": "github", "renderer": "{workspaceRoot}/tools/nx-release/renderer.cjs" } } }, "plugins": [ { "plugin": "@nx/js/typescript", "options": { "typecheck": { "targetName": "typecheck" }, "build": { "targetName": "build", "configName": "tsconfig.lib.json" } } }, { "plugin": "@nx/eslint/plugin", "options": { "targetName": "lint", "flags": ["v10_config_lookup_from_file"] } }, { "plugin": "@nx/storybook/plugin", "options": { "serveStorybookTargetName": "storybook", "buildStorybookTargetName": "build-storybook", "testStorybookTargetName": "test-storybook", "staticStorybookTargetName": "static-storybook" } } ] } ================================================ FILE: package.json ================================================ { "name": "the-dev-tools", "private": true, "type": "module", "dependencies": { "@the-dev-tools/spec": "workspace:^" }, "devDependencies": { "@aikidosec/safe-chain": "catalog:", "@nx/eslint": "catalog:", "@nx/js": "catalog:", "@nx/react": "catalog:", "@nx/storybook": "catalog:", "@nx/vite": "catalog:", "@nx/web": "catalog:", "@the-dev-tools/eslint-config": "workspace:^", "@tsconfig/strictest": "catalog:", "@typespec/compiler": "catalog:", "@typespec/prettier-plugin-typespec": "catalog:", "eslint": "catalog:", "jiti": "catalog:", "nx": "catalog:", "prettier": "catalog:", "react": "catalog:", "swc-node": "catalog:", "syncpack": "catalog:", "tailwindcss": "catalog:", "ts-node": "catalog:", "typescript": "catalog:", "typescript-eslint": "catalog:" } } ================================================ FILE: packages/auth/eslint.config.ts ================================================ export { default } from '@the-dev-tools/eslint-config'; ================================================ FILE: packages/auth/package.json ================================================ { "name": "@the-dev-tools/auth", "private": true, "type": "module", "scripts": { "better-auth": "better-auth", "build": "tsc --noEmit", "dev": "node --experimental-transform-types --watch src/index.ts", "start": "node --experimental-transform-types src/index.ts", "test": "vitest run" }, "exports": { ".": "./src/client.ts" }, "dependencies": { "@bufbuild/protobuf": "catalog:", "@connectrpc/connect": "catalog:", "@connectrpc/connect-node": "catalog:", "@effect/platform": "catalog:", "@effect/platform-node": "catalog:", "@the-dev-tools/spec": "workspace:^", "better-auth": "catalog:", "effect": "catalog:", "id128": "catalog:" }, "devDependencies": { "@better-auth/cli": "catalog:", "@the-dev-tools/eslint-config": "workspace:^", "@the-dev-tools/server": "workspace:^", "@types/node": "catalog:", "eslint": "catalog:", "typescript": "catalog:", "vitest": "catalog:" } } ================================================ FILE: packages/auth/project.json ================================================ { "$schema": "../../node_modules/nx/schemas/project-schema.json", "name": "auth", "projectType": "library", "targets": { "test": { "dependsOn": ["server:build", "spec:build"] }, "better-auth": { "configurations": { "development": {} }, "defaultConfiguration": "development" } } } ================================================ FILE: packages/auth/src/adapter.test.ts ================================================ import { Command, FileSystem, Path, Url } from '@effect/platform'; import { NodeContext } from '@effect/platform-node'; import { runAdapterTest } from 'better-auth/adapters/test'; import { Effect, Layer, ManagedRuntime, Match, pipe, Record, Schedule } from 'effect'; import { Ulid } from 'id128'; import os from 'node:os'; import { afterAll, beforeAll, describe, expect, test } from 'vitest'; import { HealthService } from '@the-dev-tools/spec/buf/api/health/v1/health_pb'; import { createAdapter, makeTransport } from './adapter.ts'; import { plugins } from './auth-effect.ts'; class Server extends Effect.Service()('Server', { scoped: Effect.gen(function* () { const path = yield* Path.Path; const fs = yield* FileSystem.FileSystem; const dist = yield* pipe( import.meta.resolve('@the-dev-tools/server'), Url.fromString, Effect.flatMap(path.fromFileUrl), ); const socketPath = path.resolve(os.tmpdir(), 'the-dev-tools', 'test.auth-adapter.server.socket'); const db = { name: 'state', path: path.resolve(import.meta.dirname, '..') }; yield* Effect.addFinalizer(() => pipe(path.resolve(db.path, db.name + '.db'), fs.remove, Effect.ignore)); const process = yield* pipe( path.join(dist, 'server'), Command.make, Command.env({ DB_ENCRYPTION_KEY: 'secret', DB_MODE: 'local', DB_NAME: db.name, DB_PATH: db.path, SERVER_SOCKET_PATH: socketPath, }), Command.stdout('inherit'), Command.stderr('inherit'), Command.start, ); // Wait for the server to start up yield* pipe( Effect.tryPromise((signal) => makeTransport(socketPath).unary(HealthService.method.healthCheck, signal, 0, undefined, {}), ), Effect.retry({ schedule: Schedule.fixed('0.5 seconds'), times: 30 }), ); return { process, socketPath }; }), }) {} const runtime = pipe( Layer.empty, Layer.provideMerge(Server.Default), Layer.provideMerge(NodeContext.layer), ManagedRuntime.make, ); beforeAll(() => runtime.runPromise(Server)); afterAll(() => runtime.dispose()); const { socketPath } = await runtime.runPromise(Server); const adapter = createAdapter({ debugLogs: { isRunningAdapterTests: true }, socketPath })({ plugins }); describe('Adapter', () => { runAdapterTest({ getAdapter: (_ = {}) => createAdapter({ debugLogs: { isRunningAdapterTests: true }, socketPath })(_), // IDs are stored as 16-byte ULID BLOBs — arbitrary string IDs like "mocked-id" cannot be stored. disableTests: { SHOULD_PREFER_GENERATE_ID_IF_PROVIDED: true }, }); }); type Schema = Record }>; const schema = JSON.parse((await adapter.createSchema?.({ plugins }).then((_) => _.code)) ?? '{}') as Schema; Record.map(schema, ({ fields }, model) => { describe(`Model - '${model}'`, () => { const testId = Ulid.construct(new Uint8Array()).toCanonical(); const input = pipe( Record.map(fields, (_, field) => { if (field.endsWith('Id')) return testId; return pipe( Match.value(_.type), Match.when('boolean', () => true), Match.when('number', () => 1), Match.when('string', () => 'init'), Match.when('date', () => new Date(0)), Match.exhaustive, ); }), Record.set('id', testId), ); test('create', async () => { const output = await adapter.create({ data: input, forceAllowId: true, model }); expect(output['id']).toBe(testId); }); test('find', async () => { const output = await adapter.findOne({ model, select: Record.keys(input), where: [{ field: 'id', value: testId }], }); expect(output).toEqual(input); }); }); }); ================================================ FILE: packages/auth/src/adapter.ts ================================================ import { create, fromJson, type JsonValue, toJson } from '@bufbuild/protobuf'; import { type Value, ValueSchema } from '@bufbuild/protobuf/wkt'; import { createClient } from '@connectrpc/connect'; import { createConnectTransport } from '@connectrpc/connect-node'; import * as BA from 'better-auth/adapters'; import { Match, pipe, Record } from 'effect'; import id128 from 'id128'; import { AuthAdapterService, Connector, Direction, Operator, WhereSchema, } from '@the-dev-tools/spec/buf/api/private/auth_adapter/v1/auth_adapter_pb'; // eslint-disable-next-line import-x/no-named-as-default-member const { Ulid } = id128; export const makeTransport = (socketPath: string) => createConnectTransport({ baseUrl: 'http://the-dev-tools:0', httpVersion: '1.1', nodeOptions: { socketPath }, useHttpGet: true, }); export interface CustomAdapterConfig { debugLogs?: BA.DBAdapterDebugLogOption; socketPath: string; } export const createAdapter = (config: CustomAdapterConfig) => { const transport = makeTransport(config.socketPath); const client = createClient(AuthAdapterService, transport); return BA.createAdapterFactory({ adapter: (_) => ({ create: (_: Parameters[0]) => client .create({ data: wrapMap(_.data), model: _.model, ...(_.select && { select: _.select }) }) .then((_) => unwrapMap(_.data) as T), update: (_: Parameters[0]) => client .update({ model: _.model, update: wrap(_.update), where: wrapWhere(_.where) }) .then((_) => (_.data ? unwrap(_.data) : null) as null | T), updateMany: (_) => client .updateMany({ model: _.model, update: wrapMap(_.update), where: wrapWhere(_.where) }) .then((_) => _.count), findOne: (_: Parameters[0]) => client .find({ model: _.model, where: wrapWhere(_.where), ...(_.select && { select: _.select }) }) .then((_) => (_.data ? unwrap(_.data) : null) as null | T), findMany: (_: Parameters[0]) => client .findMany({ limit: _.limit, model: _.model, ...(_.where && { where: wrapWhere(_.where) }), ...(_.offset && { offset: _.offset }), ...(_.sortBy && { sortBy: { direction: pipe( Match.value(_.sortBy.direction), Match.when('asc', () => Direction.ASCENDING), Match.when('desc', () => Direction.DESCENDING), Match.exhaustive, ), field: _.sortBy.field, }, }), }) .then((_) => _.items.map(unwrap) as T[]), delete: (_) => client.delete({ model: _.model, where: wrapWhere(_.where) }).then(() => undefined), deleteMany: (_) => client.deleteMany({ model: _.model, where: wrapWhere(_.where) }).then((_) => _.count), count: (_) => client.count({ model: _.model, ...(_.where && { where: wrapWhere(_.where) }) }).then((_) => _.count), createSchema: ({ file = 'schema.json', tables }) => Promise.resolve({ code: JSON.stringify(tables, undefined, 2), path: file }), }), config: { adapterId: '@the-dev-tools/auth-adapter', adapterName: 'DevTools Auth Adapter', customTransformInput: (_) => { if (_.fieldAttributes.type === 'date' && _.data instanceof Date) return Math.floor(_.data.getTime() / 1000); else return _.data as unknown; }, customTransformOutput: (_) => { if (_.fieldAttributes.type === 'date' && typeof _.data === 'number') return new Date(_.data * 1000); else return _.data as unknown; }, customIdGenerator: () => Ulid.generate().toCanonical(), debugLogs: config.debugLogs, supportsArrays: true, supportsBooleans: true, supportsDates: true, supportsJSON: true, supportsNumericIds: false, supportsUUIDs: false, transaction: false, usePlural: false, }, }); }; const wrapWhere = (_: Required[]) => _.map((_) => create(WhereSchema, { field: _.field, value: fromJson(ValueSchema, _.value as JsonValue), ...(_.connector && { connector: pipe( Match.value(_.connector), Match.when('AND', () => Connector.AND), Match.when('OR', () => Connector.OR), Match.exhaustive, ), }), ...(_.operator && { operator: pipe( Match.value(_.operator), Match.when('eq', () => Operator.EQUAL), Match.when('ne', () => Operator.NOT_EQUAL), Match.when('lt', () => Operator.LESS_THAN), Match.when('lte', () => Operator.LESS_OR_EQUAL), Match.when('gt', () => Operator.GREATER_THAN), Match.when('gte', () => Operator.GREATER_OR_EQUAL), Match.when('in', () => Operator.IN), Match.when('not_in', () => Operator.NOT_IN), Match.when('contains', () => Operator.CONTAINS), Match.when('starts_with', () => Operator.STARTS_WITH), Match.when('ends_with', () => Operator.ENDS_WITH), Match.exhaustive, ), }), }), ); const wrap = (_: unknown) => fromJson(ValueSchema, _ as JsonValue); const wrapMap = (_: Record) => Record.map(_, wrap); const unwrap = (_: Value) => toJson(ValueSchema, _); const unwrapMap = (_: Record) => Record.map(_, unwrap); ================================================ FILE: packages/auth/src/auth-effect.ts ================================================ import { Path } from '@effect/platform'; import { betterAuth, type BetterAuthPlugin } from 'better-auth'; import { jwt } from 'better-auth/plugins'; import { Config, Effect, pipe, Redacted } from 'effect'; import os from 'node:os'; import { createAdapter } from './adapter.ts'; import { defaultUrl } from './config.ts'; export const plugins = [ jwt({ jwks: { keyPairConfig: { alg: 'RS256' }, }, jwt: { definePayload: ({ session, user }) => ({ email: user.email, expiresAt: session.expiresAt, name: user.name, userId: user.id, }), }, }), ] satisfies BetterAuthPlugin[]; export const authEffect = Effect.gen(function* () { const path = yield* Path.Path; const configNamespace = Config.nested('AUTH'); const url = yield* pipe(Config.url('URL'), configNamespace, Config.withDefault(defaultUrl)); const secret = yield* pipe(Config.redacted('SECRET'), configNamespace); const adapterSocketPath = yield* pipe( Config.string('AUTH_ADAPTER_SOCKET'), Config.withDefault(path.resolve(os.tmpdir(), 'the-dev-tools', 'server.socket')), ); return betterAuth({ baseURL: url.href, database: createAdapter({ socketPath: adapterSocketPath }), emailAndPassword: { enabled: true, requireEmailVerification: false }, plugins, secret: Redacted.value(secret), session: { expiresIn: 60 * 60 * 24 * 7, // 7 days updateAge: 60 * 60 * 24, // update session every day }, trustedOrigins: ['*'], }); }); ================================================ FILE: packages/auth/src/auth.ts ================================================ import { NodeContext } from '@effect/platform-node'; import { Effect, pipe } from 'effect'; import { authEffect } from './auth-effect.ts'; export const auth = pipe(authEffect, Effect.provide(NodeContext.layer), Effect.runSync); ================================================ FILE: packages/auth/src/client.ts ================================================ import { jwtClient } from 'better-auth/client/plugins'; import { createAuthClient } from 'better-auth/react'; import { Config, Effect, pipe } from 'effect'; import { defaultUrl } from './config'; export const authClient = Effect.gen(function* () { const url = yield* pipe(Config.url('PUBLIC_AUTH_URL'), Config.withDefault(defaultUrl)); return createAuthClient({ baseURL: url.href, plugins: [jwtClient()], }); }); ================================================ FILE: packages/auth/src/config.ts ================================================ export const defaultUrl = new URL('http://localhost:5000'); ================================================ FILE: packages/auth/src/index.ts ================================================ import { HttpApp, HttpMiddleware, HttpRouter, HttpServer } from '@effect/platform'; import { NodeHttpServer, NodeRuntime } from '@effect/platform-node'; import { Effect, Layer, pipe } from 'effect'; import { createServer } from 'node:http'; import { authEffect } from './auth-effect.ts'; const app = Effect.gen(function* () { const auth = yield* authEffect; const authHttpApp = HttpApp.fromWebHandler(auth.handler); return pipe( HttpRouter.empty, HttpRouter.mountApp('/api/auth', authHttpApp, { includePrefix: true }), HttpMiddleware.logger, HttpMiddleware.cors({ allowedOrigins: () => true, credentials: true }), HttpServer.serve(), HttpServer.withLogAddress, ); }); const HttpServerLive = NodeHttpServer.layer(() => createServer(), { port: 5000 }); pipe(Layer.unwrapEffect(app), Layer.provide(HttpServerLive), Layer.launch, NodeRuntime.runMain); ================================================ FILE: packages/auth/tsconfig.json ================================================ { "extends": ["../../tsconfig.base.json"], "files": [], "references": [ { "path": "./tsconfig.lib.json" } ] } ================================================ FILE: packages/auth/tsconfig.lib.json ================================================ { "extends": "./tsconfig.json", "include": ["."], "exclude": ["node_modules"], "references": [ { "path": "../spec/tsconfig.lib.json" }, { "path": "../../tools/eslint/tsconfig.lib.json" } ] } ================================================ FILE: packages/auth/vitest.config.ts ================================================ import type { UserConfig } from 'vitest/config'; export default { test: { include: ['src/**/*.test.ts'], }, } satisfies UserConfig; ================================================ FILE: packages/auth-lib/go.mod ================================================ module github.com/the-dev-tools/dev-tools/packages/auth-lib go 1.25 require github.com/golang-jwt/jwt/v5 v5.3.0 ================================================ FILE: packages/auth-lib/go.sum ================================================ github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= ================================================ FILE: packages/auth-lib/jwks/jwks.go ================================================ // Package jwks provides JWKS (JSON Web Key Set) fetching and parsing utilities // for validating JWT tokens signed with RSA keys. package jwks import ( "context" "crypto/rsa" "encoding/base64" "encoding/json" "errors" "fmt" "log/slog" "math/big" "net/http" "sync/atomic" "time" "github.com/golang-jwt/jwt/v5" ) // Claims represents the JWT claims from BetterAuth tokens. type Claims struct { Email string `json:"email"` Name string `json:"name"` jwt.RegisteredClaims } // ValidateJWT validates a JWT token using the given keyfunc and returns the claims. func ValidateJWT(tokenString string, keyfunc jwt.Keyfunc) (*Claims, error) { token, err := jwt.ParseWithClaims(tokenString, &Claims{}, keyfunc) if err != nil { return nil, err } claims, ok := token.Claims.(*Claims) if !ok || !token.Valid { return nil, errors.New("invalid token") } return claims, nil } // Response represents a JSON Web Key Set. type Response struct { Keys []Key `json:"keys"` } // Key represents a single JSON Web Key. type Key struct { Kty string `json:"kty"` Kid string `json:"kid"` N string `json:"n"` E string `json:"e"` Alg string `json:"alg"` Use string `json:"use"` } var jwksHTTPClient = &http.Client{ Timeout: 15 * time.Second, } // FetchJWKS fetches and parses JWKS from the given URL, returning RSA public keys indexed by kid. func FetchJWKS(url string) (map[string]*rsa.PublicKey, error) { resp, err := jwksHTTPClient.Get(url) //nolint:gosec // JWKS URL is configured by the operator if err != nil { return nil, fmt.Errorf("failed to fetch JWKS: %w", err) } defer resp.Body.Close() //nolint:errcheck // Best-effort close on read-only response if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("JWKS endpoint returned status %d", resp.StatusCode) } var jwks Response if err := json.NewDecoder(resp.Body).Decode(&jwks); err != nil { return nil, fmt.Errorf("failed to decode JWKS: %w", err) } return ParseJWKS(jwks.Keys) } // ParseJWKS parses JWK keys into RSA public keys indexed by kid. func ParseJWKS(keys []Key) (map[string]*rsa.PublicKey, error) { result := make(map[string]*rsa.PublicKey, len(keys)) for _, key := range keys { if key.Kty != "RSA" { continue } pubKey, err := parseRSAPublicKey(key) if err != nil { return nil, fmt.Errorf("failed to parse key %s: %w", key.Kid, err) } result[key.Kid] = pubKey } if len(result) == 0 { return nil, errors.New("no RSA keys found in JWKS") } return result, nil } func parseRSAPublicKey(key Key) (*rsa.PublicKey, error) { nBytes, err := base64.RawURLEncoding.DecodeString(key.N) if err != nil { return nil, fmt.Errorf("failed to decode modulus: %w", err) } eBytes, err := base64.RawURLEncoding.DecodeString(key.E) if err != nil { return nil, fmt.Errorf("failed to decode exponent: %w", err) } n := new(big.Int).SetBytes(nBytes) e := 0 for _, b := range eBytes { e = e<<8 + int(b) } return &rsa.PublicKey{N: n, E: e}, nil } // NewJWKSKeyfunc creates a jwt.Keyfunc that validates tokens using the given RSA public keys. func NewJWKSKeyfunc(keys map[string]*rsa.PublicKey) jwt.Keyfunc { return func(token *jwt.Token) (interface{}, error) { // Verify the signing method is RSA if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } // Look up key by kid kid, ok := token.Header["kid"].(string) if !ok { // If no kid, try the first key for _, key := range keys { return key, nil } return nil, errors.New("no kid in token header and no keys available") } key, ok := keys[kid] if !ok { return nil, fmt.Errorf("key %s not found in JWKS", kid) } return key, nil } } // Provider fetches JWKS keys and refreshes them in the background. type Provider struct { url string keys atomic.Pointer[map[string]*rsa.PublicKey] interval time.Duration initialRetries int } // ProviderOption configures a Provider. type ProviderOption func(*Provider) // WithRefreshInterval sets the JWKS refresh interval (default 5 minutes). func WithRefreshInterval(d time.Duration) ProviderOption { return func(p *Provider) { p.interval = d } } // WithInitialRetries configures retry attempts for the initial JWKS fetch (default 0, fail fast). // Useful for development where services start concurrently. func WithInitialRetries(attempts int) ProviderOption { return func(p *Provider) { p.initialRetries = attempts } } // NewProvider creates a Provider that fetches JWKS from the given URL. // It performs an initial fetch and fails if the endpoint is unreachable after retries. func NewProvider(url string, opts ...ProviderOption) (*Provider, error) { p := &Provider{ url: url, interval: 5 * time.Minute, } for _, opt := range opts { opt(p) } var keys map[string]*rsa.PublicKey var err error for attempt := range p.initialRetries + 1 { keys, err = FetchJWKS(url) if err == nil { break } if attempt < p.initialRetries { slog.Info("JWKS fetch failed, retrying...", "url", url, "attempt", attempt+1, "error", err) time.Sleep(time.Duration(attempt+1) * time.Second) } } if err != nil { return nil, fmt.Errorf("initial JWKS fetch from %s: %w", url, err) } p.keys.Store(&keys) return p, nil } // Start launches a background goroutine that refreshes JWKS keys at the configured interval. // The goroutine exits when ctx is cancelled. func (p *Provider) Start(ctx context.Context) { go func() { ticker := time.NewTicker(p.interval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: keys, err := FetchJWKS(p.url) if err != nil { slog.Warn("JWKS refresh failed, keeping old keys", "url", p.url, "error", err) continue } p.keys.Store(&keys) slog.Debug("JWKS keys refreshed", "url", p.url, "key_count", len(keys)) } } }() } // Keyfunc returns a jwt.Keyfunc that reads keys from the Provider's atomic pointer (lock-free). func (p *Provider) Keyfunc() jwt.Keyfunc { return func(token *jwt.Token) (interface{}, error) { keysPtr := p.keys.Load() if keysPtr == nil { return nil, errors.New("JWKS keys not loaded") } return NewJWKSKeyfunc(*keysPtr)(token) } } ================================================ FILE: packages/auth-lib/jwks/jwks_test.go ================================================ package jwks import ( "context" "crypto/rand" "crypto/rsa" "encoding/json" "math/big" "net/http" "net/http/httptest" "sync" "sync/atomic" "testing" "time" "github.com/golang-jwt/jwt/v5" ) func generateTestKey(t *testing.T, kid string) (*rsa.PrivateKey, Key) { t.Helper() priv, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { t.Fatalf("generate RSA key: %v", err) } return priv, Key{ Kty: "RSA", Kid: kid, N: base64RawURL(priv.N.Bytes()), E: base64RawURL(big.NewInt(int64(priv.E)).Bytes()), Alg: "RS256", Use: "sig", } } func base64RawURL(data []byte) string { const encodeURL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" buf := make([]byte, 0, (len(data)*8+5)/6) for i := 0; i < len(data); i += 3 { val := uint(data[i]) << 16 if i+1 < len(data) { val |= uint(data[i+1]) << 8 } if i+2 < len(data) { val |= uint(data[i+2]) } remaining := len(data) - i switch { case remaining >= 3: buf = append(buf, encodeURL[(val>>18)&0x3F], encodeURL[(val>>12)&0x3F], encodeURL[(val>>6)&0x3F], encodeURL[val&0x3F]) case remaining == 2: buf = append(buf, encodeURL[(val>>18)&0x3F], encodeURL[(val>>12)&0x3F], encodeURL[(val>>6)&0x3F]) case remaining == 1: buf = append(buf, encodeURL[(val>>18)&0x3F], encodeURL[(val>>12)&0x3F]) } } return string(buf) } func serveJWKS(t *testing.T, keys []Key) *httptest.Server { t.Helper() return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(Response{Keys: keys}) })) } func TestProvider_InitialFetch(t *testing.T) { priv, jwk := generateTestKey(t, "key-1") srv := serveJWKS(t, []Key{jwk}) defer srv.Close() p, err := NewProvider(srv.URL) if err != nil { t.Fatalf("NewProvider: %v", err) } // Sign a token with the test key and validate token := jwt.NewWithClaims(jwt.SigningMethodRS256, &Claims{ Email: "test@example.com", RegisteredClaims: jwt.RegisteredClaims{ Subject: "user-1", ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)), }, }) token.Header["kid"] = "key-1" tokenStr, err := token.SignedString(priv) if err != nil { t.Fatalf("sign token: %v", err) } claims, err := ValidateJWT(tokenStr, p.Keyfunc()) if err != nil { t.Fatalf("ValidateJWT: %v", err) } if claims.Subject != "user-1" { t.Errorf("Subject = %q, want %q", claims.Subject, "user-1") } if claims.Email != "test@example.com" { t.Errorf("Email = %q, want %q", claims.Email, "test@example.com") } } func TestProvider_InitialFetchFailure(t *testing.T) { _, err := NewProvider("http://127.0.0.1:1") // unreachable if err == nil { t.Fatal("expected error for unreachable JWKS URL") } } func TestProvider_BackgroundRefresh(t *testing.T) { // Start with key-1 priv1, jwk1 := generateTestKey(t, "key-1") var mu sync.Mutex currentKeys := []Key{jwk1} srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mu.Lock() keys := currentKeys mu.Unlock() w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(Response{Keys: keys}) })) defer srv.Close() p, err := NewProvider(srv.URL, WithRefreshInterval(50*time.Millisecond)) if err != nil { t.Fatalf("NewProvider: %v", err) } ctx, cancel := context.WithCancel(t.Context()) defer cancel() p.Start(ctx) // Token signed with key-1 should work tok1 := signTestToken(t, priv1, "key-1", "user-1") if _, err := ValidateJWT(tok1, p.Keyfunc()); err != nil { t.Fatalf("key-1 should validate: %v", err) } // Rotate: add key-2, remove key-1 priv2, jwk2 := generateTestKey(t, "key-2") mu.Lock() currentKeys = []Key{jwk2} mu.Unlock() // Wait for refresh time.Sleep(150 * time.Millisecond) // Token signed with key-2 should work now tok2 := signTestToken(t, priv2, "key-2", "user-2") if _, err := ValidateJWT(tok2, p.Keyfunc()); err != nil { t.Fatalf("key-2 should validate after rotation: %v", err) } // Token signed with old key-1 should fail if _, err := ValidateJWT(tok1, p.Keyfunc()); err == nil { t.Fatal("key-1 should fail after rotation") } } func TestProvider_RefreshFailureKeepsOldKeys(t *testing.T) { priv, jwk := generateTestKey(t, "key-1") var callCount atomic.Int32 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { callCount.Add(1) if callCount.Load() > 1 { // Fail on refresh w.WriteHeader(http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(Response{Keys: []Key{jwk}}) })) defer srv.Close() p, err := NewProvider(srv.URL, WithRefreshInterval(50*time.Millisecond)) if err != nil { t.Fatalf("NewProvider: %v", err) } ctx, cancel := context.WithCancel(t.Context()) defer cancel() p.Start(ctx) // Wait for a failed refresh time.Sleep(150 * time.Millisecond) // Old keys should still work tok := signTestToken(t, priv, "key-1", "user-1") if _, err := ValidateJWT(tok, p.Keyfunc()); err != nil { t.Fatalf("old keys should still work after refresh failure: %v", err) } } func TestProvider_ContextCancellation(t *testing.T) { _, jwk := generateTestKey(t, "key-1") srv := serveJWKS(t, []Key{jwk}) defer srv.Close() p, err := NewProvider(srv.URL, WithRefreshInterval(10*time.Millisecond)) if err != nil { t.Fatalf("NewProvider: %v", err) } ctx, cancel := context.WithCancel(t.Context()) p.Start(ctx) // Cancel and verify goroutine exits (no panic/leak) cancel() time.Sleep(50 * time.Millisecond) } func signTestToken(t *testing.T, priv *rsa.PrivateKey, kid, sub string) string { t.Helper() token := jwt.NewWithClaims(jwt.SigningMethodRS256, &Claims{ RegisteredClaims: jwt.RegisteredClaims{ Subject: sub, ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)), }, }) token.Header["kid"] = kid s, err := token.SignedString(priv) if err != nil { t.Fatalf("sign token: %v", err) } return s } ================================================ FILE: packages/auth-lib/project.json ================================================ { "$schema": "../../node_modules/nx/schemas/project-schema.json", "name": "auth-lib", "projectType": "library", "targets": { "test": { "executor": "nx:run-commands", "options": { "cwd": "{projectRoot}", "command": "go test ./... -timeout 10s" } }, "lint": { "executor": "nx:run-commands", "options": { "cwd": "{projectRoot}", "command": "golangci-lint run --allow-parallel-runners" } }, "tidy": { "executor": "nx:run-commands", "options": { "cwd": "{projectRoot}", "command": "go mod tidy" } } } } ================================================ FILE: packages/client/.storybook/main.ts ================================================ import { StorybookConfig as _ } from '@storybook/react-vite'; export { default } from '@the-dev-tools/ui/storybook-config/main.ts'; ================================================ FILE: packages/client/.storybook/manager.ts ================================================ import '@the-dev-tools/ui/storybook-config/manager.ts'; ================================================ FILE: packages/client/.storybook/preview.tsx ================================================ export { default } from '@the-dev-tools/ui/storybook-config/preview.tsx'; ================================================ FILE: packages/client/eslint.config.ts ================================================ export { default } from '@the-dev-tools/eslint-config'; ================================================ FILE: packages/client/index.html ================================================ DevTools
================================================ FILE: packages/client/package.json ================================================ { "name": "@the-dev-tools/client", "private": true, "type": "module", "scripts": { "dev": "vite" }, "exports": { ".": "./src/app/index.tsx", "./src/*": "./src/*.tsx", "./styles": "./src/app/styles.css", "./assets/*": "./assets/*" }, "dependencies": { "@bufbuild/protobuf": "catalog:", "@bufbuild/protovalidate": "catalog:", "@codemirror/autocomplete": "catalog:", "@codemirror/commands": "catalog:", "@codemirror/lang-html": "catalog:", "@codemirror/lang-javascript": "catalog:", "@codemirror/lang-json": "catalog:", "@codemirror/lang-xml": "catalog:", "@codemirror/language": "catalog:", "@codemirror/state": "catalog:", "@codemirror/view": "catalog:", "@connectrpc/connect": "catalog:", "@connectrpc/connect-query": "catalog:", "@connectrpc/connect-web": "catalog:", "@effect-atom/atom-react": "catalog:", "@effect/platform": "catalog:", "@effect/platform-browser": "catalog:", "@faker-js/faker": "catalog:", "@hookform/resolvers": "catalog:", "@lezer/highlight": "catalog:", "@lezer/lr": "catalog:", "@prettier/plugin-xml": "catalog:", "@react-aria/collections": "catalog:", "@standard-schema/spec": "catalog:", "@tanstack/react-db": "catalog:", "@tanstack/react-query": "catalog:", "@tanstack/react-router": "catalog:", "@tanstack/virtual-file-routes": "catalog:", "@the-dev-tools/spec": "workspace:^", "@the-dev-tools/spec-lib": "workspace:^", "@the-dev-tools/ui": "workspace:^", "@uiw/react-codemirror": "catalog:", "@xyflow/react": "catalog:", "effect": "catalog:", "id128": "catalog:", "openai": "catalog:", "prettier": "catalog:", "react": "catalog:", "react-aria": "catalog:", "react-aria-components": "catalog:", "react-dom": "catalog:", "react-error-boundary": "catalog:", "react-icons": "catalog:", "react-markdown": "catalog:", "react-resizable-panels": "catalog:", "react-scan": "catalog:", "react-stately": "catalog:", "react-timeago": "catalog:", "remark-gfm": "catalog:", "tailwind-merge": "catalog:", "tailwind-variants": "catalog:", "use-debounce": "catalog:" }, "devDependencies": { "@hookform/devtools": "catalog:", "@lezer/generator": "catalog:", "@storybook/react-vite": "catalog:", "@tailwindcss/vite": "catalog:", "@tanstack/react-query-devtools": "catalog:", "@tanstack/react-router-devtools": "catalog:", "@tanstack/router-plugin": "catalog:", "@the-dev-tools/auth": "workspace:^", "@the-dev-tools/eslint-config": "workspace:^", "@types/react": "catalog:", "@types/react-dom": "catalog:", "@types/react-timeago": "catalog:", "@vitejs/plugin-react": "catalog:", "babel-plugin-react-compiler": "catalog:", "electron": "catalog:", "eslint": "catalog:", "globals": "catalog:", "storybook": "catalog:", "typescript": "catalog:", "vite": "catalog:", "vite-tsconfig-paths": "catalog:" } } ================================================ FILE: packages/client/project.json ================================================ { "$schema": "../../node_modules/nx/schemas/project-schema.json", "name": "client", "projectType": "library", "targets": { "build": { "executor": "nx:noop" }, "storybook": { "options": { "args": ["--no-open"], "port": 4402 } } } } ================================================ FILE: packages/client/src/app/context.tsx ================================================ import { Transport } from '@connectrpc/connect'; import { Registry } from '@effect-atom/atom-react'; import { KeyValueStore } from '@effect/platform/KeyValueStore'; import { QueryClient } from '@tanstack/react-query'; import { Runtime } from 'effect'; import { ApiCollections, ApiTransport } from '~/shared/api'; export interface RouterContext { queryClient: QueryClient; runtime: Runtime.Runtime; transport: Transport; } ================================================ FILE: packages/client/src/app/dev-tools.tsx ================================================ import type { ReactQueryDevtools as ReactQueryDevtoolsType } from '@tanstack/react-query-devtools'; import type { TanStackRouterDevtools as TanStackRouterDevtoolsType } from '@tanstack/react-router-devtools'; import { ComponentProps, createContext, lazy, PropsWithChildren, ReactNode, Suspense, useContext, useEffect, useState, } from 'react'; import { Options as ReactScanOptions, setOptions } from 'react-scan'; const ShowDevToolsContext = createContext(false); export const DevToolsProvider = ({ children }: PropsWithChildren) => { const key = 'DEV_TOOLS_ENABLED'; const [show, setShow] = useState(!import.meta.env.PROD && Boolean(localStorage.getItem(key))); useEffect(() => { if (import.meta.env.PROD) return; // @ts-expect-error function to toggle dev tools via client console window.toggleDevTools = () => { if (show) localStorage.removeItem(key); else localStorage.setItem(key, 'true'); setShow(!show); }; }, [show]); return {children}; }; const TanStackRouterDevToolsLazy = lazy(() => import('@tanstack/react-router-devtools').then((_) => ({ default: _.TanStackRouterDevtools })), ); export const TanStackRouterDevTools = (props: ComponentProps) => { const show = useContext(ShowDevToolsContext); if (!show) return null; return ( ); }; const ReactQueryDevToolsLazy = lazy<(props: ComponentProps) => ReactNode>(() => import('@tanstack/react-query-devtools/production').then((_) => ({ default: _.ReactQueryDevtools })), ); export const ReactQueryDevTools = (props: ComponentProps) => { const show = useContext(ShowDevToolsContext); if (!show) return null; return ( ); }; export const ReactScanDevTools = (props: ReactScanOptions) => { const show = useContext(ShowDevToolsContext); useEffect(() => { setOptions({ enabled: false, showToolbar: show, ...props }); }, [props, show]); return null; }; ================================================ FILE: packages/client/src/app/entrypoint.tsx ================================================ import { Layer, pipe } from 'effect'; import { createRoot } from 'react-dom/client'; import { addGlobalLayer, App, configProviderFromMetaEnv } from '.'; import './styles.css'; pipe(configProviderFromMetaEnv(), Layer.setConfigProvider, addGlobalLayer); createRoot(document.getElementById('root')!).render(); ================================================ FILE: packages/client/src/app/env.d.ts ================================================ import type { Dialog } from 'electron'; declare const PUBLIC_ENV: object; declare global { interface Window { electron: { dialog: (method: T, ...options: Parameters) => Promise>; }; } } ================================================ FILE: packages/client/src/app/error.tsx ================================================ import { useQueryErrorResetBoundary } from '@tanstack/react-query'; import { ErrorRouteComponent, useRouter } from '@tanstack/react-router'; import { useEffect } from 'react'; import { Button } from '@the-dev-tools/ui/button'; import { tw } from '@the-dev-tools/ui/tailwind-literal'; // https://tanstack.com/router/latest/docs/framework/react/guide/external-data-loading#error-handling-with-tanstack-query export const ErrorComponent: ErrorRouteComponent = ({ error }) => { const router = useRouter(); const queryErrorResetBoundary = useQueryErrorResetBoundary(); useEffect(() => void queryErrorResetBoundary.reset(), [queryErrorResetBoundary]); return (
Failed to load
{error.message}
); }; ================================================ FILE: packages/client/src/app/import-meta.d.ts ================================================ interface ImportMeta { dirname: string; filename: string; } ================================================ FILE: packages/client/src/app/index.tsx ================================================ import { scan } from 'react-scan'; import { TransportProvider } from '@connectrpc/connect-query'; import { Atom, Result, useAtomValue } from '@effect-atom/atom-react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { RouterProvider } from '@tanstack/react-router'; import { ConfigProvider, Effect, pipe, Record, Runtime } from 'effect'; import { type ReactNode, StrictMode } from 'react'; import { UiProvider } from '@the-dev-tools/ui/provider'; import { makeToastQueue } from '@the-dev-tools/ui/toast'; import { ApiCollections, ApiTransport } from '~/shared/api'; import { runtimeAtom } from '~/shared/lib/runtime'; import { RouterContext } from './context'; import { router } from './router'; import { initUmami } from './umami'; scan({ enabled: !import.meta.env.PROD, showToolbar: false }); const appAtom = runtimeAtom.atom( Effect.gen(function* () { const runtime = yield* Effect.runtime ? R : never>(); // Telemetry startup should never block app rendering. void Runtime.runPromise(runtime)(initUmami).catch(() => undefined); yield* ApiCollections; const transport = yield* ApiTransport; const queryClient = new QueryClient(); const toastQueue = makeToastQueue(); return { queryClient, runtime, toastQueue, transport }; }), ); interface AppProps { renderError?: () => ReactNode; } export const App = ({ renderError }: AppProps = {}) => { const context = useAtomValue(appAtom); return Result.match(context, { onFailure: renderError ?? (() =>
App startup error
), onInitial: () =>
Loading...
, onSuccess: ({ value }) => { let _ = ; _ = {_}; _ = {_}; _ = {_}; _ = {_}; return _; }, }); }; export const configProviderFromMetaEnv = (extra?: Record) => pipe( { ...import.meta.env, ...extra }, Record.mapKeys((_) => _.replaceAll('__', '.')), Record.toEntries, (_) => new Map(_ as [string, string][]), ConfigProvider.fromMap, ); export const addGlobalLayer: Atom.RuntimeFactory['addGlobalLayer'] = Atom.runtime.addGlobalLayer; export { runtimeAtom }; ================================================ FILE: packages/client/src/app/router/index.tsx ================================================ import { createHashHistory, createRouter } from '@tanstack/react-router'; import { RouterContext } from '../context'; import { routeTree } from './route-tree.gen'; export const router = createRouter({ context: {} as RouterContext, history: createHashHistory(), routeTree, }); declare module '@tanstack/react-router' { interface Register { router: typeof router; } } ================================================ FILE: packages/client/src/app/router/route-tree.gen.ts ================================================ /* eslint-disable */ // @ts-nocheck // noinspection JSUnusedGlobalSymbols // This file was automatically generated by TanStack Router. // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesIndexRouteImport } from './../../pages/dashboard/routes/index' import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignUpRouteImport } from './../../pages/user/routes/signUp' import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignInRouteImport } from './../../pages/user/routes/signIn' import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRouteImport } from './../../pages/workspace/routes/workspace/$workspaceIdCan/route' import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanIndexRouteImport } from './../../pages/workspace/routes/workspace/$workspaceIdCan/index' import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteImport } from './../../pages/websocket/routes/websocket/$websocketIdCan/route' import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRouteImport } from './../../pages/http/routes/http/$httpIdCan/route' import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRouteImport } from './../../pages/graphql/routes/graphql/$graphqlIdCan/route' import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRouteImport } from './../../pages/flow/routes/flow/$flowIdCan/route' import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRouteImport } from './../../pages/websocket/routes/websocket/$websocketIdCan/index' import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanIndexRouteImport } from './../../pages/http/routes/http/$httpIdCan/index' import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanIndexRouteImport } from './../../pages/graphql/routes/graphql/$graphqlIdCan/index' import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanIndexRouteImport } from './../../pages/flow/routes/flow/$flowIdCan/index' import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotCredentialRoutesCredentialCredentialIdCanIndexRouteImport } from './../../pages/credential/routes/credential/$credentialIdCan/index' import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanHistoryRouteImport } from './../../pages/flow/routes/flow/$flowIdCan/history' import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanDeltaDotdeltaHttpIdCanRouteImport } from './../../pages/http/routes/http/$httpIdCan/delta.$deltaHttpIdCan' import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanDeltaDotdeltaGraphqlIdCanRouteImport } from './../../pages/graphql/routes/graphql/$graphqlIdCan/delta.$deltaGraphqlIdCan' const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesIndexRoute = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesIndexRouteImport.update({ id: '/(dashboard)/', path: '/', getParentRoute: () => rootRouteImport, } as any) const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignUpRoute = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignUpRouteImport.update( { id: '/(dashboard)/(user)/signUp', path: '/signUp', getParentRoute: () => rootRouteImport, } as any, ) const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignInRoute = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignInRouteImport.update( { id: '/(dashboard)/(user)/signIn', path: '/signIn', getParentRoute: () => rootRouteImport, } as any, ) const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRouteImport.update( { id: '/(dashboard)/(workspace)/workspace/$workspaceIdCan', path: '/workspace/$workspaceIdCan', getParentRoute: () => rootRouteImport, } as any, ) const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanIndexRoute = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanIndexRouteImport.update( { id: '/', path: '/', getParentRoute: () => dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute, } as any, ) const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRoute = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteImport.update( { id: '/(websocket)/websocket/$websocketIdCan', path: '/websocket/$websocketIdCan', getParentRoute: () => dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute, } as any, ) const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRoute = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRouteImport.update( { id: '/(http)/http/$httpIdCan', path: '/http/$httpIdCan', getParentRoute: () => dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute, } as any, ) const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRoute = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRouteImport.update( { id: '/(graphql)/graphql/$graphqlIdCan', path: '/graphql/$graphqlIdCan', getParentRoute: () => dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute, } as any, ) const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRoute = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRouteImport.update( { id: '/(flow)/flow/$flowIdCan', path: '/flow/$flowIdCan', getParentRoute: () => dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute, } as any, ) const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRoute = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRouteImport.update( { id: '/', path: '/', getParentRoute: () => dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRoute, } as any, ) const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanIndexRoute = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanIndexRouteImport.update( { id: '/', path: '/', getParentRoute: () => dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRoute, } as any, ) const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanIndexRoute = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanIndexRouteImport.update( { id: '/', path: '/', getParentRoute: () => dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRoute, } as any, ) const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanIndexRoute = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanIndexRouteImport.update( { id: '/', path: '/', getParentRoute: () => dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRoute, } as any, ) const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotCredentialRoutesCredentialCredentialIdCanIndexRoute = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotCredentialRoutesCredentialCredentialIdCanIndexRouteImport.update( { id: '/(credential)/credential/$credentialIdCan/', path: '/credential/$credentialIdCan/', getParentRoute: () => dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute, } as any, ) const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanHistoryRoute = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanHistoryRouteImport.update( { id: '/history', path: '/history', getParentRoute: () => dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRoute, } as any, ) const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanDeltaDotdeltaHttpIdCanRoute = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanDeltaDotdeltaHttpIdCanRouteImport.update( { id: '/delta/$deltaHttpIdCan', path: '/delta/$deltaHttpIdCan', getParentRoute: () => dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRoute, } as any, ) const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanDeltaDotdeltaGraphqlIdCanRoute = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanDeltaDotdeltaGraphqlIdCanRouteImport.update( { id: '/delta/$deltaGraphqlIdCan', path: '/delta/$deltaGraphqlIdCan', getParentRoute: () => dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRoute, } as any, ) export interface FileRoutesByFullPath { '/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesIndexRoute '/signIn': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignInRoute '/signUp': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignUpRoute '/workspace/$workspaceIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRouteWithChildren '/workspace/$workspaceIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanIndexRoute '/workspace/$workspaceIdCan/flow/$flowIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRouteWithChildren '/workspace/$workspaceIdCan/graphql/$graphqlIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRouteWithChildren '/workspace/$workspaceIdCan/http/$httpIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRouteWithChildren '/workspace/$workspaceIdCan/websocket/$websocketIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteWithChildren '/workspace/$workspaceIdCan/flow/$flowIdCan/history': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanHistoryRoute '/workspace/$workspaceIdCan/credential/$credentialIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotCredentialRoutesCredentialCredentialIdCanIndexRoute '/workspace/$workspaceIdCan/flow/$flowIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanIndexRoute '/workspace/$workspaceIdCan/graphql/$graphqlIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanIndexRoute '/workspace/$workspaceIdCan/http/$httpIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanIndexRoute '/workspace/$workspaceIdCan/websocket/$websocketIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRoute '/workspace/$workspaceIdCan/graphql/$graphqlIdCan/delta/$deltaGraphqlIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanDeltaDotdeltaGraphqlIdCanRoute '/workspace/$workspaceIdCan/http/$httpIdCan/delta/$deltaHttpIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanDeltaDotdeltaHttpIdCanRoute } export interface FileRoutesByTo { '/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesIndexRoute '/signIn': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignInRoute '/signUp': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignUpRoute '/workspace/$workspaceIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanIndexRoute '/workspace/$workspaceIdCan/flow/$flowIdCan/history': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanHistoryRoute '/workspace/$workspaceIdCan/credential/$credentialIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotCredentialRoutesCredentialCredentialIdCanIndexRoute '/workspace/$workspaceIdCan/flow/$flowIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanIndexRoute '/workspace/$workspaceIdCan/graphql/$graphqlIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanIndexRoute '/workspace/$workspaceIdCan/http/$httpIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanIndexRoute '/workspace/$workspaceIdCan/websocket/$websocketIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRoute '/workspace/$workspaceIdCan/graphql/$graphqlIdCan/delta/$deltaGraphqlIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanDeltaDotdeltaGraphqlIdCanRoute '/workspace/$workspaceIdCan/http/$httpIdCan/delta/$deltaHttpIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanDeltaDotdeltaHttpIdCanRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/(dashboard)/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesIndexRoute '/(dashboard)/(user)/signIn': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignInRoute '/(dashboard)/(user)/signUp': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignUpRoute '/(dashboard)/(workspace)/workspace/$workspaceIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRouteWithChildren '/(dashboard)/(workspace)/workspace/$workspaceIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanIndexRoute '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(flow)/flow/$flowIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRouteWithChildren '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(graphql)/graphql/$graphqlIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRouteWithChildren '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRouteWithChildren '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(websocket)/websocket/$websocketIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteWithChildren '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(flow)/flow/$flowIdCan/history': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanHistoryRoute '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(credential)/credential/$credentialIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotCredentialRoutesCredentialCredentialIdCanIndexRoute '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(flow)/flow/$flowIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanIndexRoute '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(graphql)/graphql/$graphqlIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanIndexRoute '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanIndexRoute '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(websocket)/websocket/$websocketIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRoute '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(graphql)/graphql/$graphqlIdCan/delta/$deltaGraphqlIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanDeltaDotdeltaGraphqlIdCanRoute '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan/delta/$deltaHttpIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanDeltaDotdeltaHttpIdCanRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: | '/' | '/signIn' | '/signUp' | '/workspace/$workspaceIdCan' | '/workspace/$workspaceIdCan/' | '/workspace/$workspaceIdCan/flow/$flowIdCan' | '/workspace/$workspaceIdCan/graphql/$graphqlIdCan' | '/workspace/$workspaceIdCan/http/$httpIdCan' | '/workspace/$workspaceIdCan/websocket/$websocketIdCan' | '/workspace/$workspaceIdCan/flow/$flowIdCan/history' | '/workspace/$workspaceIdCan/credential/$credentialIdCan/' | '/workspace/$workspaceIdCan/flow/$flowIdCan/' | '/workspace/$workspaceIdCan/graphql/$graphqlIdCan/' | '/workspace/$workspaceIdCan/http/$httpIdCan/' | '/workspace/$workspaceIdCan/websocket/$websocketIdCan/' | '/workspace/$workspaceIdCan/graphql/$graphqlIdCan/delta/$deltaGraphqlIdCan' | '/workspace/$workspaceIdCan/http/$httpIdCan/delta/$deltaHttpIdCan' fileRoutesByTo: FileRoutesByTo to: | '/' | '/signIn' | '/signUp' | '/workspace/$workspaceIdCan' | '/workspace/$workspaceIdCan/flow/$flowIdCan/history' | '/workspace/$workspaceIdCan/credential/$credentialIdCan' | '/workspace/$workspaceIdCan/flow/$flowIdCan' | '/workspace/$workspaceIdCan/graphql/$graphqlIdCan' | '/workspace/$workspaceIdCan/http/$httpIdCan' | '/workspace/$workspaceIdCan/websocket/$websocketIdCan' | '/workspace/$workspaceIdCan/graphql/$graphqlIdCan/delta/$deltaGraphqlIdCan' | '/workspace/$workspaceIdCan/http/$httpIdCan/delta/$deltaHttpIdCan' id: | '__root__' | '/(dashboard)/' | '/(dashboard)/(user)/signIn' | '/(dashboard)/(user)/signUp' | '/(dashboard)/(workspace)/workspace/$workspaceIdCan' | '/(dashboard)/(workspace)/workspace/$workspaceIdCan/' | '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(flow)/flow/$flowIdCan' | '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(graphql)/graphql/$graphqlIdCan' | '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan' | '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(websocket)/websocket/$websocketIdCan' | '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(flow)/flow/$flowIdCan/history' | '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(credential)/credential/$credentialIdCan/' | '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(flow)/flow/$flowIdCan/' | '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(graphql)/graphql/$graphqlIdCan/' | '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan/' | '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(websocket)/websocket/$websocketIdCan/' | '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(graphql)/graphql/$graphqlIdCan/delta/$deltaGraphqlIdCan' | '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan/delta/$deltaHttpIdCan' fileRoutesById: FileRoutesById } export interface RootRouteChildren { dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesIndexRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesIndexRoute dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignInRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignInRoute dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignUpRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignUpRoute dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRouteWithChildren } declare module '@tanstack/react-router' { interface FileRoutesByPath { '/(dashboard)/': { id: '/(dashboard)/' path: '/' fullPath: '/' preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesIndexRouteImport parentRoute: typeof rootRouteImport } '/(dashboard)/(user)/signUp': { id: '/(dashboard)/(user)/signUp' path: '/signUp' fullPath: '/signUp' preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignUpRouteImport parentRoute: typeof rootRouteImport } '/(dashboard)/(user)/signIn': { id: '/(dashboard)/(user)/signIn' path: '/signIn' fullPath: '/signIn' preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignInRouteImport parentRoute: typeof rootRouteImport } '/(dashboard)/(workspace)/workspace/$workspaceIdCan': { id: '/(dashboard)/(workspace)/workspace/$workspaceIdCan' path: '/workspace/$workspaceIdCan' fullPath: '/workspace/$workspaceIdCan' preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRouteImport parentRoute: typeof rootRouteImport } '/(dashboard)/(workspace)/workspace/$workspaceIdCan/': { id: '/(dashboard)/(workspace)/workspace/$workspaceIdCan/' path: '/' fullPath: '/workspace/$workspaceIdCan/' preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanIndexRouteImport parentRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute } '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(websocket)/websocket/$websocketIdCan': { id: '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(websocket)/websocket/$websocketIdCan' path: '/websocket/$websocketIdCan' fullPath: '/workspace/$workspaceIdCan/websocket/$websocketIdCan' preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteImport parentRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute } '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan': { id: '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan' path: '/http/$httpIdCan' fullPath: '/workspace/$workspaceIdCan/http/$httpIdCan' preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRouteImport parentRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute } '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(graphql)/graphql/$graphqlIdCan': { id: '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(graphql)/graphql/$graphqlIdCan' path: '/graphql/$graphqlIdCan' fullPath: '/workspace/$workspaceIdCan/graphql/$graphqlIdCan' preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRouteImport parentRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute } '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(flow)/flow/$flowIdCan': { id: '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(flow)/flow/$flowIdCan' path: '/flow/$flowIdCan' fullPath: '/workspace/$workspaceIdCan/flow/$flowIdCan' preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRouteImport parentRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute } '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(websocket)/websocket/$websocketIdCan/': { id: '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(websocket)/websocket/$websocketIdCan/' path: '/' fullPath: '/workspace/$workspaceIdCan/websocket/$websocketIdCan/' preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRouteImport parentRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRoute } '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan/': { id: '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan/' path: '/' fullPath: '/workspace/$workspaceIdCan/http/$httpIdCan/' preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanIndexRouteImport parentRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRoute } '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(graphql)/graphql/$graphqlIdCan/': { id: '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(graphql)/graphql/$graphqlIdCan/' path: '/' fullPath: '/workspace/$workspaceIdCan/graphql/$graphqlIdCan/' preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanIndexRouteImport parentRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRoute } '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(flow)/flow/$flowIdCan/': { id: '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(flow)/flow/$flowIdCan/' path: '/' fullPath: '/workspace/$workspaceIdCan/flow/$flowIdCan/' preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanIndexRouteImport parentRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRoute } '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(credential)/credential/$credentialIdCan/': { id: '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(credential)/credential/$credentialIdCan/' path: '/credential/$credentialIdCan' fullPath: '/workspace/$workspaceIdCan/credential/$credentialIdCan/' preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotCredentialRoutesCredentialCredentialIdCanIndexRouteImport parentRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute } '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(flow)/flow/$flowIdCan/history': { id: '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(flow)/flow/$flowIdCan/history' path: '/history' fullPath: '/workspace/$workspaceIdCan/flow/$flowIdCan/history' preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanHistoryRouteImport parentRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRoute } '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan/delta/$deltaHttpIdCan': { id: '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan/delta/$deltaHttpIdCan' path: '/delta/$deltaHttpIdCan' fullPath: '/workspace/$workspaceIdCan/http/$httpIdCan/delta/$deltaHttpIdCan' preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanDeltaDotdeltaHttpIdCanRouteImport parentRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRoute } '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(graphql)/graphql/$graphqlIdCan/delta/$deltaGraphqlIdCan': { id: '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(graphql)/graphql/$graphqlIdCan/delta/$deltaGraphqlIdCan' path: '/delta/$deltaGraphqlIdCan' fullPath: '/workspace/$workspaceIdCan/graphql/$graphqlIdCan/delta/$deltaGraphqlIdCan' preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanDeltaDotdeltaGraphqlIdCanRouteImport parentRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRoute } } } interface dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRouteChildren { dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanHistoryRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanHistoryRoute dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanIndexRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanIndexRoute } const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRouteChildren: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRouteChildren = { dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanHistoryRoute: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanHistoryRoute, dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanIndexRoute: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanIndexRoute, } const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRouteWithChildren = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRoute._addFileChildren( dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRouteChildren, ) interface dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRouteChildren { dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanIndexRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanIndexRoute dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanDeltaDotdeltaGraphqlIdCanRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanDeltaDotdeltaGraphqlIdCanRoute } const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRouteChildren: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRouteChildren = { dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanIndexRoute: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanIndexRoute, dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanDeltaDotdeltaGraphqlIdCanRoute: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanDeltaDotdeltaGraphqlIdCanRoute, } const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRouteWithChildren = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRoute._addFileChildren( dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRouteChildren, ) interface dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRouteChildren { dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanIndexRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanIndexRoute dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanDeltaDotdeltaHttpIdCanRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanDeltaDotdeltaHttpIdCanRoute } const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRouteChildren: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRouteChildren = { dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanIndexRoute: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanIndexRoute, dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanDeltaDotdeltaHttpIdCanRoute: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanDeltaDotdeltaHttpIdCanRoute, } const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRouteWithChildren = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRoute._addFileChildren( dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRouteChildren, ) interface dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteChildren { dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRoute } const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteChildren: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteChildren = { dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRoute: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRoute, } const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteWithChildren = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRoute._addFileChildren( dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteChildren, ) interface dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRouteChildren { dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanIndexRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanIndexRoute dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRouteWithChildren dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRouteWithChildren dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRouteWithChildren dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteWithChildren dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotCredentialRoutesCredentialCredentialIdCanIndexRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotCredentialRoutesCredentialCredentialIdCanIndexRoute } const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRouteChildren: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRouteChildren = { dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanIndexRoute: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanIndexRoute, dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRoute: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRouteWithChildren, dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRoute: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRouteWithChildren, dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRoute: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRouteWithChildren, dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRoute: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteWithChildren, dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotCredentialRoutesCredentialCredentialIdCanIndexRoute: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotCredentialRoutesCredentialCredentialIdCanIndexRoute, } const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRouteWithChildren = dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute._addFileChildren( dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRouteChildren, ) const rootRouteChildren: RootRouteChildren = { dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesIndexRoute: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesIndexRoute, dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignInRoute: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignInRoute, dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignUpRoute: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignUpRoute, dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRouteWithChildren, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() ================================================ FILE: packages/client/src/app/router/routes/(dashboard)/__virtual.ts ================================================ import { resolveRoutesFrom } from '../../../../pages/dashboard'; export default resolveRoutesFrom(import.meta.dirname); ================================================ FILE: packages/client/src/app/router/routes/__root.tsx ================================================ import { createRootRouteWithContext, Outlet } from '@tanstack/react-router'; import { tw } from '@the-dev-tools/ui/tailwind-literal'; import { ToastRegion } from '@the-dev-tools/ui/toast'; import { RouterContext } from '../../context'; import { DevToolsProvider, ReactQueryDevTools, ReactScanDevTools, TanStackRouterDevTools } from '../../dev-tools'; import { ErrorComponent } from '../../error'; export const Route = createRootRouteWithContext()({ component: () => ( <>
), errorComponent: ErrorComponent, }); ================================================ FILE: packages/client/src/app/router/vite.tsx ================================================ import { tanstackRouter } from '@tanstack/router-plugin/vite'; export const routerVitePlugin = tanstackRouter({ autoCodeSplitting: true, generatedRouteTree: 'src/app/router/route-tree.gen.ts', routesDirectory: 'src/app/router/routes', target: 'react', }); ================================================ FILE: packages/client/src/app/styles.css ================================================ @layer react-flow { @import '@xyflow/react/dist/style.css'; } @import '@the-dev-tools/ui/styles'; @source '..'; :root { --surface-1: #fefefe; --surface-2: #ffffff; --surface-3: #f7f7f7; --surface-4: #f5f5f5; --surface-5: #f3f3f3; --surface-6: #f0f0f0; --surface-7: #ececec; --border: #e0e0e0; --border-1: #e0e0e0; --divider: #ededed; --text-primary: #2d2d2d; --text-secondary: #404040; --text-tertiary: #5c5c5c; --text-muted: #737373; --text-subtle: #8c8c8c; --text-inverse: #ffffff; --text-error: #ef4444; --brand-400: #8e4cfb; --brand-secondary: #33b4ff; --brand-tertiary-2: #32bd7e; --shimmer-highlight: rgba(0, 0, 0, 0.7); } .dark { --surface-1: #1e1e1e; --surface-2: #232323; --surface-3: #242424; --surface-4: #292929; --surface-5: #363636; --surface-6: #454545; --surface-7: #454545; --border: #2c2c2c; --border-1: #3d3d3d; --divider: #393939; --text-primary: #e6e6e6; --text-secondary: #cccccc; --text-tertiary: #b3b3b3; --text-muted: #787878; --text-subtle: #7d7d7d; --text-inverse: #1b1b1b; --text-error: #ef4444; --brand-400: #8e4cfb; --brand-secondary: #33b4ff; --brand-tertiary-2: #32bd7e; --bg: #1b1b1b; --shimmer-highlight: rgba(255, 255, 255, 0.85); } @keyframes thinking-shimmer { 0% { background-position: 150% 0; } 50% { background-position: 0% 0; } 100% { background-position: -150% 0; } } @keyframes toolcall-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } html, body, #root { height: 100%; overflow: hidden; user-select: none; } ================================================ FILE: packages/client/src/app/umami.tsx ================================================ import { Config, Effect, pipe } from 'effect'; import { Ulid } from 'id128'; interface Umami { identify(uniqueId: string, data?: object): void; identify(data: object): void; track(payload?: object): void; track(event: string, data?: object): void; } export const initUmami = Effect.gen(function* () { const configNamespace = Config.nested('PUBLIC_UMAMI'); const enable = yield* pipe( Config.boolean('ENABLE'), configNamespace, Config.orElse(() => Config.succeed(false)), ); if (!enable) return; const host = yield* pipe(Config.string('HOST'), configNamespace); const websiteId = yield* pipe(Config.string('ID'), configNamespace); const umami = yield* Effect.tryPromise( () => new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = `${host}/script.js`; script.setAttribute('data-website-id', websiteId); script.setAttribute('data-auto-track', 'false'); script.addEventListener( 'load', () => { const { umami } = window as unknown as { umami?: Umami }; if (umami) resolve(umami); else reject(new Error('Umami script loaded but window.umami is missing')); }, { once: true }, ); script.addEventListener( 'error', () => { reject(new Error(`Failed to load Umami script from ${script.src}`)); }, { once: true }, ); document.head.appendChild(script); }), ); const sessionIdKey = 'UMAMI_SESSION_ID'; let sessionId = localStorage.getItem(sessionIdKey); if (!sessionId) { sessionId = Ulid.generate().toCanonical(); localStorage.setItem(sessionIdKey, sessionId); } umami.identify(sessionId); umami.track('init'); }); ================================================ FILE: packages/client/src/features/agent/agent-logger.ts ================================================ /** JSON stringify with BigInt support */ const safeStringify = (value: unknown): string => JSON.stringify(value, (_key: string, v: unknown) => (typeof v === 'bigint' ? v.toString() : v)); /** Truncate a string to maxLen, appending '...[truncated]' if needed */ const truncate = (s: string, maxLen = 2048): string => (s.length <= maxLen ? s : s.slice(0, maxLen) + '...[truncated]'); interface AgentLogIpc { cleanup: () => void; write: (fileName: string, jsonLine: string) => void; } interface LogEntry { [key: string]: unknown; event: string; sessionId: string; ts: string; } /** Get the agentLog IPC bridge if running inside Electron, null otherwise */ const getAgentLogIpc = (): AgentLogIpc | null => { if (typeof window === 'undefined') return null; const electron = (window as unknown as { electron?: { agentLog?: AgentLogIpc } }).electron; return electron?.agentLog ?? null; }; /** * JSONL logger for agent conversations. * Writes to local files via Electron IPC. Silent no-op when running outside Electron. */ export class AgentLogger { private buffer: string[] = []; private fileName: string; private flushTimer: null | ReturnType = null; private ipc: AgentLogIpc | null; private sessionId: string; private sessionStart: number; constructor(flowId: string) { this.sessionId = crypto.randomUUID(); this.sessionStart = performance.now(); this.ipc = getAgentLogIpc(); const shortFlowId = flowId.slice(0, 8); const ts = new Date().toISOString().replace(/[:.]/g, '-'); this.fileName = `agent-${shortFlowId}-${ts}-${this.sessionId.slice(0, 8)}.jsonl`; } private write(entry: LogEntry) { if (!this.ipc) return; this.buffer.push(safeStringify(entry)); this.flushTimer ??= setTimeout(() => void this.flush(), 100); } private flush() { if (!this.ipc || this.buffer.length === 0) return; const batch = this.buffer.join('\n') + '\n'; this.buffer = []; this.ipc.write(this.fileName, batch); } // --- Event methods --- logSessionStart(flowId: string, messageContent: string) { this.write({ event: 'session_start', flowId, sessionId: this.sessionId, ts: new Date().toISOString(), userMessagePreview: truncate(messageContent, 500), }); } logSessionEnd(success: boolean, aborted: boolean) { this.write({ aborted, durationMs: Math.round(performance.now() - this.sessionStart), event: 'session_end', sessionId: this.sessionId, success, ts: new Date().toISOString(), }); // Flush synchronously on close this.close(); } logSystemPrompt(prompt: string, contextStats: { edges: number; nodes: number; variables: number }) { this.write({ contextStats, event: 'system_prompt', promptLength: prompt.length, sessionId: this.sessionId, ts: new Date().toISOString(), }); } logUserMessage(content: string) { this.write({ content: truncate(content), event: 'user_message', sessionId: this.sessionId, ts: new Date().toISOString(), }); } logAssistantMessage(content: string) { this.write({ content: truncate(content), event: 'assistant_message', sessionId: this.sessionId, ts: new Date().toISOString(), }); } logApiRequest(model: string, messageCount: number, hasTools: boolean) { this.write({ event: 'api_request', hasTools, messageCount, model, sessionId: this.sessionId, ts: new Date().toISOString(), }); } logApiResponse( latencyMs: number, finishReason: null | string | undefined, usage: null | undefined | { completion_tokens?: number; prompt_tokens?: number; total_tokens?: number }, ) { this.write({ event: 'api_response', finishReason: finishReason ?? 'unknown', latencyMs: Math.round(latencyMs), sessionId: this.sessionId, ts: new Date().toISOString(), usage: usage ?? null, }); } logToolCallStart(toolCallId: string, toolName: string, args: Record) { this.write({ args: truncate(safeStringify(args)), event: 'tool_call_start', sessionId: this.sessionId, toolCallId, toolName, ts: new Date().toISOString(), }); } logToolCallEnd(toolCallId: string, toolName: string, durationMs: number, result: string, error?: string) { this.write({ durationMs: Math.round(durationMs), error: error ?? undefined, event: 'tool_call_end', result: truncate(result), sessionId: this.sessionId, toolCallId, toolName, ts: new Date().toISOString(), }); } logValidation(orphanCount: number, orphanNames: string[]) { this.write({ event: 'validation', orphanCount, orphanNames, sessionId: this.sessionId, ts: new Date().toISOString(), }); } logError(error: unknown, phase: string) { const message = error instanceof Error ? error.message : String(error); const stack = error instanceof Error ? error.stack : undefined; this.write({ event: 'error', message, phase, sessionId: this.sessionId, stack, ts: new Date().toISOString(), }); } /** Flush remaining buffer immediately */ close() { if (this.flushTimer) { clearTimeout(this.flushTimer); this.flushTimer = null; } this.flush(); } } ================================================ FILE: packages/client/src/features/agent/context-builder.ts ================================================ /* eslint-disable @typescript-eslint/no-unnecessary-condition */ import { eq, useLiveQuery } from '@tanstack/react-db'; import { Ulid } from 'id128'; import { FlowItemState, NodeKind } from '@the-dev-tools/spec/buf/api/flow/v1/flow_pb'; import { HttpMethod } from '@the-dev-tools/spec/buf/api/http/v1/http_pb'; import { EdgeCollectionSchema, FlowVariableCollectionSchema, NodeCollectionSchema, NodeExecutionCollectionSchema, NodeGraphQLCollectionSchema, NodeHttpCollectionSchema, } from '@the-dev-tools/spec/tanstack-db/v1/api/flow'; import { HttpCollectionSchema } from '@the-dev-tools/spec/tanstack-db/v1/api/http'; import { useApiCollection } from '~/shared/api'; import { queryCollection } from '~/shared/lib'; import type { EdgeInfo, FlowContextData, NodeExecutionInfo, NodeInfo, VariableInfo } from './types'; const NODE_KIND_NAMES: Record = { [NodeKind.AI]: 'Ai', [NodeKind.CONDITION]: 'Condition', [NodeKind.FOR]: 'For', [NodeKind.FOR_EACH]: 'ForEach', [NodeKind.GRAPH_Q_L]: 'GraphQL', [NodeKind.HTTP]: 'HTTP', [NodeKind.JS]: 'JavaScript', [NodeKind.MANUAL_START]: 'ManualStart', [NodeKind.UNSPECIFIED]: 'Unknown', }; const FLOW_ITEM_STATE_NAMES: Record = { [FlowItemState.CANCELED]: 'Canceled', [FlowItemState.FAILURE]: 'Failure', [FlowItemState.RUNNING]: 'Running', [FlowItemState.SUCCESS]: 'Success', [FlowItemState.UNSPECIFIED]: 'Idle', }; const HTTP_METHOD_NAMES: Record = { [HttpMethod.DELETE]: 'DELETE', [HttpMethod.GET]: 'GET', [HttpMethod.HEAD]: 'HEAD', [HttpMethod.OPTIONS]: 'OPTIONS', [HttpMethod.PATCH]: 'PATCH', [HttpMethod.POST]: 'POST', [HttpMethod.PUT]: 'PUT', [HttpMethod.UNSPECIFIED]: 'UNSPECIFIED', }; const escapeXml = (s: string): string => s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); export const useFlowContext = (flowId: Uint8Array): FlowContextData => { const nodeCollection = useApiCollection(NodeCollectionSchema); const edgeCollection = useApiCollection(EdgeCollectionSchema); const variableCollection = useApiCollection(FlowVariableCollectionSchema); const executionCollection = useApiCollection(NodeExecutionCollectionSchema); const nodeHttpCollection = useApiCollection(NodeHttpCollectionSchema); const nodeGraphqlCollection = useApiCollection(NodeGraphQLCollectionSchema); const httpCollection = useApiCollection(HttpCollectionSchema); const { data: nodesData } = useLiveQuery( (_) => _.from({ node: nodeCollection }).where((_) => eq(_.node.flowId, flowId)), [nodeCollection, flowId], ); const { data: edgesData } = useLiveQuery( (_) => _.from({ edge: edgeCollection }).where((_) => eq(_.edge.flowId, flowId)), [edgeCollection, flowId], ); const { data: variablesData } = useLiveQuery( (_) => _.from({ variable: variableCollection }).where((_) => eq(_.variable.flowId, flowId)), [variableCollection, flowId], ); // Get all node IDs from the current flow as a Set for efficient lookup const nodeIdSet = new Set( (nodesData ?? []).filter((n) => n.nodeId != null).map((n) => Ulid.construct(n.nodeId).toCanonical()), ); // Get all executions - we'll filter in memory by node IDs const { data: allExecutionsData } = useLiveQuery((_) => _.from({ exec: executionCollection }), [executionCollection]); // Filter executions to only those belonging to nodes in this flow const executionsData = (allExecutionsData ?? []).filter( (e) => e.nodeId != null && nodeIdSet.has(Ulid.construct(e.nodeId).toCanonical()), ); // Get all nodeHttp mappings for HTTP nodes const { data: nodeHttpData } = useLiveQuery((_) => _.from({ nodeHttp: nodeHttpCollection }), [nodeHttpCollection]); // Build a map of nodeId -> httpId for quick lookup const nodeHttpMap = new Map( (nodeHttpData ?? []) .filter((nh) => nh.nodeId != null && nh.httpId != null) .map((nh) => [Ulid.construct(nh.nodeId).toCanonical(), Ulid.construct(nh.httpId).toCanonical()]), ); // Get all nodeGraphql mappings for GraphQL nodes const { data: nodeGraphqlData } = useLiveQuery( (_) => _.from({ nodeGql: nodeGraphqlCollection }), [nodeGraphqlCollection], ); // Build a map of nodeId -> graphqlId for quick lookup const nodeGraphqlMap = new Map( (nodeGraphqlData ?? []) .filter((ng) => ng.nodeId != null && ng.graphqlId != null) .map((ng) => [Ulid.construct(ng.nodeId).toCanonical(), Ulid.construct(ng.graphqlId).toCanonical()]), ); // Get all HTTP requests to fetch their methods const { data: httpData } = useLiveQuery((_) => _.from({ http: httpCollection }), [httpCollection]); // Build a map of httpId -> method for quick lookup const httpMethodMap = new Map( (httpData ?? []) .filter((h) => h.httpId != null) .map((h) => [Ulid.construct(h.httpId).toCanonical(), HTTP_METHOD_NAMES[h.method] ?? 'UNSPECIFIED']), ); const nodes: NodeInfo[] = (nodesData ?? []) .filter((n) => n.nodeId != null) .map((n) => { const nodeIdStr = Ulid.construct(n.nodeId).toCanonical(); const httpId = n.kind === NodeKind.HTTP ? nodeHttpMap.get(nodeIdStr) : undefined; const httpMethod = httpId ? httpMethodMap.get(httpId) : undefined; const graphqlId = n.kind === NodeKind.GRAPH_Q_L ? nodeGraphqlMap.get(nodeIdStr) : undefined; return { graphqlId, httpId, httpMethod, id: nodeIdStr, info: n.info ?? undefined, kind: NODE_KIND_NAMES[n.kind] ?? 'Unknown', name: n.name, position: { x: n.position?.x ?? 0, y: n.position?.y ?? 0 }, state: FLOW_ITEM_STATE_NAMES[n.state] ?? 'Idle', }; }); const edges: EdgeInfo[] = (edgesData ?? []) .filter((e) => e.edgeId != null) .map((e) => ({ id: Ulid.construct(e.edgeId).toCanonical(), sourceHandle: e.sourceHandle !== undefined ? String(e.sourceHandle) : undefined, sourceId: Ulid.construct(e.sourceId).toCanonical(), targetId: Ulid.construct(e.targetId).toCanonical(), })); const variables: VariableInfo[] = (variablesData ?? []) .filter((v) => v.flowVariableId != null) .map((v) => ({ enabled: v.enabled, id: Ulid.construct(v.flowVariableId).toCanonical(), key: v.key, value: v.value, })); // Only keep the most recent execution per node to limit context size // Input/output are stored but will be truncated when accessed via getNodeOutput const executionsByNode = new Map(); for (const e of executionsData ?? []) { if (e.nodeExecutionId == null) continue; const nodeIdStr = Ulid.construct(e.nodeId).toCanonical(); const existing = executionsByNode.get(nodeIdStr); if (!existing || (e.completedAt && (!existing.completedAt || e.completedAt > existing.completedAt))) { executionsByNode.set(nodeIdStr, e); } } const executions: NodeExecutionInfo[] = Array.from(executionsByNode.values()).map((e) => ({ completedAt: e.completedAt instanceof Date ? e.completedAt.toISOString() : e.completedAt, error: e.error ?? undefined, id: Ulid.construct(e.nodeExecutionId).toCanonical(), input: e.input ?? undefined, name: e.name, nodeId: Ulid.construct(e.nodeId).toCanonical(), output: e.output ?? undefined, state: FLOW_ITEM_STATE_NAMES[e.state] ?? 'Idle', })); return { edges, executions, flowId: Ulid.construct(flowId).toCanonical(), nodes, variables, }; }; interface FlowCollections { edgeCollection: ReturnType>; executionCollection: ReturnType>; httpCollection: ReturnType>; nodeCollection: ReturnType>; nodeGraphqlCollection: ReturnType>; nodeHttpCollection: ReturnType>; variableCollection: ReturnType>; } /** * Async version of useFlowContext that queries collections directly. * Use this outside React's render cycle (e.g. in the agent tool loop) * to get a fresh snapshot of flow data after mutations. */ export const refreshFlowContext = async ( flowId: Uint8Array, collections: FlowCollections, ): Promise => { const { edgeCollection, executionCollection, httpCollection, nodeCollection, nodeGraphqlCollection, nodeHttpCollection, variableCollection, } = collections; const nodesData = await queryCollection((_) => _.from({ node: nodeCollection }).where((_) => eq(_.node.flowId, flowId)), ); const edgesData = await queryCollection((_) => _.from({ edge: edgeCollection }).where((_) => eq(_.edge.flowId, flowId)), ); const variablesData = await queryCollection((_) => _.from({ variable: variableCollection }).where((_) => eq(_.variable.flowId, flowId)), ); const nodeIdSet = new Set( nodesData.filter((n) => n.nodeId != null).map((n) => Ulid.construct(n.nodeId).toCanonical()), ); const allExecutionsData = await queryCollection((_) => _.from({ exec: executionCollection })); const executionsData = allExecutionsData.filter( (e) => e.nodeId != null && nodeIdSet.has(Ulid.construct(e.nodeId).toCanonical()), ); const nodeHttpData = await queryCollection((_) => _.from({ nodeHttp: nodeHttpCollection })); const nodeHttpMap = new Map( nodeHttpData .filter((nh) => nh.nodeId != null && nh.httpId != null) .map((nh) => [Ulid.construct(nh.nodeId).toCanonical(), Ulid.construct(nh.httpId).toCanonical()]), ); const nodeGraphqlData = await queryCollection((_) => _.from({ nodeGql: nodeGraphqlCollection })); const nodeGraphqlMap = new Map( nodeGraphqlData .filter((ng) => ng.nodeId != null && ng.graphqlId != null) .map((ng) => [Ulid.construct(ng.nodeId).toCanonical(), Ulid.construct(ng.graphqlId).toCanonical()]), ); const httpData = await queryCollection((_) => _.from({ http: httpCollection })); const httpMethodMap = new Map( httpData .filter((h) => h.httpId != null) .map((h) => [Ulid.construct(h.httpId).toCanonical(), HTTP_METHOD_NAMES[h.method] ?? 'UNSPECIFIED']), ); const nodes: NodeInfo[] = nodesData .filter((n) => n.nodeId != null) .map((n) => { const nodeIdStr = Ulid.construct(n.nodeId).toCanonical(); const httpId = n.kind === NodeKind.HTTP ? nodeHttpMap.get(nodeIdStr) : undefined; const httpMethod = httpId ? httpMethodMap.get(httpId) : undefined; const graphqlId = n.kind === NodeKind.GRAPH_Q_L ? nodeGraphqlMap.get(nodeIdStr) : undefined; return { graphqlId, httpId, httpMethod, id: nodeIdStr, info: n.info ?? undefined, kind: NODE_KIND_NAMES[n.kind] ?? 'Unknown', name: n.name, position: { x: n.position?.x ?? 0, y: n.position?.y ?? 0 }, state: FLOW_ITEM_STATE_NAMES[n.state] ?? 'Idle', }; }); const edges: EdgeInfo[] = edgesData .filter((e) => e.edgeId != null) .map((e) => ({ id: Ulid.construct(e.edgeId).toCanonical(), sourceHandle: e.sourceHandle !== undefined ? String(e.sourceHandle) : undefined, sourceId: Ulid.construct(e.sourceId).toCanonical(), targetId: Ulid.construct(e.targetId).toCanonical(), })); const variables: VariableInfo[] = variablesData .filter((v) => v.flowVariableId != null) .map((v) => ({ enabled: v.enabled, id: Ulid.construct(v.flowVariableId).toCanonical(), key: v.key, value: v.value, })); const executionsByNode = new Map(); for (const e of executionsData) { if (e.nodeExecutionId == null) continue; const nodeIdStr = Ulid.construct(e.nodeId).toCanonical(); const existing = executionsByNode.get(nodeIdStr); if (!existing || (e.completedAt && (!existing.completedAt || e.completedAt > existing.completedAt))) { executionsByNode.set(nodeIdStr, e); } } const executions: NodeExecutionInfo[] = Array.from(executionsByNode.values()).map((e) => ({ completedAt: e.completedAt instanceof Date ? e.completedAt.toISOString() : e.completedAt, error: e.error ?? undefined, id: Ulid.construct(e.nodeExecutionId).toCanonical(), input: e.input ?? undefined, name: e.name, nodeId: Ulid.construct(e.nodeId).toCanonical(), output: e.output ?? undefined, state: FLOW_ITEM_STATE_NAMES[e.state] ?? 'Idle', })); return { edges, executions, flowId: Ulid.construct(flowId).toCanonical(), nodes, variables, }; }; /** * Detect orphan nodes that are not reachable from ManualStart via BFS. * Reusable by both the system prompt builder and the post-execution validation loop. */ export const detectOrphanNodes = ( nodes: Pick[], edges: Pick[], ): Pick[] => { const startNode = nodes.find((n) => n.kind === 'ManualStart'); if (!startNode) return []; // Build outgoing edge map const outgoing = new Map(); for (const e of edges) { const list = outgoing.get(e.sourceId) ?? []; list.push(e.targetId); outgoing.set(e.sourceId, list); } // BFS to find reachable nodes const reachable = new Set(); const queue = [startNode.id]; while (queue.length > 0) { const nodeId = queue.shift()!; if (reachable.has(nodeId)) continue; reachable.add(nodeId); queue.push(...(outgoing.get(nodeId) ?? [])); } return nodes.filter((n) => n.kind !== 'ManualStart' && !reachable.has(n.id)); }; /** * Detect dead-end nodes: reachable from Start but have no outgoing edges. * Only flags as problematic when there are many dead-ends AND the flow has * deeper interior nodes — indicating the model forgot fan-in connections. */ export const detectDeadEndNodes = ( nodes: Pick[], edges: Pick[], ): Pick[] => { const hasOutgoing = new Set(edges.map((e) => e.sourceId)); const hasIncoming = new Set(edges.map((e) => e.targetId)); // Dead-ends: non-start nodes with incoming edges but no outgoing edges const deadEnds = nodes.filter((n) => n.kind !== 'ManualStart' && hasIncoming.has(n.id) && !hasOutgoing.has(n.id)); // Interior nodes: non-start nodes that DO have outgoing edges (flow has depth) const interiorNodes = nodes.filter((n) => n.kind !== 'ManualStart' && hasOutgoing.has(n.id)); // Only flag when: many dead-ends AND flow has interior depth if (deadEnds.length > 3 && interiorNodes.length > 0) { return deadEnds; } return []; }; const buildXmlFlowBlock = (context: FlowContextData): string => { // 1. Build outgoing edge map: sourceId -> EdgeInfo[] const outgoingEdges = new Map(); for (const e of context.edges) { const list = outgoingEdges.get(e.sourceId) ?? []; list.push(e); outgoingEdges.set(e.sourceId, list); } // 2. Build node-name lookup const nodeNameMap = new Map(); for (const n of context.nodes) { nodeNameMap.set(n.id, n.name); } // 3. Compute orphan set const orphanNodes = detectOrphanNodes(context.nodes, context.edges); const orphanSet = new Set(orphanNodes.map((n) => n.id)); // 4. Compute endpoint set (sequential nodes with no outgoing edges) const endpointSet = new Set( context.nodes .filter((n) => ['GraphQL', 'HTTP', 'JavaScript', 'ManualStart'].includes(n.kind) && !outgoingEdges.has(n.id)) .map((n) => n.id), ); // 5. Compute selected set const selectedSet = new Set(context.selectedNodeIds ?? []); // 6. Build execution error map: nodeId -> error string const errorMap = new Map(); for (const exec of context.executions) { if (exec.state === 'Failure' && exec.error) { errorMap.set(exec.nodeId, exec.error); } } // 7. Build XML nodes const lines: string[] = ['']; for (const node of context.nodes) { const attrs: string[] = [ `id="${escapeXml(node.id)}"`, `name="${escapeXml(node.name)}"`, `type="${escapeXml(node.kind)}"`, ]; if (node.httpMethod) attrs.push(`method="${escapeXml(node.httpMethod)}"`); if (node.state !== 'Idle') attrs.push(`state="${escapeXml(node.state)}"`); // Prefer execution error over node.info const errorDetail = errorMap.get(node.id) ?? node.info; if (errorDetail) attrs.push(`error="${escapeXml(errorDetail)}"`); if (selectedSet.has(node.id)) attrs.push('selected="true"'); if (orphanSet.has(node.id)) attrs.push('orphan="true"'); if (endpointSet.has(node.id)) attrs.push('endpoint="true"'); const edges = outgoingEdges.get(node.id); if (!edges || edges.length === 0) { lines.push(` `); } else { lines.push(` `); for (const edge of edges) { const targetName = nodeNameMap.get(edge.targetId) ?? edge.targetId; const edgeAttrs = [`id="${escapeXml(edge.id)}"`, `target="${escapeXml(targetName)}"`]; if (edge.sourceHandle) edgeAttrs.push(`handle="${escapeXml(edge.sourceHandle)}"`); lines.push(` `); } lines.push(' '); } } // 8. Variables block (only enabled, skip if empty) const enabledVars = context.variables.filter((v) => v.enabled); if (enabledVars.length > 0) { lines.push(' '); for (const v of enabledVars) { lines.push(` `); } lines.push(' '); } lines.push(''); return lines.join('\n'); }; const buildXmlCompactSummary = (context: FlowContextData): string => { const orphans = detectOrphanNodes(context.nodes, context.edges); // Find endpoint nodes const outgoing = new Set(context.edges.map((e) => e.sourceId)); const endpoints = context.nodes.filter( (n) => ['GraphQL', 'HTTP', 'JavaScript', 'ManualStart'].includes(n.kind) && !outgoing.has(n.id), ); const lines: string[] = [``]; for (const ep of endpoints) { lines.push(` `); } for (const o of orphans) { lines.push(` `); } if (endpoints.length > 5) { lines.push( ` `, ); } lines.push(''); return lines.join('\n'); }; export const buildXmlValidationMessage = ( orphans: Pick[], deadEnds: Pick[], ): string => { if (orphans.length > 0) { const orphanElements = orphans .map((n) => ` `) .join('\n'); return `\n${orphanElements}\n\nConnect these nodes using connectChain before responding.`; } const deadEndElements = deadEnds .map((n) => ` `) .join('\n'); return `\n${deadEndElements}\n\nUse connectChain with nested arrays for fan-in: [["NodeA","NodeB"],"TargetNode"].`; }; export const buildCompactStateSummary = (context: FlowContextData): string => { return buildXmlCompactSummary(context); }; export const buildSystemPrompt = (context: FlowContextData): string => { return `You are a workflow automation assistant. You help users create and modify workflow nodes using natural language. Current Workflow State (ID: ${context.flowId}): ${buildXmlFlowBlock(context)} IMPORTANT RULES: 1. To find the start node, look for a node with type "ManualStart". 2. When connecting nodes, use the node IDs from the workflow XML. 3. Node outputs are stored by node name. In JS code use ctx["NodeName"]. HTTP nodes output { response: { status, body }, request }. GraphQL nodes output { response: { status, body, headers, duration }, request: { url, query, variables, headers } }. ForEach nodes expose { item, key } during iteration. In HTTP/GraphQL fields use {{NodeName.response.body.field}} interpolation — see . 4. A node can connect to multiple targets for parallel execution (all branches run and complete before downstream nodes continue). To run steps sequentially, chain them: Start → A → B → C. Only create Condition nodes when "then" and "else" lead to DIFFERENT destinations — if both go to the same node, skip the Condition. 5. ALWAYS use connectChain for ALL connections — sequential, branching (auto-applies "then"), fan-out, and fan-in. Examples: ["A","B"] single, ["A","B","C"] chain, ["A",["B","C"],"D"] fan-out/fan-in, [["B","C"],"D"] fan-in only. Pass sourceHandle: "else" or "loop" for non-default branches. Use edge id attributes from \`\` elements when calling disconnectNodes. 6. Always confirm what you did after executing tools. 7. If a node has state="Failure", use inspectNode to get detailed error and config information. 8. Use inspectNode with includeOutput: true to see the input/output data of a node's most recent execution. 9. Use updateNode to modify any node's configuration — condition expressions, loop iterations/paths, JS code, HTTP settings, GraphQL settings, or node names. Provide only the fields to change. Arrays (headers, searchParams, assertions) replace the full existing set. 10. Nodes with selected="true" are currently selected on canvas — prefer operating on those nodes unless the user specifies otherwise. 11. Nodes with endpoint="true" are the last in their chain — new nodes connect there. 12. Nodes with orphan="true" are mistakes — they must be connected to the flow via connectChain. 13. Create ALL nodes first, then connect them all at once with connectChain. Do not alternate between creating and connecting. 14. For multi-phase flows, use SEPARATE connectChain calls per phase with a shared fan-in node. Example: ["Start",["GET1","GET2"],"ProcessData"] then ["ProcessData",["POST1","POST2"],"End"]. NEVER use consecutive nested arrays — split them across calls. 15. NEVER delete a node to work around an error. If a node fails or cannot be configured with available tools, explain the problem to the user and suggest what they need to do manually. Deleting user-requested nodes and replacing them with a different type is not allowed unless the user explicitly asks for it. 16. AI nodes require a connected AI Provider node that supplies the LLM model and credentials. The agent cannot create or configure AI Provider nodes — this must be done by the user on the canvas. If an AI node fails with a provider-related error, tell the user they need to add and connect an AI Provider node to it with the appropriate credentials. 17. Use patchHttpNode to add or remove individual headers, query params, or assertions on HTTP nodes without affecting the rest. Use patchGraphqlNode for the same on GraphQL nodes. Use updateNode only when you want to replace the entire set. All text fields in HTTP nodes (url, headers, body, query params) and GraphQL nodes (url, query, variables, headers) support {{}} interpolation. The server resolves these at runtime — use variable references, not hardcoded values. Syntax: - Flow/node variable: {{BASE_URL}}, {{user_id}} - Node output path: {{NodeName.response.body.field}}, {{NodeName.response.status}} - Environment var: {{#env:HOME}}, {{#env:API_SECRET}} - Functions: {{uuid()}}, {{uuid("v7")}}, {{ulid()}}, {{now()}} - File content: {{#file:/path/to/file}} Examples: - URL: {{BASE_URL}}/api/users/{{Get_User.response.body.id}} - Header: Bearer {{Auth.response.body.token}} - Body: {"id": "{{uuid()}}", "name": "{{user_name}}"} The block in the flow XML shows available flow variables — reference them via {{key}}. When a value (base URL, API key) appears in multiple nodes, create a variable with createVariable and reference it. Node names use underscores for spaces: "Get User" → Get_User in references. Assertions use expr-lang syntax (NOT JavaScript). They are evaluated server-side against the HTTP/GraphQL response. Available variables (ONLY these exist — do NOT invent others): - response.status (int), response.body (parsed JSON object/array/string), response.headers (map), response.duration (int ms) - status, body, headers, duration (shorthand aliases for the above) - Flow variables and node outputs from flowVars are also available Built-in functions: len(), type(), all(), any(), one(), none(), map(), filter(), find(), count(), sum(), keys(), values(), has(), get() Syntax rules (NOT JavaScript): - Equality: == and != (NOT === or !==) - Null check: nil (NOT null or undefined) - Boolean operators: and, or, not (also &&, ||, !) - String operators: contains, startsWith, endsWith, matches (regex) - No semicolons, no var/let/const declarations Examples: - response.status == 200 - response.status >= 200 and response.status < 300 - response.body != nil - len(response.body) > 0 - response.headers["Content-Type"] contains "application/json" - response.body.id != nil - response.body.name == "expected" - len(response.body.items) > 0 - type(response.body.count) == "int" IMPORTANT: Every assertion must be a complete boolean expression. Do NOT use bare identifiers like "is_json" or "has_body" — those variables do not exist. Instead write: response.headers["Content-Type"] contains "json", response.body != nil, etc. `; }; ================================================ FILE: packages/client/src/features/agent/index.ts ================================================ export { buildSystemPrompt, useFlowContext } from './context-builder'; export { executeToolCall } from './tool-executor'; export * from './tool-schemas.ts'; export type { AgentChatState, EdgeInfo, FlowContextData, Message, NodeExecutionInfo, NodeInfo, ToolCall, ToolResult, VariableInfo, } from './types'; export { useAgentChat } from './use-agent-chat'; export { useAgentProviderKey, useOpenRouterKey } from './use-agent-provider-key'; export type { AgentProvider } from './use-agent-provider-key'; ================================================ FILE: packages/client/src/features/agent/layout.ts ================================================ import type { EdgeInfo, NodeInfo } from './types'; export type LayoutOrientation = 'horizontal' | 'vertical'; export interface LayoutConfig { orientation: LayoutOrientation; spacingPrimary: number; spacingSecondary: number; startX: number; startY: number; } export interface Position { x: number; y: number; } export interface LayoutResult { levels: Map; maxLevel: number; positions: Map; } export const defaultHorizontalConfig = (): LayoutConfig => ({ orientation: 'horizontal', spacingPrimary: 300, spacingSecondary: 150, startX: 0, startY: 0, }); export const defaultVerticalConfig = (): LayoutConfig => ({ orientation: 'vertical', spacingPrimary: 300, spacingSecondary: 400, startX: 0, startY: 0, }); const buildOutgoingAdjacency = (edges: EdgeInfo[]): Map => { const adj = new Map(); for (const e of edges) { const existing = adj.get(e.sourceId) ?? []; existing.push(e.targetId); adj.set(e.sourceId, existing); } return adj; }; const findStartNode = (nodes: NodeInfo[]): NodeInfo | undefined => nodes.find((n) => n.kind === 'ManualStart'); /** * Layout computes node positions using BFS-based level assignment. * Each node's level is max(parent_levels) + 1, ensuring proper dependency ordering. * Cycles are handled by only visiting each node once. */ export const layout = ( nodes: NodeInfo[], edges: EdgeInfo[], startNodeId: string, config: LayoutConfig, ): LayoutResult => { if (nodes.length === 0) { return { levels: new Map(), maxLevel: 0, positions: new Map(), }; } const outgoingEdges = buildOutgoingAdjacency(edges); const nodeLevels = new Map(); const levelNodes = new Map(); const visited = new Set(); // Start BFS from start node const queue: string[] = [startNodeId]; nodeLevels.set(startNodeId, 0); levelNodes.set(0, [startNodeId]); visited.add(startNodeId); while (queue.length > 0) { const currentNodeId = queue.shift()!; const currentLevel = nodeLevels.get(currentNodeId) ?? 0; // Process all children const children = outgoingEdges.get(currentNodeId) ?? []; for (const childId of children) { // Skip if already visited (handles cycles) if (visited.has(childId)) continue; // Child level is parent level + 1 const childLevel = currentLevel + 1; // Mark as visited and assign level visited.add(childId); nodeLevels.set(childId, childLevel); const currentLevelNodes = levelNodes.get(childLevel) ?? []; currentLevelNodes.push(childId); levelNodes.set(childLevel, currentLevelNodes); queue.push(childId); } } // Find max level let maxLevel = 0; for (const level of levelNodes.keys()) { if (level > maxLevel) maxLevel = level; } // Calculate positions based on orientation const positions = new Map(); for (let level = 0; level <= maxLevel; level++) { const nodesAtLevel = levelNodes.get(level) ?? []; if (nodesAtLevel.length === 0) continue; // Calculate primary axis position (depth direction) let primaryPos = config.orientation === 'horizontal' ? config.startX : config.startY; primaryPos += level * config.spacingPrimary; // Calculate secondary axis positions (centered around start) const totalSecondary = (nodesAtLevel.length - 1) * config.spacingSecondary; let startSecondary = config.orientation === 'horizontal' ? config.startY : config.startX; startSecondary -= totalSecondary / 2; for (let i = 0; i < nodesAtLevel.length; i++) { const nodeId = nodesAtLevel[i]!; const secondaryPos = startSecondary + i * config.spacingSecondary; const pos: Position = config.orientation === 'horizontal' ? { x: primaryPos, y: secondaryPos } : { x: secondaryPos, y: primaryPos }; positions.set(nodeId, pos); } } return { levels: nodeLevels, maxLevel, positions, }; }; /** * layoutNodes is a convenience function that finds the start node and performs layout. * Returns null if no start node is found. */ export const layoutNodes = ( nodes: NodeInfo[], edges: EdgeInfo[], config: LayoutConfig = defaultHorizontalConfig(), ): LayoutResult | null => { const startNode = findStartNode(nodes); if (!startNode) return null; return layout(nodes, edges, startNode.id, config); }; ================================================ FILE: packages/client/src/features/agent/tool-executor.ts ================================================ /* eslint-disable @typescript-eslint/await-thenable, @typescript-eslint/no-base-to-string, @typescript-eslint/no-confusing-void-expression, @typescript-eslint/no-unnecessary-condition, @typescript-eslint/no-unnecessary-type-conversion, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/restrict-template-expressions */ import type { Transport } from '@connectrpc/connect'; import { eq } from '@tanstack/react-db'; import { Ulid } from 'id128'; import { FileKind } from '@the-dev-tools/spec/buf/api/file_system/v1/file_system_pb'; import { FlowItemState, FlowService, HandleKind, NodeKind } from '@the-dev-tools/spec/buf/api/flow/v1/flow_pb'; import { HttpBodyKind, HttpMethod } from '@the-dev-tools/spec/buf/api/http/v1/http_pb'; import { request } from '~/shared/api'; import { queryCollection } from '~/shared/lib'; import type { FlowContextData, ToolCall, ToolResult } from './types'; type CollectionUtils = ReturnType['utils']; type CollectionData = ReturnType; /** * Normalizes JS code references by replacing whitespace with underscores in node names. * - ["Node Name"].field → ["Node_Name"].field */ function normalizeJsCodeReferences(code: string): string { if (!code) return code; // Pattern: ["NodeName"] - replace whitespace in node name with underscores return code.replace(/\["([^"]+)"\]/g, (_, nodeName) => `["${nodeName.replace(/\s+/g, '_')}"]`); } /** * Normalizes condition expressions by: * - Removing bracket/quote syntax: ["NodeName"].field → NodeName.field * - Replacing whitespace with underscores in node names * - Converting JS strict equality/inequality to expr-lang operators * - Converting JS null/undefined to expr-lang nil */ function normalizeConditionSyntax(expr: string): string { if (!expr) return expr; // Pattern: ["NodeName"] - convert to plain identifier with underscores let normalized = expr.replace(/\["([^"]+)"\]/g, (_, nodeName) => nodeName.replace(/\s+/g, '_')); // Convert JS strict equality/inequality to expr-lang operators normalized = normalized.replace(/===/g, '=='); normalized = normalized.replace(/!==/g, '!='); // Convert JS null/undefined to expr-lang nil (word boundaries to avoid partial matches) normalized = normalized.replace(/\bnull\b/g, 'nil'); normalized = normalized.replace(/\bundefined\b/g, 'nil'); return normalized; } /** * Normalizes node names by replacing whitespace with underscores. */ function normalizeNodeName(name: string): string { if (!name) return name; return name.replace(/\s+/g, '_'); } interface Collections { aiCollection: { utils: CollectionUtils }; conditionCollection: { utils: CollectionUtils }; edgeCollection: { utils: CollectionUtils }; executionCollection: CollectionData; fileCollection: CollectionData; forCollection: { utils: CollectionUtils }; forEachCollection: { utils: CollectionUtils }; graphqlAssertCollection: { utils: CollectionUtils }; graphqlCollection: { utils: CollectionUtils }; graphqlHeaderCollection: { utils: CollectionUtils }; httpAssertCollection: { utils: CollectionUtils }; httpBodyRawCollection: { utils: CollectionUtils }; httpCollection: { utils: CollectionUtils }; httpHeaderCollection: { utils: CollectionUtils }; httpSearchParamCollection: { utils: CollectionUtils }; jsCollection: { utils: CollectionUtils }; nodeCollection: { utils: CollectionUtils }; nodeGraphqlCollection: { utils: CollectionUtils }; nodeHttpCollection: { utils: CollectionUtils }; variableCollection: { utils: CollectionUtils }; } interface ToolExecutorContext { collections: Collections; flowContext: FlowContextData; sessionCreatedNodeIds: Set; transport: Transport; waitForFlowCompletion: () => Promise; workspaceId: Uint8Array; } const parseUlid = (id: string): Uint8Array => Ulid.fromCanonical(id).bytes; const HANDLE_KIND_MAP: Record = { ai_tools: HandleKind.AI_TOOLS, else: HandleKind.ELSE, loop: HandleKind.LOOP, then: HandleKind.THEN, }; const HTTP_METHOD_MAP: Record = { DELETE: HttpMethod.DELETE, GET: HttpMethod.GET, HEAD: HttpMethod.HEAD, OPTIONS: HttpMethod.OPTIONS, PATCH: HttpMethod.PATCH, POST: HttpMethod.POST, PUT: HttpMethod.PUT, }; const NODE_KIND_NAMES: Record = { [NodeKind.AI]: 'Ai', [NodeKind.CONDITION]: 'Condition', [NodeKind.FOR]: 'For', [NodeKind.FOR_EACH]: 'ForEach', [NodeKind.GRAPH_Q_L]: 'GraphQL', [NodeKind.HTTP]: 'HTTP', [NodeKind.JS]: 'JavaScript', [NodeKind.MANUAL_START]: 'ManualStart', [NodeKind.UNSPECIFIED]: 'Unknown', }; const FLOW_ITEM_STATE_NAMES: Record = { [FlowItemState.CANCELED]: 'Canceled', [FlowItemState.FAILURE]: 'Failure', [FlowItemState.RUNNING]: 'Running', [FlowItemState.SUCCESS]: 'Success', [FlowItemState.UNSPECIFIED]: 'Idle', }; const AGENT_MAX_FILE_ORDER = 1_000_000_000; const areBytesEqual = (left: Uint8Array, right: Uint8Array): boolean => { if (left.length !== right.length) return false; for (let i = 0; i < left.length; i++) { if (left[i] !== right[i]) return false; } return true; }; const getNextAgentFileOrder = async (fileCollection: CollectionData, workspaceId: Uint8Array): Promise => { const files = await queryCollection((_) => _.from({ item: fileCollection })); let maxOrder = 0; for (const file of files) { if (typeof file !== 'object' || file === null) continue; const fileData = file as Record; const fileWorkspaceId = fileData['workspaceId']; if (!(fileWorkspaceId instanceof Uint8Array)) continue; if (!areBytesEqual(fileWorkspaceId, workspaceId)) continue; const order = fileData['order']; if (typeof order !== 'number') continue; if (!Number.isFinite(order)) continue; if (Math.abs(order) > AGENT_MAX_FILE_ORDER) continue; if (order > maxOrder) maxOrder = order; } return maxOrder + 1; }; const MUTATION_TOOLS = new Set([ 'connectChain', 'createAiNode', 'createConditionNode', 'createForEachNode', 'createForNode', 'createHttpNode', 'createJsNode', 'deleteNode', 'disconnectNodes', 'patchHttpNode', 'updateNode', ]); export const executeToolCall = async ( toolCall: ToolCall, flowId: Uint8Array, context: ToolExecutorContext, ): Promise => { const { arguments: args, id, name } = toolCall; const isMutation = MUTATION_TOOLS.has(name); try { const result = await executeToolInternal(name, args, flowId, context); return { isMutation, result, toolCallId: id }; } catch (error) { return { error: error instanceof Error ? error.message : String(error), isMutation, result: null, toolCallId: id, }; } }; const executeToolInternal = async ( name: string, args: Record, flowId: Uint8Array, context: ToolExecutorContext, ): Promise => { const { collections, flowContext, transport, workspaceId } = context; const { aiCollection, conditionCollection, edgeCollection, executionCollection, fileCollection, forCollection, forEachCollection, httpAssertCollection, httpBodyRawCollection, httpCollection, httpHeaderCollection, httpSearchParamCollection, jsCollection, nodeCollection, nodeHttpCollection, variableCollection, } = collections; switch (name) { case 'connectChain': { const nodeIds = args.nodeIds as (string | string[])[]; const handleOverride = args.sourceHandle as string | undefined; if (handleOverride && !['ai_tools', 'else', 'loop', 'then'].includes(handleOverride)) { throw new Error(`Invalid sourceHandle "${handleOverride}". Valid values: "then", "else", "loop", "ai_tools".`); } if (!nodeIds || nodeIds.length < 2) { throw new Error('connectChain requires at least 2 elements.'); } // Validate: no consecutive nested arrays for (let i = 0; i < nodeIds.length - 1; i++) { if (Array.isArray(nodeIds[i]) && Array.isArray(nodeIds[i + 1])) { throw new Error( `connectChain: consecutive nested arrays at positions ${i} and ${i + 1} are not allowed. ` + `Insert a shared fan-in node between the groups, or split into separate connectChain calls. ` + `Example: instead of ["A",["B","C"],["D","E"],"F"], use ["A",["B","C"],"Mid"] then ["Mid",["D","E"],"F"].`, ); } } // Validate: parallel groups have ≥2 unique IDs for (let i = 0; i < nodeIds.length; i++) { const el = nodeIds[i]!; if (Array.isArray(el)) { const unique = new Set(el); if (unique.size < 2) { throw new Error(`connectChain: parallel group at position ${i} must have at least 2 unique node IDs.`); } if (unique.size !== el.length) { throw new Error(`connectChain: parallel group at position ${i} contains duplicate node IDs.`); } } } // Expand consecutive element pairs into edge pairs const edgePairs: [string, string][] = []; for (let i = 0; i < nodeIds.length - 1; i++) { const current = nodeIds[i]!; const next = nodeIds[i + 1]!; const sources = Array.isArray(current) ? current : [current]; const targets = Array.isArray(next) ? next : [next]; for (const s of sources) for (const t of targets) edgePairs.push([s, t]); } const edgeIds: string[] = []; const errors: string[] = []; // Process SEQUENTIALLY to avoid parallel race conditions for (let idx = 0; idx < edgePairs.length; idx++) { const [sourceIdStr, targetIdStr] = edgePairs[idx]!; try { const sourceId = parseUlid(sourceIdStr); const targetId = parseUlid(targetIdStr); const edgeId = Ulid.generate().bytes; // Query live edges to check for existing outgoing connections const existingEdges = await queryCollection((_) => _.from({ e: edgeCollection }).where((_) => eq(_.e.sourceId, sourceId)), ); const duplicateEdge = existingEdges.find((e) => Ulid.construct(e.targetId).toCanonical() === targetIdStr); if (duplicateEdge) { errors.push(`Edge ${idx}: Edge from ${sourceIdStr} to ${targetIdStr} already exists. Skipped.`); continue; } // Determine handle kind for branching nodes const sourceNode = flowContext.nodes.find((n) => n.id === sourceIdStr); const isBranching = sourceNode && ['Condition', 'For', 'ForEach'].includes(sourceNode.kind); const isAiSource = sourceNode?.kind === 'Ai'; // Validate handle is valid for the specific node type if (isBranching && handleOverride) { const validHandles = sourceNode.kind === 'Condition' ? ['then', 'else'] : ['then', 'loop']; if (!validHandles.includes(handleOverride)) { errors.push( `Edge ${idx}: Invalid sourceHandle "${handleOverride}" for ${sourceNode.kind} node "${sourceNode.name}". ` + `Valid handles: ${validHandles.join(', ')}. Skipped.`, ); continue; } } if (isAiSource && handleOverride) { const validHandles = ['ai_tools']; if (!validHandles.includes(handleOverride)) { errors.push( `Edge ${idx}: Invalid sourceHandle "${handleOverride}" for Ai node "${sourceNode.name}". ` + `Valid handles: ${validHandles.join(', ')}. Skipped.`, ); continue; } } const edgeHandle = isBranching ? (HANDLE_KIND_MAP[handleOverride ?? 'then'] ?? HandleKind.THEN) : isAiSource && handleOverride ? HANDLE_KIND_MAP[handleOverride] : undefined; await edgeCollection.utils.insert({ edgeId, flowId, sourceId, targetId, ...(edgeHandle !== undefined ? { sourceHandle: edgeHandle } : {}), }); edgeIds.push(Ulid.construct(edgeId).toCanonical()); } catch (error) { errors.push(`Edge ${idx}: ${error instanceof Error ? error.message : String(error)}`); } } return { edgeIds, edgesCreated: edgeIds.length, ...(errors.length > 0 ? { errors } : {}), }; } case 'createAiNode': { const nodeId = Ulid.generate().bytes; const position = (args.position as { x: number; y: number }) ?? { x: 0, y: 0 }; const nodeName = normalizeNodeName(args.name as string); const prompt = args.prompt as string; const maxIterations = (args.maxIterations as number | undefined) ?? 5; if (!Number.isInteger(maxIterations) || maxIterations <= 0) { throw new Error(`maxIterations must be a positive integer, got: ${maxIterations}`); } // Call both inserts before awaiting to ensure optimistic updates happen // synchronously before any sync responses can arrive from the server const nodePromise = nodeCollection.utils.insert({ flowId, kind: NodeKind.AI, name: nodeName, nodeId, position, }); const aiPromise = aiCollection.utils.insert({ maxIterations, nodeId, prompt, }); await Promise.all([nodePromise, aiPromise]); const canonicalId = Ulid.construct(nodeId).toCanonical(); context.sessionCreatedNodeIds.add(canonicalId); return { name: nodeName, nodeId: canonicalId }; } case 'createConditionNode': { const nodeId = Ulid.generate().bytes; const position = (args.position as { x: number; y: number }) ?? { x: 0, y: 0 }; const condition = normalizeConditionSyntax(args.condition as string); const nodeName = normalizeNodeName(args.name as string); // Call both inserts before awaiting to ensure optimistic updates happen // synchronously before any sync responses can arrive from the server const nodePromise = nodeCollection.utils.insert({ flowId, kind: NodeKind.CONDITION, name: nodeName, nodeId, position, }); const conditionPromise = conditionCollection.utils.insert({ condition, nodeId, }); await Promise.all([nodePromise, conditionPromise]); { const canonicalId = Ulid.construct(nodeId).toCanonical(); context.sessionCreatedNodeIds.add(canonicalId); return { name: nodeName, nodeId: canonicalId }; } } case 'createForEachNode': { // Validate path is provided const rawPath = args.path as string | undefined; if (!rawPath || rawPath.trim() === '') { throw new Error( 'path is required for ForEach nodes. ' + 'Provide an expression for the array/object to iterate. ' + 'Example: HTTP_Request.response.body.items', ); } // Validate break condition is provided const rawCondition = args.condition as string | undefined; if (!rawCondition || rawCondition.trim() === '') { throw new Error( 'condition (break condition) is required for ForEach nodes. ' + 'Provide an expression that evaluates to true to exit the loop early. ' + 'Example: ForEach_Loop.key >= 5', ); } const nodeId = Ulid.generate().bytes; const position = (args.position as { x: number; y: number }) ?? { x: 0, y: 0 }; const path = normalizeConditionSyntax(rawPath); const condition = normalizeConditionSyntax(rawCondition); const errorHandling = args.errorHandling as string; const nodeName = normalizeNodeName(args.name as string); // Call both inserts before awaiting to ensure optimistic updates happen // synchronously before any sync responses can arrive from the server const nodePromise = nodeCollection.utils.insert({ flowId, kind: NodeKind.FOR_EACH, name: nodeName, nodeId, position, }); const forEachPromise = forEachCollection.utils.insert({ condition, errorHandling: errorHandling === 'break' ? 1 : 0, nodeId, path, }); await Promise.all([nodePromise, forEachPromise]); { const canonicalId = Ulid.construct(nodeId).toCanonical(); context.sessionCreatedNodeIds.add(canonicalId); return { name: nodeName, nodeId: canonicalId }; } } case 'createForNode': { // Validate iterations is a positive integer const iterations = args.iterations as number | undefined; if (iterations === undefined || iterations === null) { throw new Error('iterations is required for For nodes. Specify the number of times to iterate.'); } if (!Number.isInteger(iterations) || iterations <= 0) { throw new Error(`iterations must be a positive integer, got: ${iterations}`); } // Validate break condition is provided const rawCondition = args.condition as string | undefined; if (!rawCondition || rawCondition.trim() === '') { throw new Error( 'condition (break condition) is required for For nodes. ' + 'Provide an expression that evaluates to true to exit the loop early. ' + 'Example: Counter.count >= 10', ); } const nodeId = Ulid.generate().bytes; const position = (args.position as { x: number; y: number }) ?? { x: 0, y: 0 }; const condition = normalizeConditionSyntax(rawCondition); const errorHandling = args.errorHandling as string; const nodeName = normalizeNodeName(args.name as string); // Call both inserts before awaiting to ensure optimistic updates happen // synchronously before any sync responses can arrive from the server const nodePromise = nodeCollection.utils.insert({ flowId, kind: NodeKind.FOR, name: nodeName, nodeId, position, }); const forPromise = forCollection.utils.insert({ condition, errorHandling: errorHandling === 'break' ? 1 : 0, iterations, nodeId, }); await Promise.all([nodePromise, forPromise]); { const canonicalId = Ulid.construct(nodeId).toCanonical(); context.sessionCreatedNodeIds.add(canonicalId); return { name: nodeName, nodeId: canonicalId }; } } case 'createHttpNode': { const nodeId = Ulid.generate().bytes; const position = (args.position as { x: number; y: number }) ?? { x: 0, y: 0 }; const nodeName = normalizeNodeName(args.name as string); let httpId: Uint8Array; let httpIdStr: string; const insertPromises: Promise[] = []; if (args.httpId) { // Use existing HTTP request httpId = parseUlid(args.httpId as string); httpIdStr = args.httpId as string; } else { // Validate HTTP method const methodStr = ((args.method as string) ?? '').toUpperCase(); if (!methodStr) { throw new Error( 'method is required when creating a new HTTP node. ' + 'Valid methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS', ); } const method = HTTP_METHOD_MAP[methodStr]; if (method === undefined) { throw new Error( `Invalid HTTP method: "${args.method}". ` + 'Valid methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS', ); } const url = (args.url as string) ?? ''; const methodsWithBody = new Set(['PATCH', 'POST', 'PUT']); const needsBody = methodsWithBody.has(methodStr); // Create new HTTP request with appropriate bodyKind httpId = Ulid.generate().bytes; httpIdStr = Ulid.construct(httpId).toCanonical(); insertPromises.push( httpCollection.utils.insert({ bodyKind: needsBody ? HttpBodyKind.RAW : HttpBodyKind.UNSPECIFIED, httpId, method, name: nodeName, url, }), getNextAgentFileOrder(fileCollection, workspaceId).then((order) => fileCollection.utils.insert({ fileId: httpId, kind: FileKind.HTTP, order, workspaceId, }), ), ); // If a body is provided and the method supports it, insert the raw body const body = args.body as string | undefined; if (body && needsBody) { insertPromises.push( collections.httpBodyRawCollection.utils.insert({ data: body, httpId, }), ); } else if (body && !needsBody) { throw new Error( `Cannot set body for ${methodStr} requests. ` + 'Only POST, PUT, and PATCH methods support a request body.', ); } } // Call all inserts before awaiting to ensure optimistic updates happen // synchronously before any sync responses can arrive from the server insertPromises.push( nodeCollection.utils.insert({ flowId, kind: NodeKind.HTTP, name: nodeName, nodeId, position, }), nodeHttpCollection.utils.insert({ httpId, nodeId, }), ); await Promise.all(insertPromises); { const canonicalId = Ulid.construct(nodeId).toCanonical(); context.sessionCreatedNodeIds.add(canonicalId); return { httpId: httpIdStr, name: nodeName, nodeId: canonicalId }; } } case 'createGraphQLNode': { const nodeId = Ulid.generate().bytes; const position = (args.position as { x: number; y: number }) ?? { x: 0, y: 0 }; const nodeName = normalizeNodeName(args.name as string); const url = (args.url as string) ?? ''; const query = (args.query as string) ?? ''; const variables = (args.variables as string) ?? ''; const graphqlId = Ulid.generate().bytes; const graphqlIdStr = Ulid.construct(graphqlId).toCanonical(); const insertPromises: Promise[] = [ collections.graphqlCollection.utils.insert({ graphqlId, name: nodeName, query, url, variables, }), getNextAgentFileOrder(fileCollection, workspaceId).then((order) => fileCollection.utils.insert({ fileId: graphqlId, kind: FileKind.GRAPH_Q_L, order, workspaceId, }), ), nodeCollection.utils.insert({ flowId, kind: NodeKind.GRAPH_Q_L, name: nodeName, nodeId, position, }), collections.nodeGraphqlCollection.utils.insert({ graphqlId, nodeId, }), ]; await Promise.all(insertPromises); { const canonicalId = Ulid.construct(nodeId).toCanonical(); context.sessionCreatedNodeIds.add(canonicalId); return { graphqlId: graphqlIdStr, name: nodeName, nodeId: canonicalId }; } } case 'createJsNode': { const nodeId = Ulid.generate().bytes; const position = (args.position as { x: number; y: number }) ?? { x: 0, y: 0 }; const code = normalizeJsCodeReferences(args.code as string); const nodeName = normalizeNodeName(args.name as string); // Call both inserts before awaiting to ensure optimistic updates happen // synchronously before any sync responses can arrive from the server const nodePromise = nodeCollection.utils.insert({ flowId, kind: NodeKind.JS, name: nodeName, nodeId, position, }); const jsPromise = jsCollection.utils.insert({ code: `export default function(ctx) {\n ${code}\n}`, nodeId, }); await Promise.all([nodePromise, jsPromise]); { const canonicalId = Ulid.construct(nodeId).toCanonical(); context.sessionCreatedNodeIds.add(canonicalId); return { name: nodeName, nodeId: canonicalId }; } } case 'createVariable': { const flowVariableId = Ulid.generate().bytes; const key = args.key as string; const value = args.value as string; const enabled = args.enabled as boolean; const description = args.description as string; const order = args.order as number; // Await to ensure server persistence before returning await variableCollection.utils.insert({ description, enabled, flowId, flowVariableId, key, order, value, }); return { flowVariableId: Ulid.construct(flowVariableId).toCanonical() }; } case 'deleteNode': { const nodeIdStr = args.nodeId as string; if (context.sessionCreatedNodeIds.has(nodeIdStr)) { return { blocked: true, message: 'Cannot delete a node you just created. If the node has an error, explain the issue to the user and suggest what they can do to fix it (e.g., adding an AI Provider node). Do NOT delete and recreate with a different node type.', }; } const nodeId = parseUlid(nodeIdStr); // Query live edges from collection to avoid stale flowContext during batched tool calls. const liveEdges = await queryCollection((_) => _.from({ edge: edgeCollection }).where((_) => eq(_.edge.flowId, flowId)), ); const connectedEdgeIds = liveEdges .filter((edge) => edge.edgeId != null && edge.sourceId != null && edge.targetId != null) .filter((edge) => areBytesEqual(edge.sourceId, nodeId) || areBytesEqual(edge.targetId, nodeId)) .map((edge) => edge.edgeId); for (const edgeId of connectedEdgeIds) { edgeCollection.utils.delete({ edgeId }); } nodeCollection.utils.delete({ nodeId }); return { deletedEdges: connectedEdgeIds.length, success: true }; } case 'disconnectNodes': { const edgeId = parseUlid(args.edgeId as string); edgeCollection.utils.delete({ edgeId }); return { success: true }; } case 'flowRunRequest': { await request({ input: { flowId }, method: FlowService.method.flowRun, transport, }); await context.waitForFlowCompletion(); return { message: 'Flow execution completed. Use getFlowExecutionSummary to inspect results.', success: true, }; } case 'flowStopRequest': { await request({ input: { flowId }, method: FlowService.method.flowStop, transport, }); return { message: 'Flow execution stopped', success: true }; } case 'getFlowExecutionSummary': { // Query fresh nodes from the collection const freshNodes = await queryCollection((_) => _.from({ node: collections.nodeCollection }).where((_) => eq(_.node.flowId, flowId)), ); // Build a set of node IDs belonging to this flow const nodeIdSet = new Set( freshNodes.filter((n) => n.nodeId != null).map((n) => Ulid.construct(n.nodeId).toCanonical()), ); // Query all executions and filter to this flow's nodes const allExecs = await queryCollection((_) => _.from({ exec: collections.executionCollection })); const flowExecs = allExecs.filter( (e) => e.nodeId != null && nodeIdSet.has(Ulid.construct(e.nodeId).toCanonical()), ); const executedNodeIds = new Set(flowExecs.map((e) => Ulid.construct(e.nodeId).toCanonical())); // Build executed nodes list with state from execution records const executedNodes = freshNodes .filter((n) => n.nodeId != null && executedNodeIds.has(Ulid.construct(n.nodeId).toCanonical())) .map((n) => { const nodeExecs = flowExecs .filter((e) => Ulid.construct(e.nodeId).toCanonical() === Ulid.construct(n.nodeId).toCanonical()) .sort((a, b) => { if (!a.completedAt && !b.completedAt) return 0; if (!a.completedAt) return 1; if (!b.completedAt) return -1; return Number(b.completedAt - a.completedAt); }); const latestExec = nodeExecs[0]; return { id: Ulid.construct(n.nodeId).toCanonical(), name: n.name, state: latestExec ? (FLOW_ITEM_STATE_NAMES[latestExec.state] ?? 'Unknown') : 'Unknown', }; }); // Never-reached: non-ManualStart nodes without any executions const neverReachedNodes = freshNodes .filter( (n) => n.nodeId != null && n.kind !== NodeKind.MANUAL_START && !executedNodeIds.has(Ulid.construct(n.nodeId).toCanonical()), ) .map((n) => ({ id: Ulid.construct(n.nodeId).toCanonical(), kind: NODE_KIND_NAMES[n.kind] ?? 'Unknown', name: n.name, })); return { executedNodes, neverReachedNodes, warning: neverReachedNodes.length > 0 ? `${neverReachedNodes.length} node(s) were never reached during execution. This may indicate an untaken branch or a wiring problem.` : undefined, }; } case 'inspectNode': { const nodeIdStr = args.nodeId as string; const includeOutput = (args.includeOutput as boolean) ?? false; const node = flowContext.nodes.find((n) => n.id === nodeIdStr); if (!node) throw new Error(`Node not found: ${nodeIdStr}`); const nodeIdBytes = parseUlid(nodeIdStr); // Base info (always returned) const result: Record = { error: node.info ?? undefined, id: node.id, kind: node.kind, name: node.name, state: node.state, }; // Type-specific config switch (node.kind) { case 'Ai': { const [aiData] = await queryCollection((_) => _.from({ ai: aiCollection }) .where((_) => eq(_.ai.nodeId, nodeIdBytes)) .findOne(), ); result.prompt = aiData?.prompt ?? ''; result.maxIterations = aiData?.maxIterations ?? 5; break; } case 'Condition': { const [condData] = await queryCollection((_) => _.from({ cond: conditionCollection }) .where((_) => eq(_.cond.nodeId, nodeIdBytes)) .findOne(), ); result.condition = condData?.condition ?? ''; break; } case 'For': { const [forData] = await queryCollection((_) => _.from({ f: forCollection }) .where((_) => eq(_.f.nodeId, nodeIdBytes)) .findOne(), ); result.iterations = forData?.iterations; result.condition = forData?.condition ?? ''; result.errorHandling = forData?.errorHandling === 1 ? 'break' : 'continue'; break; } case 'ForEach': { const [feData] = await queryCollection((_) => _.from({ fe: forEachCollection }) .where((_) => eq(_.fe.nodeId, nodeIdBytes)) .findOne(), ); result.path = feData?.path ?? ''; result.condition = feData?.condition ?? ''; result.errorHandling = feData?.errorHandling === 1 ? 'break' : 'continue'; break; } case 'GraphQL': { if (!node.graphqlId) break; const graphqlIdBytes = parseUlid(node.graphqlId); const [graphqlData] = await queryCollection((_) => _.from({ gql: collections.graphqlCollection }) .where((_) => eq(_.gql.graphqlId, graphqlIdBytes)) .findOne(), ); const gqlHeaders = await queryCollection((_) => _.from({ h: collections.graphqlHeaderCollection }).where((_) => eq(_.h.graphqlId, graphqlIdBytes)), ); const gqlAsserts = await queryCollection((_) => _.from({ a: collections.graphqlAssertCollection }).where((_) => eq(_.a.graphqlId, graphqlIdBytes)), ); result.graphqlId = node.graphqlId; result.url = graphqlData?.url ?? ''; result.query = graphqlData?.query ?? ''; result.variables = graphqlData?.variables ?? ''; result.headers = gqlHeaders.map((h) => ({ enabled: h.enabled, id: h.graphqlHeaderId ? Ulid.construct(h.graphqlHeaderId).toCanonical() : undefined, key: h.key, value: h.value, })); result.assertions = gqlAsserts.map((a) => ({ enabled: a.enabled, id: a.graphqlAssertId ? Ulid.construct(a.graphqlAssertId).toCanonical() : undefined, value: a.value, })); break; } case 'HTTP': { if (!node.httpId) break; const httpIdBytes = parseUlid(node.httpId); const [httpData] = await queryCollection((_) => _.from({ http: httpCollection }) .where((_) => eq(_.http.httpId, httpIdBytes)) .findOne(), ); const searchParams = await queryCollection((_) => _.from({ sp: httpSearchParamCollection }).where((_) => eq(_.sp.httpId, httpIdBytes)), ); const headers = await queryCollection((_) => _.from({ h: httpHeaderCollection }).where((_) => eq(_.h.httpId, httpIdBytes)), ); const bodyRaw = await queryCollection((_) => _.from({ br: httpBodyRawCollection }).where((_) => eq(_.br.httpId, httpIdBytes)), ); const asserts = await queryCollection((_) => _.from({ a: httpAssertCollection }).where((_) => eq(_.a.httpId, httpIdBytes)), ); const HTTP_METHOD_NAMES: Record = { 0: 'UNSPECIFIED', 1: 'GET', 2: 'POST', 3: 'PUT', 4: 'PATCH', 5: 'DELETE', 6: 'HEAD', 7: 'OPTIONS', 8: 'CONNECT', }; result.httpId = node.httpId; result.url = httpData?.url ?? ''; result.method = HTTP_METHOD_NAMES[httpData?.method ?? 0] ?? 'UNSPECIFIED'; result.headers = headers.map((h) => ({ enabled: h.enabled, id: h.httpHeaderId ? Ulid.construct(h.httpHeaderId).toCanonical() : undefined, key: h.key, value: h.value, })); result.searchParams = searchParams.map((sp) => ({ enabled: sp.enabled, id: sp.httpSearchParamId ? Ulid.construct(sp.httpSearchParamId).toCanonical() : undefined, key: sp.key, value: sp.value, })); result.body = bodyRaw.length > 0 ? bodyRaw[0]?.data : undefined; result.assertions = asserts.map((a) => ({ enabled: a.enabled, id: a.httpAssertId ? Ulid.construct(a.httpAssertId).toCanonical() : undefined, value: a.value, })); break; } case 'JavaScript': { const [jsData] = await queryCollection((_) => _.from({ js: jsCollection }) .where((_) => eq(_.js.nodeId, nodeIdBytes)) .findOne(), ); result.code = jsData?.code ?? ''; break; } } // Query execution data fresh from collection (not cached flowContext) const allExecs = await queryCollection((_) => _.from({ exec: executionCollection })); const nodeExecs = allExecs .filter((e) => e.nodeId != null && Ulid.construct(e.nodeId).toCanonical() === nodeIdStr) .sort((a, b) => { if (!a.completedAt && !b.completedAt) return 0; if (!a.completedAt) return 1; if (!b.completedAt) return -1; return Number(b.completedAt - a.completedAt); }); if (nodeExecs.length > 0) { const latest = nodeExecs[0]!; result.execution = { completedAt: latest.completedAt instanceof Date ? latest.completedAt.toISOString() : latest.completedAt ? String(latest.completedAt) : undefined, error: latest.error ?? undefined, state: FLOW_ITEM_STATE_NAMES[latest.state] ?? 'Unknown', }; if (includeOutput) { const MAX_OUTPUT_LENGTH = 10000; const truncateData = (data: unknown): unknown => { if (data == null) return data; const str = typeof data === 'string' ? data : JSON.stringify(data); if (str.length <= MAX_OUTPUT_LENGTH) return data; return { _originalLength: str.length, _truncated: true, preview: str.slice(0, MAX_OUTPUT_LENGTH) + '...', }; }; (result.execution as Record).input = truncateData(latest.input); (result.execution as Record).output = truncateData(latest.output); } } return result; } case 'patchGraphqlNode': { const nodeIdStr = args.nodeId as string; const node = flowContext.nodes.find((n) => n.id === nodeIdStr); if (!node) throw new Error(`Node not found: ${nodeIdStr}`); if (node.kind !== 'GraphQL') throw new Error(`patchGraphqlNode only works on GraphQL nodes, got: ${node.kind}`); if (!node.graphqlId) throw new Error(`GraphQL node "${node.name}" has no associated GraphQL request`); const graphqlIdBytes = parseUlid(node.graphqlId); const patchedFields: string[] = []; const warnings: string[] = []; // --- Remove headers --- const removeHeaderIds = args.removeHeaderIds as string[] | undefined; const addHeaders = args.addHeaders as | undefined | { description?: string; enabled?: boolean; key: string; value?: string }[]; if (removeHeaderIds?.length) { const existingHeaders = await queryCollection((_) => _.from({ h: collections.graphqlHeaderCollection }).where((_) => eq(_.h.graphqlId, graphqlIdBytes)), ); const existingHeaderIds = new Set( existingHeaders .filter((h) => h.graphqlHeaderId != null) .map((h) => Ulid.construct(h.graphqlHeaderId).toCanonical()), ); let removedCount = 0; for (const id of removeHeaderIds) { if (!existingHeaderIds.has(id)) continue; collections.graphqlHeaderCollection.utils.delete({ graphqlHeaderId: parseUlid(id) }); removedCount++; } if (removedCount > 0) { patchedFields.push(`removedHeaders(${removedCount})`); } const skippedCount = removeHeaderIds.length - removedCount; if (skippedCount > 0) { warnings.push(`Skipped ${skippedCount} header ID(s) not belonging to this GraphQL node.`); } } // --- Add headers --- if (addHeaders?.length) { const existingHeaders = await queryCollection((_) => _.from({ h: collections.graphqlHeaderCollection }).where((_) => eq(_.h.graphqlId, graphqlIdBytes)), ); const maxOrder = existingHeaders.reduce((max, h) => Math.max(max, h.order ?? -1), -1); let nextOrder = maxOrder + 1; for (const h of addHeaders) { await collections.graphqlHeaderCollection.utils.insert({ description: h.description ?? '', enabled: h.enabled ?? true, graphqlHeaderId: Ulid.generate().bytes, graphqlId: graphqlIdBytes, key: h.key, order: nextOrder++, value: h.value ?? '', }); } patchedFields.push(`addedHeaders(${addHeaders.length})`); } // --- Remove assertions --- const removeAssertionIds = args.removeAssertionIds as string[] | undefined; const addAssertions = args.addAssertions as undefined | { enabled?: boolean; value: string }[]; if (removeAssertionIds?.length) { const existingAssertions = await queryCollection((_) => _.from({ a: collections.graphqlAssertCollection }).where((_) => eq(_.a.graphqlId, graphqlIdBytes)), ); const existingAssertionIds = new Set( existingAssertions .filter((a) => a.graphqlAssertId != null) .map((a) => Ulid.construct(a.graphqlAssertId).toCanonical()), ); let removedCount = 0; for (const id of removeAssertionIds) { if (!existingAssertionIds.has(id)) continue; collections.graphqlAssertCollection.utils.delete({ graphqlAssertId: parseUlid(id) }); removedCount++; } if (removedCount > 0) { patchedFields.push(`removedAssertions(${removedCount})`); } const skippedCount = removeAssertionIds.length - removedCount; if (skippedCount > 0) { warnings.push(`Skipped ${skippedCount} assertion ID(s) not belonging to this GraphQL node.`); } } // --- Add assertions --- if (addAssertions?.length) { const existingAssertions = await queryCollection((_) => _.from({ a: collections.graphqlAssertCollection }).where((_) => eq(_.a.graphqlId, graphqlIdBytes)), ); const maxOrder = existingAssertions.reduce((max, a) => Math.max(max, a.order ?? -1), -1); let nextOrder = maxOrder + 1; for (const a of addAssertions) { await collections.graphqlAssertCollection.utils.insert({ enabled: a.enabled ?? true, graphqlAssertId: Ulid.generate().bytes, graphqlId: graphqlIdBytes, order: nextOrder++, value: normalizeConditionSyntax(a.value), }); } patchedFields.push(`addedAssertions(${addAssertions.length})`); } if (patchedFields.length === 0) { return { message: 'No patch operations provided', success: false }; } return { patchedFields, success: true, warnings: warnings.length > 0 ? warnings : undefined }; } case 'patchHttpNode': { const nodeIdStr = args.nodeId as string; const node = flowContext.nodes.find((n) => n.id === nodeIdStr); if (!node) throw new Error(`Node not found: ${nodeIdStr}`); if (node.kind !== 'HTTP') throw new Error(`patchHttpNode only works on HTTP nodes, got: ${node.kind}`); if (!node.httpId) throw new Error(`HTTP node "${node.name}" has no associated HTTP request`); const httpIdBytes = parseUlid(node.httpId); const patchedFields: string[] = []; const warnings: string[] = []; // --- Remove headers --- const removeHeaderIds = args.removeHeaderIds as string[] | undefined; const addHeaders = args.addHeaders as | undefined | { description?: string; enabled?: boolean; key: string; value?: string }[]; if (removeHeaderIds?.length) { const existingHeaders = await queryCollection((_) => _.from({ h: httpHeaderCollection }).where((_) => eq(_.h.httpId, httpIdBytes)), ); const existingHeaderIds = new Set( existingHeaders .filter((h) => h.httpHeaderId != null) .map((h) => Ulid.construct(h.httpHeaderId).toCanonical()), ); let removedCount = 0; for (const id of removeHeaderIds) { if (!existingHeaderIds.has(id)) continue; httpHeaderCollection.utils.delete({ httpHeaderId: parseUlid(id) }); removedCount++; } if (removedCount > 0) { patchedFields.push(`removedHeaders(${removedCount})`); } const skippedCount = removeHeaderIds.length - removedCount; if (skippedCount > 0) { warnings.push(`Skipped ${skippedCount} header ID(s) not belonging to this HTTP node.`); } } // --- Add headers --- if (addHeaders?.length) { const existingHeaders = await queryCollection((_) => _.from({ h: httpHeaderCollection }).where((_) => eq(_.h.httpId, httpIdBytes)), ); const maxOrder = existingHeaders.reduce((max, h) => Math.max(max, h.order ?? -1), -1); let nextOrder = maxOrder + 1; for (const h of addHeaders) { await httpHeaderCollection.utils.insert({ description: h.description ?? '', enabled: h.enabled ?? true, httpHeaderId: Ulid.generate().bytes, httpId: httpIdBytes, key: h.key, order: nextOrder++, value: h.value ?? '', }); } patchedFields.push(`addedHeaders(${addHeaders.length})`); } // --- Remove search params --- const removeSearchParamIds = args.removeSearchParamIds as string[] | undefined; const addSearchParams = args.addSearchParams as | undefined | { description?: string; enabled?: boolean; key: string; value?: string }[]; if (removeSearchParamIds?.length) { const existingSearchParams = await queryCollection((_) => _.from({ sp: httpSearchParamCollection }).where((_) => eq(_.sp.httpId, httpIdBytes)), ); const existingSearchParamIds = new Set( existingSearchParams .filter((sp) => sp.httpSearchParamId != null) .map((sp) => Ulid.construct(sp.httpSearchParamId).toCanonical()), ); let removedCount = 0; for (const id of removeSearchParamIds) { if (!existingSearchParamIds.has(id)) continue; httpSearchParamCollection.utils.delete({ httpSearchParamId: parseUlid(id) }); removedCount++; } if (removedCount > 0) { patchedFields.push(`removedSearchParams(${removedCount})`); } const skippedCount = removeSearchParamIds.length - removedCount; if (skippedCount > 0) { warnings.push(`Skipped ${skippedCount} query param ID(s) not belonging to this HTTP node.`); } } // --- Add search params --- if (addSearchParams?.length) { const existingSearchParams = await queryCollection((_) => _.from({ sp: httpSearchParamCollection }).where((_) => eq(_.sp.httpId, httpIdBytes)), ); const maxOrder = existingSearchParams.reduce((max, sp) => Math.max(max, sp.order ?? -1), -1); let nextOrder = maxOrder + 1; for (const sp of addSearchParams) { await httpSearchParamCollection.utils.insert({ description: sp.description ?? '', enabled: sp.enabled ?? true, httpId: httpIdBytes, httpSearchParamId: Ulid.generate().bytes, key: sp.key, order: nextOrder++, value: sp.value ?? '', }); } patchedFields.push(`addedSearchParams(${addSearchParams.length})`); } // --- Remove assertions --- const removeAssertionIds = args.removeAssertionIds as string[] | undefined; const addAssertions = args.addAssertions as undefined | { enabled?: boolean; value: string }[]; if (removeAssertionIds?.length) { const existingAssertions = await queryCollection((_) => _.from({ a: httpAssertCollection }).where((_) => eq(_.a.httpId, httpIdBytes)), ); const existingAssertionIds = new Set( existingAssertions .filter((a) => a.httpAssertId != null) .map((a) => Ulid.construct(a.httpAssertId).toCanonical()), ); let removedCount = 0; for (const id of removeAssertionIds) { if (!existingAssertionIds.has(id)) continue; httpAssertCollection.utils.delete({ httpAssertId: parseUlid(id) }); removedCount++; } if (removedCount > 0) { patchedFields.push(`removedAssertions(${removedCount})`); } const skippedCount = removeAssertionIds.length - removedCount; if (skippedCount > 0) { warnings.push(`Skipped ${skippedCount} assertion ID(s) not belonging to this HTTP node.`); } } // --- Add assertions --- if (addAssertions?.length) { const existingAssertions = await queryCollection((_) => _.from({ a: httpAssertCollection }).where((_) => eq(_.a.httpId, httpIdBytes)), ); const maxOrder = existingAssertions.reduce((max, a) => Math.max(max, a.order ?? -1), -1); let nextOrder = maxOrder + 1; for (const a of addAssertions) { await httpAssertCollection.utils.insert({ enabled: a.enabled ?? true, httpAssertId: Ulid.generate().bytes, httpId: httpIdBytes, order: nextOrder++, value: normalizeConditionSyntax(a.value), }); } patchedFields.push(`addedAssertions(${addAssertions.length})`); } if (patchedFields.length === 0) { return { message: 'No patch operations provided', success: false }; } return { patchedFields, success: true, warnings: warnings.length > 0 ? warnings : undefined }; } case 'updateNode': { const nodeIdStr = args.nodeId as string; const node = flowContext.nodes.find((n) => n.id === nodeIdStr); if (!node) throw new Error(`Node not found: ${nodeIdStr}`); const nodeIdBytes = parseUlid(nodeIdStr); const updatedFields: string[] = []; // --- Base fields (any node type) --- if (args.name !== undefined) { nodeCollection.utils.update({ name: normalizeNodeName(args.name as string), nodeId: nodeIdBytes, }); updatedFields.push('name'); } // --- Type-specific fields --- switch (node.kind) { case 'Ai': { const aiUpdates: Record = { nodeId: nodeIdBytes }; let hasAiUpdates = false; if (args.prompt !== undefined) { aiUpdates.prompt = args.prompt; hasAiUpdates = true; updatedFields.push('prompt'); } if (args.maxIterations !== undefined) { const maxIterations = args.maxIterations as number; if (!Number.isInteger(maxIterations) || maxIterations <= 0) { throw new Error(`maxIterations must be a positive integer, got: ${maxIterations}`); } aiUpdates.maxIterations = maxIterations; hasAiUpdates = true; updatedFields.push('maxIterations'); } if (hasAiUpdates) aiCollection.utils.update(aiUpdates); break; } case 'Condition': { if (args.condition !== undefined) { conditionCollection.utils.update({ condition: normalizeConditionSyntax(args.condition as string), nodeId: nodeIdBytes, }); updatedFields.push('condition'); } break; } case 'For': { const forUpdates: Record = { nodeId: nodeIdBytes }; let hasForUpdates = false; if (args.iterations !== undefined) { const iterations = args.iterations as number; if (!Number.isInteger(iterations) || iterations <= 0) { throw new Error(`iterations must be a positive integer, got: ${iterations}`); } forUpdates.iterations = iterations; hasForUpdates = true; updatedFields.push('iterations'); } if (args.condition !== undefined) { forUpdates.condition = normalizeConditionSyntax(args.condition as string); hasForUpdates = true; updatedFields.push('condition'); } if (args.errorHandling !== undefined) { forUpdates.errorHandling = args.errorHandling === 'break' ? 1 : 0; hasForUpdates = true; updatedFields.push('errorHandling'); } if (hasForUpdates) forCollection.utils.update(forUpdates); break; } case 'ForEach': { const feUpdates: Record = { nodeId: nodeIdBytes }; let hasFeUpdates = false; if (args.path !== undefined) { feUpdates.path = normalizeConditionSyntax(args.path as string); hasFeUpdates = true; updatedFields.push('path'); } if (args.condition !== undefined) { feUpdates.condition = normalizeConditionSyntax(args.condition as string); hasFeUpdates = true; updatedFields.push('condition'); } if (args.errorHandling !== undefined) { feUpdates.errorHandling = args.errorHandling === 'break' ? 1 : 0; hasFeUpdates = true; updatedFields.push('errorHandling'); } if (hasFeUpdates) forEachCollection.utils.update(feUpdates); break; } case 'GraphQL': { if (!node.graphqlId) throw new Error(`GraphQL node "${node.name}" has no associated GraphQL request`); const graphqlIdBytes = parseUlid(node.graphqlId); // Update url/query/variables const gqlUpdates: Record = { graphqlId: graphqlIdBytes }; let hasGqlUpdates = false; if (args.url !== undefined) { gqlUpdates.url = args.url; hasGqlUpdates = true; updatedFields.push('url'); } if (args.query !== undefined) { gqlUpdates.query = args.query; hasGqlUpdates = true; updatedFields.push('query'); } if (args.variables !== undefined) { gqlUpdates.variables = args.variables; hasGqlUpdates = true; updatedFields.push('variables'); } if (hasGqlUpdates) { collections.graphqlCollection.utils.update(gqlUpdates); } // Replace headers if provided if (args.headers !== undefined) { const existingHeaders = await queryCollection((_) => _.from({ h: collections.graphqlHeaderCollection }).where((_) => eq(_.h.graphqlId, graphqlIdBytes)), ); for (const h of existingHeaders) { if (h.graphqlHeaderId) collections.graphqlHeaderCollection.utils.delete({ graphqlHeaderId: h.graphqlHeaderId }); } const newHeaders = args.headers as { description?: string; enabled?: boolean; key: string; value?: string; }[]; for (let i = 0; i < newHeaders.length; i++) { const h = newHeaders[i]!; await collections.graphqlHeaderCollection.utils.insert({ description: h.description ?? '', enabled: h.enabled ?? true, graphqlHeaderId: Ulid.generate().bytes, graphqlId: graphqlIdBytes, key: h.key, order: i, value: h.value ?? '', }); } updatedFields.push('headers'); } // Replace assertions if provided if (args.assertions !== undefined) { const existingAsserts = await queryCollection((_) => _.from({ a: collections.graphqlAssertCollection }).where((_) => eq(_.a.graphqlId, graphqlIdBytes)), ); for (const a of existingAsserts) { if (a.graphqlAssertId) collections.graphqlAssertCollection.utils.delete({ graphqlAssertId: a.graphqlAssertId }); } const newAsserts = args.assertions as { enabled?: boolean; value: string }[]; for (let i = 0; i < newAsserts.length; i++) { const a = newAsserts[i]!; await collections.graphqlAssertCollection.utils.insert({ enabled: a.enabled ?? true, graphqlAssertId: Ulid.generate().bytes, graphqlId: graphqlIdBytes, order: i, value: normalizeConditionSyntax(a.value), }); } updatedFields.push('assertions'); } break; } case 'HTTP': { if (!node.httpId) throw new Error(`HTTP node "${node.name}" has no associated HTTP request`); const httpIdBytes = parseUlid(node.httpId); const METHODS_WITH_BODY = new Set(['PATCH', 'POST', 'PUT']); const HTTP_METHOD_NAMES_LOCAL: Record = { 0: 'UNSPECIFIED', 1: 'GET', 2: 'POST', 3: 'PUT', 4: 'PATCH', 5: 'DELETE', 6: 'HEAD', 7: 'OPTIONS', 8: 'CONNECT', }; const [httpData] = await queryCollection((_) => _.from({ http: httpCollection }) .where((_) => eq(_.http.httpId, httpIdBytes)) .findOne(), ); const clearHttpBody = async () => { httpCollection.utils.update({ bodyKind: HttpBodyKind.UNSPECIFIED, httpId: httpIdBytes }); const existingBody = await queryCollection((_) => _.from({ br: httpBodyRawCollection }).where((_) => eq(_.br.httpId, httpIdBytes)), ); if (existingBody.length > 0) { httpBodyRawCollection.utils.update({ data: '', httpId: httpIdBytes }); } }; // Update method/url const httpUpdates: Record = { httpId: httpIdBytes }; let hasHttpUpdates = false; const currentMethod = HTTP_METHOD_NAMES_LOCAL[httpData?.method ?? 0] ?? 'UNSPECIFIED'; let effectiveMethod = currentMethod; if (args.method !== undefined) { const methodStr = (args.method as string).toUpperCase(); const method = HTTP_METHOD_MAP[methodStr]; if (method === undefined) { throw new Error( `Invalid HTTP method: "${args.method}". Valid: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS`, ); } effectiveMethod = methodStr; httpUpdates.method = method; hasHttpUpdates = true; updatedFields.push('method'); } if (args.url !== undefined) { httpUpdates.url = args.url; hasHttpUpdates = true; updatedFields.push('url'); } if (hasHttpUpdates) { httpCollection.utils.update(httpUpdates); } // Replace headers if provided if (args.headers !== undefined) { const existingHeaders = await queryCollection((_) => _.from({ h: httpHeaderCollection }).where((_) => eq(_.h.httpId, httpIdBytes)), ); for (const h of existingHeaders) { if (h.httpHeaderId) httpHeaderCollection.utils.delete({ httpHeaderId: h.httpHeaderId }); } const newHeaders = args.headers as { description?: string; enabled?: boolean; key: string; value?: string; }[]; for (let i = 0; i < newHeaders.length; i++) { const h = newHeaders[i]!; await httpHeaderCollection.utils.insert({ description: h.description ?? '', enabled: h.enabled ?? true, httpHeaderId: Ulid.generate().bytes, httpId: httpIdBytes, key: h.key, order: i, value: h.value ?? '', }); } updatedFields.push('headers'); } // Replace search params if provided if (args.searchParams !== undefined) { const existingParams = await queryCollection((_) => _.from({ sp: httpSearchParamCollection }).where((_) => eq(_.sp.httpId, httpIdBytes)), ); for (const sp of existingParams) { if (sp.httpSearchParamId) httpSearchParamCollection.utils.delete({ httpSearchParamId: sp.httpSearchParamId }); } const newParams = args.searchParams as { description?: string; enabled?: boolean; key: string; value?: string; }[]; for (let i = 0; i < newParams.length; i++) { const sp = newParams[i]!; await httpSearchParamCollection.utils.insert({ description: sp.description ?? '', enabled: sp.enabled ?? true, httpId: httpIdBytes, httpSearchParamId: Ulid.generate().bytes, key: sp.key, order: i, value: sp.value ?? '', }); } updatedFields.push('searchParams'); } // Method-body guard: validate body is only set for methods that support it if (args.body !== undefined && args.body !== null) { if (!METHODS_WITH_BODY.has(effectiveMethod)) { throw new Error( `Cannot set body for ${effectiveMethod} requests. ` + 'Only POST, PUT, and PATCH methods support a request body. ' + 'Either change the method first or remove the body.', ); } } // If method is changed to a no-body method and body wasn't explicitly provided, // clear any existing body to keep method/body state consistent. if (args.method !== undefined && args.body === undefined && !METHODS_WITH_BODY.has(effectiveMethod)) { await clearHttpBody(); updatedFields.push('body'); } // Set or clear body if (args.body !== undefined) { const body = args.body as null | string; if (body === null) { await clearHttpBody(); } else { httpCollection.utils.update({ bodyKind: HttpBodyKind.RAW, httpId: httpIdBytes }); const existingBody = await queryCollection((_) => _.from({ br: httpBodyRawCollection }).where((_) => eq(_.br.httpId, httpIdBytes)), ); if (existingBody.length > 0) { httpBodyRawCollection.utils.update({ data: body, httpId: httpIdBytes }); } else { await httpBodyRawCollection.utils.insert({ data: body, httpId: httpIdBytes }); } } updatedFields.push('body'); } // Replace assertions if provided if (args.assertions !== undefined) { const existingAsserts = await queryCollection((_) => _.from({ a: httpAssertCollection }).where((_) => eq(_.a.httpId, httpIdBytes)), ); for (const a of existingAsserts) { if (a.httpAssertId) httpAssertCollection.utils.delete({ httpAssertId: a.httpAssertId }); } const newAsserts = args.assertions as { enabled?: boolean; value: string }[]; for (let i = 0; i < newAsserts.length; i++) { const a = newAsserts[i]!; await httpAssertCollection.utils.insert({ enabled: a.enabled ?? true, httpAssertId: Ulid.generate().bytes, httpId: httpIdBytes, order: i, value: normalizeConditionSyntax(a.value), }); } updatedFields.push('assertions'); } break; } case 'JavaScript': { if (args.code !== undefined) { jsCollection.utils.update({ code: `export default function(ctx) {\n ${normalizeJsCodeReferences(args.code as string)}\n}`, nodeId: nodeIdBytes, }); updatedFields.push('code'); } break; } } if (updatedFields.length === 0) { return { message: `No applicable fields provided for ${node.kind} node "${node.name}"`, success: false }; } return { success: true, updatedFields }; } case 'updateVariable': { const flowVariableId = parseUlid(args.flowVariableId as string); const updates: Record = { flowVariableId }; if (args.key !== undefined) updates.key = args.key; if (args.value !== undefined) updates.value = args.value; if (args.enabled !== undefined) updates.enabled = args.enabled; if (args.description !== undefined) updates.description = args.description; if (args.order !== undefined) updates.order = args.order; variableCollection.utils.update(updates); return { success: true }; } default: throw new Error(`Unknown tool: ${name}`); } }; export type { Collections, ToolExecutorContext }; ================================================ FILE: packages/client/src/features/agent/tool-schemas.ts ================================================ /** * Runtime tool schema utilities - converts Effect Schemas to JSON Schema tool definitions. * These utilities are used by the agent to handle AI tool calling. */ import { JSONSchema, Schema } from 'effect'; import { ExecutionSchemas } from '@the-dev-tools/spec/tools/execution'; import { MutationSchemas } from '@the-dev-tools/spec/tools/mutation'; // Re-export schemas for convenience export { ExecutionSchemas, MutationSchemas }; export * from '@the-dev-tools/spec-lib/common'; // ============================================================================= // Tool Definition Type // ============================================================================= export interface ToolDefinition { description: string; name: string; parameters: object; } // ============================================================================= // JSON Schema Generation // ============================================================================= /** Recursively resolve $ref references in a JSON Schema */ function resolveRefs(obj: unknown, defs: Record): unknown { if (obj === null || typeof obj !== 'object') return obj; if (Array.isArray(obj)) return obj.map((item) => resolveRefs(item, defs)); const record = obj as Record; if ('$ref' in record && typeof record['$ref'] === 'string') { const defName = record['$ref'].replace('#/$defs/', ''); const resolved = defs[defName]; if (resolved) { const { $ref: _, ...rest } = record; return { ...(resolveRefs(resolved, defs) as Record), ...rest }; } } if ('allOf' in record && Array.isArray(record['allOf']) && record['allOf'].length === 1) { const first = record['allOf'][0] as Record; if ('$ref' in first) { const { allOf: _, ...rest } = record; return { ...(resolveRefs(first, defs) as Record), ...rest }; } } const result: Record = {}; for (const [key, value] of Object.entries(record)) { if (key === '$defs' || key === '$schema') continue; result[key] = resolveRefs(value, defs); } return result; } /** Convert an Effect Schema to a tool definition with JSON Schema parameters */ function schemaToToolDefinition(schema: Schema.Schema): ToolDefinition { const jsonSchema = JSONSchema.make(schema) as { $defs: Record; $ref: string; $schema: string; }; const defs = jsonSchema.$defs; const defName = jsonSchema.$ref.replace('#/$defs/', ''); const def = defs[defName] as | undefined | { description?: string; properties: Record; required?: string[]; type: string; }; return { description: def?.description ?? '', name: defName || 'unknown', parameters: def ? { additionalProperties: false, properties: resolveRefs(def.properties, defs), required: def.required, type: def.type, } : jsonSchema, }; } // ============================================================================= // Auto-generated Tool Definitions // ============================================================================= export const executionSchemas = Object.values(ExecutionSchemas).map((s) => schemaToToolDefinition(s as Schema.Schema), ); export const mutationSchemas = Object.values(MutationSchemas).map((s) => schemaToToolDefinition(s as Schema.Schema), ); // Patch CreateHttpNode to include optional body field (executor already handles it) const createHttpNodeDef = mutationSchemas.find((t) => t.name === 'createHttpNode'); if (createHttpNodeDef) { const params = createHttpNodeDef.parameters as { properties: Record; required?: string[]; }; params.properties['body'] = { description: 'Optional JSON request body for POST, PUT, or PATCH requests. Only valid for methods that support a body. Supports {{variable}} interpolation.', type: 'string', }; // Remove additionalProperties:false so the extra field is accepted delete (params as Record)['additionalProperties']; if (params.properties['url']) { params.properties['url'] = { ...(params.properties['url'] as object), description: 'The URL for the HTTP request. Supports {{variable}} interpolation, e.g. {{BASE_URL}}/api/users', }; } } // Patch CreateGraphQLNode to add field descriptions const createGraphQLNodeDef = mutationSchemas.find((t) => t.name === 'createGraphQLNode'); if (createGraphQLNodeDef) { const params = createGraphQLNodeDef.parameters as { properties: Record; }; if (params.properties['url']) { params.properties['url'] = { ...(params.properties['url'] as object), description: 'The GraphQL API endpoint URL. Supports {{variable}} interpolation, e.g. {{BASE_URL}}/graphql', }; } if (params.properties['query']) { params.properties['query'] = { ...(params.properties['query'] as object), description: 'The GraphQL query or mutation string. Example: query { users { id name } }', }; } if (params.properties['variables']) { params.properties['variables'] = { ...(params.properties['variables'] as object), description: 'JSON string of GraphQL variables. Supports {{variable}} interpolation. Example: {"userId": "{{user_id}}"}', }; } } /** All tool schemas combined - ready for AI tool calling */ export const allToolSchemas = [...executionSchemas, ...mutationSchemas]; // ============================================================================= // Effect Schemas (for runtime validation) // ============================================================================= export const EffectSchemas = { Execution: ExecutionSchemas, Mutation: MutationSchemas, } as const; // ============================================================================= // Validation Helper // ============================================================================= const schemaMap: Record> = Object.fromEntries( Object.entries(EffectSchemas).flatMap(([, group]) => Object.entries(group).map(([name, schema]) => [ name.charAt(0).toLowerCase() + name.slice(1), schema as Schema.Schema, ]), ), ); /** * Validate tool input against the Effect Schema */ export function validateToolInput( toolName: string, input: unknown, ): { data: unknown; success: true } | { errors: string[]; success: false } { const schema = schemaMap[toolName]; if (!schema) { return { errors: [`Unknown tool: ${toolName}`], success: false }; } try { const decoded = Schema.decodeUnknownSync(schema)(input); return { data: decoded, success: true }; } catch (error) { if (error instanceof Error) { return { errors: [error.message], success: false }; } return { errors: ['Unknown validation error'], success: false }; } } ================================================ FILE: packages/client/src/features/agent/types.ts ================================================ import type { ChatCompletionMessageParam, ChatCompletionTool } from 'openai/resources/chat/completions'; export type MessageRole = 'assistant' | 'system' | 'tool' | 'user'; export interface Message { content: string; id: string; role: MessageRole; timestamp: number; toolCallId?: string; toolCalls?: ToolCall[]; } export interface ToolCall { arguments: Record; id: string; name: string; } export interface ToolResult { error?: string; isMutation?: boolean; result: unknown; toolCallId: string; } export interface AgentChatState { error: null | string; isLoading: boolean; messages: Message[]; streamingContent: string; } export interface FlowContextData { edges: EdgeInfo[]; executions: NodeExecutionInfo[]; flowId: string; nodes: NodeInfo[]; selectedNodeIds?: string[]; variables: VariableInfo[]; } export interface NodeInfo { graphqlId?: string; httpId?: string; httpMethod?: string; id: string; info?: string; kind: string; name: string; position: { x: number; y: number }; state: string; } export interface NodeExecutionInfo { completedAt?: string; error?: string; id: string; input?: unknown; name: string; nodeId: string; output?: unknown; state: string; } export interface EdgeInfo { id: string; sourceHandle?: string; sourceId: string; targetId: string; } export interface VariableInfo { enabled: boolean; id: string; key: string; value: string; } export interface ToolSchema { description: string; name: string; parameters: { additionalProperties?: boolean; properties: Record; required?: string[]; type: 'object'; }; } export const formatToolAsOpenAI = (schema: ToolSchema): ChatCompletionTool => ({ function: { description: schema.description, name: schema.name, parameters: schema.parameters, }, type: 'function', }); export type OpenAIMessage = ChatCompletionMessageParam; ================================================ FILE: packages/client/src/features/agent/use-agent-chat.ts ================================================ /* eslint-disable @typescript-eslint/no-unnecessary-condition */ import { eq } from '@tanstack/react-db'; import { Ulid } from 'id128'; import OpenAI from 'openai'; import { useCallback, useRef, useSyncExternalStore } from 'react'; import { NodeKind } from '@the-dev-tools/spec/buf/api/flow/v1/flow_pb'; import { FileCollectionSchema } from '@the-dev-tools/spec/tanstack-db/v1/api/file_system'; import { EdgeCollectionSchema, FlowCollectionSchema, FlowVariableCollectionSchema, NodeAiCollectionSchema, NodeCollectionSchema, NodeConditionCollectionSchema, NodeExecutionCollectionSchema, NodeForCollectionSchema, NodeForEachCollectionSchema, NodeGraphQLCollectionSchema, NodeHttpCollectionSchema, NodeJsCollectionSchema, } from '@the-dev-tools/spec/tanstack-db/v1/api/flow'; import { GraphQLAssertCollectionSchema, GraphQLCollectionSchema, GraphQLHeaderCollectionSchema, } from '@the-dev-tools/spec/tanstack-db/v1/api/graph_q_l'; import { HttpAssertCollectionSchema, HttpBodyRawCollectionSchema, HttpCollectionSchema, HttpHeaderCollectionSchema, HttpSearchParamCollectionSchema, } from '@the-dev-tools/spec/tanstack-db/v1/api/http'; import { useApiCollection } from '~/shared/api'; import { queryCollection } from '~/shared/lib'; import { routes } from '~/shared/routes'; import type { AgentProvider } from './use-agent-provider-key'; import { AgentLogger } from './agent-logger'; import { buildCompactStateSummary, buildSystemPrompt, buildXmlValidationMessage, detectDeadEndNodes, detectOrphanNodes, refreshFlowContext, useFlowContext, } from './context-builder'; import { defaultHorizontalConfig, layoutNodes } from './layout'; import { type Collections, executeToolCall, type ToolExecutorContext } from './tool-executor'; import { allToolSchemas } from './tool-schemas'; import { type AgentChatState, formatToolAsOpenAI, type Message, type OpenAIMessage, type ToolCall, type ToolResult, type ToolSchema, } from './types'; const DEFAULT_MODELS: Record = { anthropic: 'claude-sonnet-4-6', openai: 'gpt-5', openrouter: 'minimax/minimax-m2.5', }; const createProviderClient = ( provider: AgentProvider, apiKey: string, ): { client: OpenAI; model: string; providerName: AgentProvider } => { if (provider === 'openrouter') { return { client: new OpenAI({ apiKey, baseURL: 'https://openrouter.ai/api/v1', dangerouslyAllowBrowser: true, }), model: DEFAULT_MODELS.openrouter, providerName: provider, }; } if (provider === 'anthropic') { return { client: new OpenAI({ apiKey, baseURL: 'https://api.anthropic.com/v1', dangerouslyAllowBrowser: true, defaultHeaders: { 'anthropic-dangerous-direct-browser-access': 'true', 'anthropic-version': '2023-06-01', }, }), model: DEFAULT_MODELS.anthropic, providerName: provider, }; } return { client: new OpenAI({ apiKey, dangerouslyAllowBrowser: true, }), model: DEFAULT_MODELS.openai, providerName: provider, }; }; const generateId = () => crypto.randomUUID(); /** JSON stringify with BigInt support */ const safeStringify = (value: unknown): string => JSON.stringify(value, (_key: string, v: unknown) => (typeof v === 'bigint' ? v.toString() : v)); // --------------------------------------------------------------------------- // Streaming helpers // --------------------------------------------------------------------------- interface StreamedMessage { content: null | string; tool_calls?: { function: { arguments: string; name: string }; id: string; type: 'function'; }[]; } interface StreamMeta { finishReason: null | string | undefined; usage: unknown; } /** * Consumes an OpenAI streaming response, accumulating content and tool calls. * Calls `onContent` with the accumulated text after every content delta so the * UI can render tokens in real-time. */ const consumeStream = async ( stream: AsyncIterable, onContent: (accumulated: string) => void, ): Promise<{ message: StreamedMessage; meta: StreamMeta }> => { let content = ''; let hasContent = false; const toolCallsMap = new Map(); let finishReason: null | string | undefined = null; let usage: unknown = undefined; for await (const chunk of stream) { const choice = chunk.choices[0]; if (!choice) { // Final chunk may carry only usage data if (chunk.usage) usage = chunk.usage; continue; } if (choice.finish_reason) finishReason = choice.finish_reason; if (chunk.usage) usage = chunk.usage; const delta = choice.delta; if (delta?.content) { content += delta.content; hasContent = true; onContent(content); } if (delta?.tool_calls) { for (const tc of delta.tool_calls) { const existing = toolCallsMap.get(tc.index); if (existing) { if (tc.function?.name) existing.name += tc.function.name; if (tc.function?.arguments) existing.arguments += tc.function.arguments; } else { toolCallsMap.set(tc.index, { arguments: tc.function?.arguments ?? '', id: tc.id ?? '', name: tc.function?.name ?? '', }); } } } } const toolCalls = toolCallsMap.size > 0 ? Array.from(toolCallsMap.entries()) .sort(([a], [b]) => a - b) .map(([, tc]) => ({ function: { arguments: tc.arguments, name: tc.name }, id: tc.id, type: 'function' as const, })) : undefined; return { message: { content: hasContent ? content : null, tool_calls: toolCalls, }, meta: { finishReason, usage }, }; }; type NodeCollection = ReturnType>; type EdgeCollection = ReturnType>; const NODE_KIND_NAMES: Record = { [NodeKind.AI]: 'Ai', [NodeKind.CONDITION]: 'Condition', [NodeKind.FOR]: 'For', [NodeKind.FOR_EACH]: 'ForEach', [NodeKind.GRAPH_Q_L]: 'GraphQL', [NodeKind.HTTP]: 'HTTP', [NodeKind.JS]: 'JavaScript', [NodeKind.MANUAL_START]: 'ManualStart', [NodeKind.UNSPECIFIED]: 'Unknown', }; /** * Query fresh nodes and edges directly from collections, then apply layout. * This avoids stale context issues when mutations haven't propagated to React state yet. */ const applyLayoutToFlow = async ( flowId: Uint8Array, nodeCollection: NodeCollection, edgeCollection: EdgeCollection, ): Promise => { // Query fresh nodes directly from the collection const freshNodes = await queryCollection((_) => _.from({ node: nodeCollection }).where((_) => eq(_.node.flowId, flowId)), ); // Query fresh edges directly from the collection const freshEdges = await queryCollection((_) => _.from({ edge: edgeCollection }).where((_) => eq(_.edge.flowId, flowId)), ); // Build node info for layout const nodes = freshNodes .filter((n) => n.nodeId != null) .map((n) => ({ id: Ulid.construct(n.nodeId).toCanonical(), kind: NODE_KIND_NAMES[n.kind] ?? 'Unknown', name: n.name, position: { x: n.position?.x ?? 0, y: n.position?.y ?? 0 }, state: 'Idle', })); // Build a set of valid node IDs for filtering const validNodeIds = new Set(nodes.map((n) => n.id)); // Build edge info for layout - only include edges where both source and target exist const edges = freshEdges .filter((e) => e.edgeId != null && e.sourceId != null && e.targetId != null) .map((e) => ({ id: Ulid.construct(e.edgeId).toCanonical(), sourceHandle: e.sourceHandle !== undefined ? String(e.sourceHandle) : undefined, sourceId: Ulid.construct(e.sourceId).toCanonical(), targetId: Ulid.construct(e.targetId).toCanonical(), })) .filter((e) => validNodeIds.has(e.sourceId) && validNodeIds.has(e.targetId)); const result = layoutNodes(nodes, edges, defaultHorizontalConfig()); if (!result) return; for (const [nodeId, position] of result.positions) { nodeCollection.utils.update({ nodeId: Ulid.fromCanonical(nodeId).bytes, position: { x: position.x, y: position.y }, }); } }; const clientToolSchemas: ToolSchema[] = [ { description: "Inspect a node's full config and execution state. Returns type-specific config (HTTP: url/method/headers/params/body/assertions, GraphQL: url/query/variables/headers/assertions, JS: code, Condition: expression, For: iterations/condition, ForEach: path/condition) plus execution state/error. " + 'Set includeOutput: true to also get execution input/output payloads (can be large).', name: 'inspectNode', parameters: { additionalProperties: false, properties: { includeOutput: { description: 'Include execution input/output payloads (default: false). Only use when you need to see actual request/response data.', type: 'boolean', }, nodeId: { description: 'The node ID to inspect', type: 'string' }, }, required: ['nodeId'], type: 'object', }, }, { description: 'Get a summary of the latest flow execution showing which nodes ran and which were never reached.', name: 'getFlowExecutionSummary', parameters: { additionalProperties: false, properties: {}, required: [], type: 'object', }, }, { description: "Update any node's configuration in a single call. Provide nodeId and only the fields to change — unspecified fields stay unchanged. " + 'Base fields (name) work on any node. Type-specific fields: ' + 'Ai: prompt, maxIterations. Condition: condition. For: iterations, condition (break), errorHandling. ' + 'ForEach: path, condition (break), errorHandling. JS: code. ' + 'HTTP: method, url, headers, searchParams, body, assertions (arrays replace existing set). ' + 'GraphQL: url, query, variables, headers, assertions (arrays replace existing set).', name: 'updateNode', parameters: { additionalProperties: false, properties: { assertions: { description: 'Replaces all existing assertions (HTTP and GraphQL)', items: { properties: { enabled: { type: 'boolean' }, value: { description: 'Expr-lang boolean expression evaluated against the HTTP/GraphQL response. Must be a complete expression, not a bare identifier. Available: response.status (int), response.body (parsed JSON), response.headers (map), response.duration. For GraphQL: data.* and errors.* also available. Examples: response.status == 200, response.body != nil, data.users[0].id != nil', type: 'string', }, }, required: ['value'], type: 'object', }, type: 'array', }, body: { description: 'Raw body content (JSON string). Set to null to clear. (HTTP only) Supports {{variable}} interpolation.', type: ['string', 'null'], }, code: { description: 'JavaScript code (JS nodes only)', type: 'string', }, condition: { description: 'For Condition nodes: branching expression. For For/ForEach: break condition (expr-lang syntax).', type: 'string', }, errorHandling: { description: 'Error handling strategy (For/ForEach only)', enum: ['ignore', 'break'], type: 'string', }, headers: { description: 'Replaces all existing headers (HTTP and GraphQL)', items: { properties: { enabled: { type: 'boolean' }, key: { type: 'string' }, value: { description: 'Supports {{variable}} interpolation, e.g. Bearer {{Auth.response.body.token}}', type: 'string', }, }, required: ['key'], type: 'object', }, type: 'array', }, iterations: { description: 'Number of loop iterations, must be positive (For nodes only)', type: 'integer', }, maxIterations: { description: 'Maximum number of agentic iterations, must be positive (Ai nodes only)', type: 'integer', }, method: { description: 'HTTP method (HTTP nodes only)', enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'], type: 'string', }, name: { description: 'New node name (any node type)', type: 'string', }, nodeId: { description: 'The node ID to update', type: 'string' }, path: { description: 'Collection expression to iterate (ForEach nodes only, expr-lang syntax)', type: 'string', }, prompt: { description: 'The prompt or system instructions for the AI agent (Ai nodes only)', type: 'string', }, query: { description: 'GraphQL query or mutation string (GraphQL nodes only)', type: 'string', }, searchParams: { description: 'Replaces all existing query parameters (HTTP only)', items: { properties: { enabled: { type: 'boolean' }, key: { type: 'string' }, value: { description: 'Supports {{variable}} interpolation.', type: 'string' }, }, required: ['key'], type: 'object', }, type: 'array', }, url: { description: 'Request URL (HTTP and GraphQL nodes). Supports {{variable}} interpolation, e.g. {{BASE_URL}}/api/users/{{id}}', type: 'string', }, variables: { description: 'JSON string of GraphQL variables (GraphQL nodes only). Supports {{variable}} interpolation.', type: 'string', }, }, required: ['nodeId'], type: 'object', }, }, { description: 'Incrementally add or remove headers, query params, or assertions on an HTTP node without replacing the full set. ' + 'Use this when modifying individual items. For full replacement, use updateNode instead.', name: 'patchHttpNode', parameters: { additionalProperties: false, properties: { addAssertions: { description: 'Assertions to append', items: { properties: { enabled: { type: 'boolean' }, value: { description: 'Expr-lang boolean expression evaluated against the HTTP response. Must be a complete expression, not a bare identifier. Available: response.status (int), response.body (parsed JSON), response.headers (map), response.duration. Examples: response.status == 200, response.body != nil, response.body.id != nil, len(response.body) > 0, response.headers["Content-Type"] contains "json"', type: 'string', }, }, required: ['value'], type: 'object', }, type: 'array', }, addHeaders: { description: 'Headers to append. Supports {{variable}} interpolation in values.', items: { properties: { description: { type: 'string' }, enabled: { type: 'boolean' }, key: { type: 'string' }, value: { description: 'Supports {{variable}} interpolation', type: 'string' }, }, required: ['key'], type: 'object', }, type: 'array', }, addSearchParams: { description: 'Query params to append. Supports {{variable}} interpolation in values.', items: { properties: { description: { type: 'string' }, enabled: { type: 'boolean' }, key: { type: 'string' }, value: { description: 'Supports {{variable}} interpolation', type: 'string' }, }, required: ['key'], type: 'object', }, type: 'array', }, nodeId: { description: 'The HTTP node ID to patch', type: 'string' }, removeAssertionIds: { description: 'IDs of assertions to remove (get IDs from inspectNode)', items: { type: 'string' }, type: 'array', }, removeHeaderIds: { description: 'IDs of headers to remove (get IDs from inspectNode)', items: { type: 'string' }, type: 'array', }, removeSearchParamIds: { description: 'IDs of query params to remove (get IDs from inspectNode)', items: { type: 'string' }, type: 'array', }, }, required: ['nodeId'], type: 'object', }, }, { description: 'Incrementally add or remove headers or assertions on a GraphQL node without replacing the full set. ' + 'Use this when modifying individual items. For full replacement, use updateNode instead.', name: 'patchGraphqlNode', parameters: { additionalProperties: false, properties: { addAssertions: { description: 'Assertions to append', items: { properties: { enabled: { type: 'boolean' }, value: { description: 'Expr-lang boolean expression evaluated against the GraphQL response. Available: response.status, response.body, data.*, errors.*. Examples: response.status == 200, data.users != nil, len(data.users) > 0', type: 'string', }, }, required: ['value'], type: 'object', }, type: 'array', }, addHeaders: { description: 'Headers to append. Supports {{variable}} interpolation in values.', items: { properties: { description: { type: 'string' }, enabled: { type: 'boolean' }, key: { type: 'string' }, value: { description: 'Supports {{variable}} interpolation', type: 'string' }, }, required: ['key'], type: 'object', }, type: 'array', }, nodeId: { description: 'The GraphQL node ID to patch', type: 'string' }, removeAssertionIds: { description: 'IDs of assertions to remove (get IDs from inspectNode)', items: { type: 'string' }, type: 'array', }, removeHeaderIds: { description: 'IDs of headers to remove (get IDs from inspectNode)', items: { type: 'string' }, type: 'array', }, }, required: ['nodeId'], type: 'object', }, }, { description: 'PREFERRED tool for ALL node connections. Connects nodes into a chain with optional parallel fan-out. ' + 'Flat array: sequential chain. Nested array: parallel branches. ' + 'Example: ["Start",["A","B"],"End"] creates Start→A, Start→B, A→End, B→End. ' + 'Works for ALL node types. For branching nodes (Condition, For, ForEach, Ai), auto-applies "then" handle by default. ' + 'Use sourceHandle "else" or "loop" to override for non-default branches. ' + 'Use sourceHandle "ai_tools" to connect tool nodes to an Ai node.', name: 'connectChain', parameters: { additionalProperties: false, properties: { nodeIds: { description: 'Ordered list of node IDs. Use nested arrays for fan-out/fan-in: ' + '["A","B","C"] chains A→B→C. ' + '["A",["B","C"],"D"] fans out A→B, A→C then fans in B→D, C→D. ' + 'Minimum 2 elements. No consecutive nested arrays.', items: { oneOf: [{ type: 'string' }, { items: { type: 'string' }, type: 'array' }] }, type: 'array', }, sourceHandle: { description: 'Handle for branching source nodes. Defaults to "then". ' + 'Use "else" for Condition false-branch, "loop" for For/ForEach loop-body, ' + '"ai_tools" for connecting tool nodes to an Ai node.', enum: ['then', 'else', 'loop', 'ai_tools'], type: 'string', }, }, required: ['nodeIds'], type: 'object', }, }, ]; interface UseAgentChatOptions { apiKey: string; flowId: Uint8Array; provider: AgentProvider; selectedNodeIds?: string[]; } const createInitialAgentChatState = (): AgentChatState => ({ error: null, isLoading: false, messages: [], streamingContent: '', }); // --------------------------------------------------------------------------- // Module-level external store – survives React component remounts // --------------------------------------------------------------------------- interface ChatStoreEntry { abortController: AbortController | null; state: AgentChatState; } const chatStoreEntries = new Map(); const chatStoreListeners = new Map void>>(); const chatStore = { getAbortController(key: string): AbortController | null { return chatStoreEntries.get(key)?.abortController ?? null; }, getState(key: string): AgentChatState { let entry = chatStoreEntries.get(key); if (!entry) { entry = { abortController: null, state: createInitialAgentChatState() }; chatStoreEntries.set(key, entry); } return entry.state; }, notify(key: string) { chatStoreListeners.get(key)?.forEach((cb) => { cb(); }); }, setAbortController(key: string, ac: AbortController | null) { let entry = chatStoreEntries.get(key); if (!entry) { entry = { abortController: null, state: createInitialAgentChatState() }; chatStoreEntries.set(key, entry); } entry.abortController = ac; }, setState(key: string, updater: ((prev: AgentChatState) => AgentChatState) | AgentChatState) { let entry = chatStoreEntries.get(key); if (!entry) { entry = { abortController: null, state: createInitialAgentChatState() }; chatStoreEntries.set(key, entry); } entry.state = typeof updater === 'function' ? updater(entry.state) : updater; chatStore.notify(key); }, subscribe(key: string, callback: () => void): () => void { let listeners = chatStoreListeners.get(key); if (!listeners) { listeners = new Set(); chatStoreListeners.set(key, listeners); } listeners.add(callback); return () => { listeners.delete(callback); if (listeners.size === 0) chatStoreListeners.delete(key); }; }, }; export const useAgentChat = ({ apiKey, flowId, provider, selectedNodeIds }: UseAgentChatOptions) => { const flowIdKey = Ulid.construct(flowId).toCanonical(); const state = useSyncExternalStore( useCallback((cb: () => void) => chatStore.subscribe(flowIdKey, cb), [flowIdKey]), useCallback(() => chatStore.getState(flowIdKey), [flowIdKey]), ); const { transport } = routes.root.useRouteContext(); const { workspaceId } = routes.dashboard.workspace.route.useLoaderData(); const flowContext = useFlowContext(flowId); // Use refs to always access latest values in callbacks const flowContextRef = useRef(flowContext); flowContextRef.current = flowContext; const selectedNodeIdsRef = useRef(selectedNodeIds); selectedNodeIdsRef.current = selectedNodeIds; const messagesRef = useRef(state.messages); messagesRef.current = state.messages; const nodeCollection = useApiCollection(NodeCollectionSchema); const edgeCollection = useApiCollection(EdgeCollectionSchema); const variableCollection = useApiCollection(FlowVariableCollectionSchema); const aiCollection = useApiCollection(NodeAiCollectionSchema); const jsCollection = useApiCollection(NodeJsCollectionSchema); const conditionCollection = useApiCollection(NodeConditionCollectionSchema); const forCollection = useApiCollection(NodeForCollectionSchema); const forEachCollection = useApiCollection(NodeForEachCollectionSchema); const nodeHttpCollection = useApiCollection(NodeHttpCollectionSchema); const httpCollection = useApiCollection(HttpCollectionSchema); const httpSearchParamCollection = useApiCollection(HttpSearchParamCollectionSchema); const httpHeaderCollection = useApiCollection(HttpHeaderCollectionSchema); const httpBodyRawCollection = useApiCollection(HttpBodyRawCollectionSchema); const httpAssertCollection = useApiCollection(HttpAssertCollectionSchema); const nodeGraphqlCollection = useApiCollection(NodeGraphQLCollectionSchema); const graphqlCollection = useApiCollection(GraphQLCollectionSchema); const graphqlHeaderCollection = useApiCollection(GraphQLHeaderCollectionSchema); const graphqlAssertCollection = useApiCollection(GraphQLAssertCollectionSchema); const executionCollection = useApiCollection(NodeExecutionCollectionSchema); const fileCollection = useApiCollection(FileCollectionSchema); const flowCollection = useApiCollection(FlowCollectionSchema); const sendMessage = useCallback( async (content: string) => { // Cancel any existing request chatStore.getAbortController(flowIdKey)?.abort(); const abortController = new AbortController(); chatStore.setAbortController(flowIdKey, abortController); const { client: openai, model, providerName } = createProviderClient(provider, apiKey); // Use ref to get latest flowContext at execution time const currentFlowContext = { ...flowContextRef.current, selectedNodeIds: selectedNodeIdsRef.current, }; // Build context fresh at execution time to avoid stale closures const collections: Collections = { aiCollection, conditionCollection, edgeCollection, executionCollection, fileCollection, forCollection, forEachCollection, graphqlAssertCollection, graphqlCollection, graphqlHeaderCollection, httpAssertCollection, httpBodyRawCollection, httpCollection, httpHeaderCollection, httpSearchParamCollection, jsCollection, nodeCollection, nodeGraphqlCollection, nodeHttpCollection, variableCollection, }; const waitForFlowCompletion = async (): Promise => { const POLL_INTERVAL = 500; const MAX_WAIT = 30_000; const INITIAL_DELAY = 500; let elapsed = 0; await new Promise((r) => setTimeout(r, INITIAL_DELAY)); elapsed += INITIAL_DELAY; while (elapsed < MAX_WAIT) { await new Promise((r) => setTimeout(r, POLL_INTERVAL)); elapsed += POLL_INTERVAL; const [flow] = await queryCollection((_) => _.from({ item: flowCollection }) .where((_) => eq(_.item.flowId, flowId)) .findOne(), ); if (flow && !flow.running) break; } }; const toolContext: ToolExecutorContext = { collections, flowContext: currentFlowContext, sessionCreatedNodeIds: new Set(), transport, waitForFlowCompletion, workspaceId, }; const userMessage: Message = { content, id: generateId(), role: 'user', timestamp: Date.now(), }; const logger = new AgentLogger(currentFlowContext.flowId); logger.logSessionStart(currentFlowContext.flowId, content); chatStore.setState(flowIdKey, (prev) => ({ ...prev, error: null, isLoading: true, messages: [...prev.messages, userMessage], })); try { const systemPrompt = buildSystemPrompt(currentFlowContext); const tools = [...allToolSchemas, ...clientToolSchemas].map(formatToolAsOpenAI); logger.logSystemPrompt(systemPrompt, { edges: currentFlowContext.edges.length, nodes: currentFlowContext.nodes.length, variables: currentFlowContext.variables.length, }); logger.logUserMessage(content); const openAIMessages: OpenAIMessage[] = [ { content: systemPrompt, role: 'system' }, ...messagesRef.current.map(messageToOpenAI), { content, role: 'user' }, ]; logger.logApiRequest(`${providerName}:${model}`, openAIMessages.length, true); let apiStart = performance.now(); const updateStreamingContent = (content: string) => { chatStore.setState(flowIdKey, (prev) => ({ ...prev, streamingContent: content })); }; let stream = await openai.chat.completions.create( { messages: openAIMessages, model, stream: true, tool_choice: 'auto', tools, }, { signal: abortController.signal }, ); let { message: streamedMsg, meta } = await consumeStream(stream, updateStreamingContent); chatStore.setState(flowIdKey, (prev) => ({ ...prev, streamingContent: '' })); logger.logApiResponse(performance.now() - apiStart, meta.finishReason, meta.usage); let assistantMessage = streamedMsg; let validationRetries = 0; const MAX_VALIDATION_RETRIES = 2; for (;;) { // === Existing tool call loop === while (assistantMessage?.tool_calls && assistantMessage.tool_calls.length > 0) { const toolCalls: ToolCall[] = assistantMessage.tool_calls.map((tc) => ({ arguments: JSON.parse(tc.function.arguments) as Record, id: tc.id, name: tc.function.name, })); const toolMessage: Message = { content: assistantMessage.content ?? '', id: generateId(), role: 'assistant', timestamp: Date.now(), toolCalls, }; chatStore.setState(flowIdKey, (prev) => ({ ...prev, messages: [...prev.messages, toolMessage], })); for (const tc of toolCalls) { logger.logToolCallStart(tc.id, tc.name, tc.arguments); } const toolCallTimers: number[] = []; const toolResults: ToolResult[] = []; for (const tc of toolCalls) { toolCallTimers.push(performance.now()); toolResults.push(await executeToolCall(tc, flowId, toolContext)); } for (let i = 0; i < toolResults.length; i++) { const tr = toolResults[i]!; const tc = toolCalls[i]!; const elapsed = performance.now() - toolCallTimers[i]!; logger.logToolCallEnd(tc.id, tc.name, elapsed, tr.error ?? safeStringify(tr.result), tr.error); } // Apply layout and refresh context after mutations const hadMutations = toolResults.some((tr: ToolResult) => tr.isMutation && !tr.error); if (hadMutations) { // Query fresh data directly from collections to avoid stale React context await applyLayoutToFlow(flowId, nodeCollection, edgeCollection); // Refresh flow context so subsequent tool calls see newly created nodes toolContext.flowContext = { ...(await refreshFlowContext(flowId, { edgeCollection, executionCollection, httpCollection, nodeCollection, nodeGraphqlCollection, nodeHttpCollection, variableCollection, })), selectedNodeIds: selectedNodeIdsRef.current, }; // Inject updated flow state so LLM sees current topology const stateSummary = buildCompactStateSummary(toolContext.flowContext); openAIMessages.push({ content: stateSummary, role: 'system' }); } const toolResultMessages: Message[] = toolResults.map((tr) => ({ content: tr.error ?? safeStringify(tr.result), id: generateId(), role: 'tool' as const, timestamp: Date.now(), toolCallId: tr.toolCallId, })); chatStore.setState(flowIdKey, (prev) => ({ ...prev, messages: [...prev.messages, ...toolResultMessages], })); openAIMessages.push({ content: assistantMessage.content, role: 'assistant', tool_calls: assistantMessage.tool_calls, }); // Collapse identical error messages to reduce noise const errorGroups = new Map(); for (const tr of toolResults) { if (tr.error) { const existing = errorGroups.get(tr.error); if (existing) { existing.count++; } else { errorGroups.set(tr.error, { count: 1, firstId: tr.toolCallId }); } } } for (const tr of toolResults) { const errorGroup = tr.error ? errorGroups.get(tr.error) : undefined; let content: string; if (tr.error && errorGroup && errorGroup.count > 1) { if (tr.toolCallId === errorGroup.firstId) { content = `${tr.error} (this error occurred ${errorGroup.count} times in this batch)`; } else { content = `Same error as ${errorGroup.firstId}`; } } else { content = tr.error ?? safeStringify(tr.result); } openAIMessages.push({ content, role: 'tool', tool_call_id: tr.toolCallId, }); } logger.logApiRequest(`${providerName}:${model}`, openAIMessages.length, true); apiStart = performance.now(); stream = await openai.chat.completions.create( { messages: openAIMessages, model, stream: true, tool_choice: 'auto', tools, }, { signal: abortController.signal }, ); ({ message: streamedMsg, meta } = await consumeStream(stream, updateStreamingContent)); chatStore.setState(flowIdKey, (prev) => ({ ...prev, streamingContent: '' })); logger.logApiResponse(performance.now() - apiStart, meta.finishReason, meta.usage); assistantMessage = streamedMsg; } // === Post-execution validation: check for orphan nodes === if (validationRetries >= MAX_VALIDATION_RETRIES) break; const freshNodes = await queryCollection((_) => _.from({ node: nodeCollection }).where((_) => eq(_.node.flowId, flowId)), ); const freshEdges = await queryCollection((_) => _.from({ edge: edgeCollection }).where((_) => eq(_.edge.flowId, flowId)), ); const nodeInfos = freshNodes .filter((n) => n.nodeId != null) .map((n) => ({ id: Ulid.construct(n.nodeId).toCanonical(), kind: NODE_KIND_NAMES[n.kind] ?? 'Unknown', name: n.name, })); const edgeInfos = freshEdges .filter((e) => e.edgeId != null) .map((e) => ({ sourceId: Ulid.construct(e.sourceId).toCanonical(), targetId: Ulid.construct(e.targetId).toCanonical(), })); const orphans = detectOrphanNodes(nodeInfos, edgeInfos); const deadEnds = orphans.length === 0 ? detectDeadEndNodes(nodeInfos, edgeInfos) : []; logger.logValidation( orphans.length, orphans.map((n) => n.name), ); if (orphans.length === 0 && deadEnds.length === 0) break; validationRetries++; const validationContent = buildXmlValidationMessage(orphans, deadEnds); // Add the assistant's text response to messages before injecting validation if (assistantMessage?.content) { openAIMessages.push({ content: assistantMessage.content, role: 'assistant', }); } openAIMessages.push({ content: validationContent, role: 'user', }); logger.logApiRequest(`${providerName}:${model}`, openAIMessages.length, true); apiStart = performance.now(); stream = await openai.chat.completions.create( { messages: openAIMessages, model, stream: true, tool_choice: 'auto', tools }, { signal: abortController.signal }, ); ({ message: streamedMsg, meta } = await consumeStream(stream, updateStreamingContent)); chatStore.setState(flowIdKey, (prev) => ({ ...prev, streamingContent: '' })); logger.logApiResponse(performance.now() - apiStart, meta.finishReason, meta.usage); assistantMessage = streamedMsg; } const finalMessage: Message = { content: assistantMessage?.content ?? '', id: generateId(), role: 'assistant', timestamp: Date.now(), }; logger.logAssistantMessage(finalMessage.content); logger.logSessionEnd(true, false); chatStore.setState(flowIdKey, (prev) => ({ ...prev, isLoading: false, messages: [...prev.messages, finalMessage], })); } catch (error) { // Ignore abort errors if (error instanceof Error && error.name === 'AbortError') { logger.logSessionEnd(false, true); chatStore.setState(flowIdKey, (prev) => ({ ...prev, isLoading: false, streamingContent: '' })); return; } // Anthropic browser calls can be blocked by org CORS settings. const isNetworkFailure = error instanceof Error && /failed to fetch|networkerror/i.test(error.message); if (provider === 'anthropic' && isNetworkFailure) { const corsHelp = 'Anthropic blocked the browser request (CORS). Make sure your Anthropic org allows browser CORS requests, or use OpenRouter for browser-based chat.'; logger.logError(error, 'sendMessage'); logger.logSessionEnd(false, false); chatStore.setState(flowIdKey, (prev) => ({ ...prev, error: corsHelp, isLoading: false, streamingContent: '', })); return; } logger.logError(error, 'sendMessage'); logger.logSessionEnd(false, false); const errorMessage = error instanceof Error ? error.message : 'An error occurred'; chatStore.setState(flowIdKey, (prev) => ({ ...prev, error: errorMessage, isLoading: false, streamingContent: '', })); } finally { if (chatStore.getAbortController(flowIdKey) === abortController) { chatStore.setAbortController(flowIdKey, null); } } }, [ apiKey, provider, flowId, transport, nodeCollection, edgeCollection, variableCollection, aiCollection, jsCollection, conditionCollection, forCollection, forEachCollection, nodeHttpCollection, nodeGraphqlCollection, httpCollection, httpSearchParamCollection, httpHeaderCollection, httpBodyRawCollection, httpAssertCollection, graphqlCollection, graphqlHeaderCollection, graphqlAssertCollection, executionCollection, fileCollection, flowCollection, workspaceId, ], ); const clearMessages = useCallback(() => { chatStore.getAbortController(flowIdKey)?.abort(); chatStore.setAbortController(flowIdKey, null); chatStore.setState(flowIdKey, { error: null, isLoading: false, messages: [], streamingContent: '', }); }, [flowIdKey]); const cancel = useCallback(() => { chatStore.getAbortController(flowIdKey)?.abort(); chatStore.setAbortController(flowIdKey, null); chatStore.setState(flowIdKey, (prev) => ({ ...prev, isLoading: false, streamingContent: '' })); }, [flowIdKey]); return { cancel, clearMessages, error: state.error, isLoading: state.isLoading, messages: state.messages, sendMessage, streamingContent: state.streamingContent, }; }; const messageToOpenAI = (message: Message): OpenAIMessage => { if (message.role === 'tool' && message.toolCallId) { return { content: message.content, role: 'tool', tool_call_id: message.toolCallId, }; } if (message.role === 'assistant' && message.toolCalls) { return { content: message.content, role: 'assistant', tool_calls: message.toolCalls.map((tc) => ({ function: { arguments: JSON.stringify(tc.arguments), name: tc.name, }, id: tc.id, type: 'function' as const, })), }; } return { content: message.content, role: message.role as 'assistant' | 'system' | 'user', }; }; ================================================ FILE: packages/client/src/features/agent/use-agent-provider-key.ts ================================================ import { useCallback, useSyncExternalStore } from 'react'; export type AgentProvider = 'anthropic' | 'openai' | 'openrouter'; const PROVIDER_STORAGE_KEY = 'agent-provider'; const API_KEY_STORAGE_KEYS: Record = { anthropic: 'agent-api-key-anthropic', openai: 'agent-api-key-openai', openrouter: 'agent-api-key-openrouter', }; const DEFAULT_PROVIDER: AgentProvider = 'openrouter'; const listeners = new Set<() => void>(); const subscribe = (cb: () => void) => { listeners.add(cb); return () => void listeners.delete(cb); }; const normalizeProvider = (value: null | string): AgentProvider => { if (value === 'anthropic' || value === 'openai' || value === 'openrouter') { return value; } return DEFAULT_PROVIDER; }; const getProviderSnapshot = () => normalizeProvider(localStorage.getItem(PROVIDER_STORAGE_KEY)); const getApiKeySnapshot = (provider: AgentProvider) => localStorage.getItem(API_KEY_STORAGE_KEYS[provider]) ?? ''; const notify = () => { for (const cb of listeners) cb(); }; export const useAgentProviderKey = () => { const provider = useSyncExternalStore(subscribe, getProviderSnapshot, () => DEFAULT_PROVIDER); const apiKey = useSyncExternalStore( subscribe, () => getApiKeySnapshot(provider), () => '', ); const setProvider = useCallback((provider: AgentProvider) => { const current = getProviderSnapshot(); if (current === provider) return; localStorage.setItem(PROVIDER_STORAGE_KEY, provider); notify(); }, []); const setApiKey = useCallback( (key: string) => { const trimmed = key.trim(); const storageKey = API_KEY_STORAGE_KEYS[provider]; if (trimmed) { localStorage.setItem(storageKey, trimmed); } else { localStorage.removeItem(storageKey); } notify(); }, [provider], ); const getApiKey = useCallback((provider: AgentProvider) => getApiKeySnapshot(provider), []); return { apiKey, getApiKey, provider, setApiKey, setProvider, }; }; export const useOpenRouterKey = () => { const { apiKey, setApiKey } = useAgentProviderKey(); return { apiKey, setApiKey }; }; ================================================ FILE: packages/client/src/features/delta/index.tsx ================================================ import { Message, MessageShape } from '@bufbuild/protobuf'; import { debounceStrategy, eq, Ref, useLiveQuery, usePacedMutations } from '@tanstack/react-db'; import { Ulid } from 'id128'; import { Tooltip, TooltipTrigger } from 'react-aria-components'; import { Button } from '@the-dev-tools/ui/button'; import { Checkbox } from '@the-dev-tools/ui/checkbox'; import { RedoIcon } from '@the-dev-tools/ui/icons'; import { tw } from '@the-dev-tools/ui/tailwind-literal'; import { TextInputField } from '@the-dev-tools/ui/text-field'; import { ReferenceField } from '~/features/expression'; import { ColumnActionDelete } from '~/features/form-table'; import { ApiCollectionSchema, request, useApiCollection } from '~/shared/api'; import { eqStruct, Filter, PartialUndefined, pick } from '~/shared/lib'; import { routes } from '~/shared/routes'; export interface UseDeltaStateProps< Schema extends ApiCollectionSchema, Key extends keyof MessageShape, > { deltaId: Uint8Array | undefined; deltaSchema: ApiCollectionSchema; insertExtra?: object | undefined; isDelta?: boolean | undefined; originId: Uint8Array; originSchema: Schema; valueKey: Key; } export const useDeltaState = >({ deltaId, deltaSchema, insertExtra, isDelta = true, originId, originSchema, valueKey, }: UseDeltaStateProps) => { type Value = MessageShape[Key]; const { transport } = routes.root.useRouteContext(); const originIdKey = originSchema.keys[0]; if (!originIdKey || originSchema.keys.length > 1) throw new Error('Unsupported delta collection'); const originCollection = useApiCollection(originSchema); const origin = useLiveQuery( (_) => _.from({ item: originCollection }) .where((_) => eq(_.item![originIdKey as never], originId)) .select((_) => pick(_.item as never, valueKey)) .findOne(), [originId, valueKey, originCollection, originIdKey], ).data as Record | undefined; const originValue = origin?.[valueKey]; const deltaIdKey = deltaSchema.keys[0]; if (!deltaIdKey || deltaSchema.keys.length > 1) throw new Error('Unsupported delta collection'); const deltaCollection = useApiCollection(deltaSchema); const delta = useLiveQuery( (_) => _.from({ item: deltaCollection }) .where((_) => eq(_.item[deltaIdKey as never], deltaId)) .select((_) => pick(_.item as never, valueKey)) .findOne(), [deltaCollection, deltaId, deltaIdKey, valueKey], ).data as Record | undefined; const deltaValue = delta?.[valueKey]; let value = originValue; if (isDelta && deltaValue !== undefined) value = deltaValue; const updateOrigin = usePacedMutations({ mutationFn: async ({ transaction }) => { const mutationTime = Date.now(); const items = transaction.mutations.map( (_) => ({ ...originCollection.utils.parseKeyUnsafe(_.key as string), ..._.changes, }) as never, ); await request({ input: { items }, method: originSchema.operations.update!, transport }); await originCollection.utils.waitForSync(mutationTime); }, onMutate: (value) => { const key = originCollection.utils.getKey({ [originIdKey]: originId } as never); originCollection.update(key, (_) => { _[valueKey as never] = value as never; }); }, strategy: debounceStrategy({ wait: 200 }), }); const updateDelta = usePacedMutations({ mutationFn: async ({ transaction }) => { const mutationTime = Date.now(); const items = transaction.mutations.map( (_) => ({ ...deltaCollection.utils.parseKeyUnsafe(_.key as string), // TODO: deduplicate spec union kind enums and un-hardcode numeric value [valueKey]: { kind: 165745230 /* VALUE */, value: _.changes[valueKey as never] }, }) as never, ); await request({ input: { items }, method: deltaSchema.operations.update!, transport }); await deltaCollection.utils.waitForSync(mutationTime); }, onMutate: (value) => { const key = deltaCollection.utils.getKey({ [deltaIdKey]: deltaId } as never); deltaCollection.update(key, (_) => { _[valueKey as never] = value as never; }); }, strategy: debounceStrategy({ wait: 200 }), }); const setValue = (value: Value) => { if (!isDelta) { if (originValue === undefined) { originCollection.utils.insert?.({ [originIdKey]: originId, [valueKey]: value, } as never); } else { updateOrigin(value); } } else if (!deltaId) { deltaCollection.utils.insert?.({ [deltaIdKey]: Ulid.generate().bytes, [originIdKey]: originId, [valueKey]: value, ...insertExtra, } as never); } else { if (delta === undefined) { deltaCollection.utils.insert?.({ [deltaIdKey]: deltaId, [originIdKey]: originId, [valueKey]: value, ...insertExtra, } as never); } else { updateDelta(value); } } }; return [value, setValue] as const; }; export interface DeltaResetButtonProps< Schema extends ApiCollectionSchema, Key extends keyof MessageShape, > { deltaId: Uint8Array | undefined; deltaSchema: Schema; isDelta?: boolean | undefined; valueKey: Key; } export const DeltaResetButton = >({ deltaId, deltaSchema, isDelta = true, valueKey, }: DeltaResetButtonProps) => { const idKey = deltaSchema.keys[0]; if (!idKey || deltaSchema.keys.length > 1) throw new Error('Unsupported delta collection'); const collection = useApiCollection(deltaSchema); const delta = useLiveQuery( (_) => _.from({ item: collection }) .where((_) => eq(_.item![idKey as never], deltaId)) .select((_) => pick(_.item as never, valueKey)) .findOne(), [collection, deltaId, idKey, valueKey], ).data as Record | undefined; const hasDelta = delta?.[valueKey] !== undefined; if (!isDelta) return null; return ( Reset delta ); }; export interface DeltaOptions { deltaKey: keyof Filter, Uint8Array> & string; deltaParentKey: PartialUndefined>; deltaSchema: TDeltaSchema; isDelta?: boolean; originKey: keyof Filter, Uint8Array> & string; originSchema: TOriginSchema; } export interface UseDeltaColumnStateProps< TOriginSchema extends ApiCollectionSchema, TDeltaSchema extends ApiCollectionSchema, > extends DeltaOptions { originKeyObject: PartialUndefined>; valueKey: keyof MessageShape; } export const useDeltaColumnState = < TOriginSchema extends ApiCollectionSchema, TDeltaSchema extends ApiCollectionSchema, >({ deltaKey, deltaParentKey, deltaSchema, isDelta, originKey, originKeyObject, originSchema, valueKey, }: UseDeltaColumnStateProps) => { const originId = originKeyObject[originKey] as Uint8Array; const originCollection = useApiCollection(originSchema as ApiCollectionSchema); const isExtra = useLiveQuery( (_) => _.from({ item: originCollection }) .where(eqStruct(originKeyObject as Message)) .select((_) => ({ isExtra: eqStruct(deltaParentKey as Message)(_) })) .findOne(), [deltaParentKey, originCollection, originKeyObject], ).data?.isExtra ?? false; const deltaCollection = useApiCollection(deltaSchema as ApiCollectionSchema); const deltaId = useLiveQuery( (_) => _.from({ item: deltaCollection }) .where(eqStruct(deltaParentKey as Message)) .where(eqStruct(originKeyObject as Message)) .select((_) => ({ deltaId: _.item[deltaKey as never] as Ref })) .findOne(), [deltaCollection, deltaKey, deltaParentKey, originKeyObject], ).data?.deltaId as Uint8Array | undefined; const deltaOptions = { deltaId, deltaSchema, isDelta: isDelta && !isExtra, originId, originSchema, valueKey: valueKey as never, }; const [value, setValue] = useDeltaState({ ...deltaOptions, insertExtra: deltaParentKey }); return { deltaOptions, setValue, value }; }; export interface DeltaCheckboxProps< TOriginSchema extends ApiCollectionSchema, TDeltaSchema extends ApiCollectionSchema, > extends UseDeltaColumnStateProps { isReadOnly?: boolean | undefined; } export const DeltaCheckbox = ( props: DeltaCheckboxProps, ) => { const { isReadOnly = false } = props; const { deltaOptions, setValue, value } = useDeltaColumnState(props); return (
void setValue(_ as never)} /> {!isReadOnly && }
); }; export interface DeltaTextFieldProps< TOriginSchema extends ApiCollectionSchema, TDeltaSchema extends ApiCollectionSchema, > extends UseDeltaColumnStateProps { isReadOnly?: boolean | undefined; valueKey: keyof Filter, string> & string; } export const DeltaTextField = ( props: DeltaTextFieldProps, ) => { const { isReadOnly = false, valueKey } = props; const { deltaOptions, setValue, value } = useDeltaColumnState(props); return (
void setValue(_ as never)} placeholder={`Enter ${valueKey}`} value={value as unknown as string} /> {!isReadOnly && }
); }; export interface DeltaReferenceProps< TOriginSchema extends ApiCollectionSchema, TDeltaSchema extends ApiCollectionSchema, > extends UseDeltaColumnStateProps { allowFiles?: boolean; fullExpression?: boolean; isReadOnly?: boolean | undefined; valueKey: keyof Filter, string> & string; } export const DeltaReference = ( props: DeltaReferenceProps, ) => { const { allowFiles, fullExpression, isReadOnly = false, valueKey } = props; const { deltaOptions, setValue, value } = useDeltaColumnState(props); return (
void setValue(_ as never)} placeholder={`Enter ${valueKey}`} readOnly={isReadOnly} value={value as unknown as string} variant='table-cell' /> {!isReadOnly && }
); }; export const ColumnActionDeleteDelta = < TOriginSchema extends ApiCollectionSchema, TDeltaSchema extends ApiCollectionSchema, >({ deltaParentKey, isDelta, originKeyObject, originSchema, }: Omit, 'valueKey'>) => { const originCollection = useApiCollection(originSchema as ApiCollectionSchema); const isExtra = useLiveQuery( (_) => _.from({ item: originCollection }) .where(eqStruct(originKeyObject as Message)) .select((_) => ({ isExtra: eqStruct(deltaParentKey as Message)(_) })) .findOne(), [deltaParentKey, originCollection, originKeyObject], ).data?.isExtra ?? false; return ( (!isDelta || isExtra) && ( void originCollection.utils.delete?.(originKeyObject as never)} /> ) ); }; ================================================ FILE: packages/client/src/features/expression/code-mirror/drop-extension.ts ================================================ import { fromJson } from '@bufbuild/protobuf'; import { StateEffect, StateField } from '@codemirror/state'; import { EditorView, type Extension } from '@codemirror/view'; import { ReferenceKeyJson, ReferenceKeySchema } from '@the-dev-tools/spec/buf/api/reference/v1/reference_pb'; import { referenceKeysToExpression, referenceKeysToJsExpression } from '../reference-path'; export type DropFormat = 'full-expression' | 'javascript' | 'string-expression'; const MIME = 'application/x-devtools-reference'; const setDragOver = StateEffect.define(); export const referenceDropExtension = (format: DropFormat): Extension => { const dragOver = StateField.define({ create: () => false, update: (value, tr) => { for (const effect of tr.effects) { if (effect.is(setDragOver)) return effect.value; } return value; }, }); return [ dragOver, EditorView.domEventHandlers({ dragenter(event, view) { if (event.dataTransfer?.types.includes(MIME)) { view.dispatch({ effects: setDragOver.of(true) }); } return false; }, dragleave(event, view) { if (!view.dom.contains(event.relatedTarget as Node)) { view.dispatch({ effects: setDragOver.of(false) }); } return false; }, dragover(event) { if (event.dataTransfer?.types.includes(MIME)) { event.preventDefault(); event.dataTransfer.dropEffect = 'copy'; } return false; }, drop(event, view) { view.dispatch({ effects: setDragOver.of(false) }); const data = event.dataTransfer?.getData(MIME); if (!data || view.state.readOnly) return false; event.preventDefault(); const keysJson = JSON.parse(data) as ReferenceKeyJson[]; const keys = keysJson.map((_) => fromJson(ReferenceKeySchema, _)); const insertText = format === 'javascript' ? referenceKeysToJsExpression(keys) : referenceKeysToExpression(keys, format === 'string-expression' ? 'StringExpression' : 'FullExpression'); const pos = view.posAtCoords({ x: event.clientX, y: event.clientY }) ?? view.state.selection.main.head; view.dispatch({ changes: [{ from: pos, insert: insertText }], selection: { anchor: pos + insertText.length }, }); view.focus(); return true; }, }), EditorView.baseTheme({ '&.cm-editor.cm-drop-target': { outline: '2px solid var(--color-accent)' }, }), EditorView.updateListener.of((update) => { const isDragOver = update.state.field(dragOver); update.view.dom.classList.toggle('cm-drop-target', isDragOver); }), ]; }; ================================================ FILE: packages/client/src/features/expression/code-mirror/extensions.tsx ================================================ import { autocompletion, closeBrackets, closeBracketsKeymap, Completion, completionKeymap, CompletionSource, pickedCompletion, startCompletion, } from '@codemirror/autocomplete'; import { history, historyKeymap, standardKeymap } from '@codemirror/commands'; import { bracketMatching, defaultHighlightStyle, LanguageSupport, LRLanguage, syntaxHighlighting, } from '@codemirror/language'; import { ChangeSpec, EditorSelection, EditorState, Extension, Prec, Text } from '@codemirror/state'; import { EditorView, keymap, tooltips } from '@codemirror/view'; import { Client } from '@connectrpc/connect'; import { styleTags, tags } from '@lezer/highlight'; import { useQuery } from '@tanstack/react-query'; import { Array, Match, pipe } from 'effect'; import { Suspense } from 'react'; import { LuClipboardCopy } from 'react-icons/lu'; import { ReferenceCompletion, ReferenceKind, ReferenceService, } from '@the-dev-tools/spec/buf/api/reference/v1/reference_pb'; import { Button } from '@the-dev-tools/ui/button'; import { tw } from '@the-dev-tools/ui/tailwind-literal'; import { useConnectSuspenseQuery } from '~/shared/api'; import { ReactRender } from '~/shared/lib'; import { ReferenceContextProps } from '../reference'; import { parser } from './syntax.grammar'; export const CodeMirrorMarkupLanguages = ['text', 'json', 'html', 'xml'] as const; export type CodeMirrorMarkupLanguage = (typeof CodeMirrorMarkupLanguages)[number]; export const CodeMirrorLanguages = [...CodeMirrorMarkupLanguages, 'javascript'] as const; export type CodeMirrorLanguage = (typeof CodeMirrorLanguages)[number]; export const useCodeMirrorLanguageExtensions = (language: CodeMirrorLanguage): Extension[] => { const { data: extensions } = useQuery({ initialData: [], queryFn: async () => { if (language === 'text') return []; return await pipe( Match.value(language), Match.when('html', () => import('@codemirror/lang-html').then((_) => _.html())), Match.when('javascript', () => import('@codemirror/lang-javascript').then((_) => _.javascript())), Match.when('json', () => import('@codemirror/lang-json').then((_) => _.json())), Match.when('xml', () => import('@codemirror/lang-xml').then((_) => _.xml())), Match.exhaustive, (_) => _.then(Array.make), ); }, queryKey: ['code-mirror', language], }); return extensions; }; interface BuiltinMethod { detail: string; label: string; } // A builtin is either: // - callable: root-level function (e.g. `uuid()`), optional method chain on its return (e.g. `now().Unix()`) // - namespace: root-level identifier holding sub-functions (e.g. `faker.email()`) interface BuiltinFunction { detail: string; kind: 'callable' | 'namespace'; label: string; methods?: BuiltinMethod[]; name: string; } const BUILTIN_FUNCTIONS: BuiltinFunction[] = [ { detail: 'Generate UUID v4', kind: 'callable', label: 'uuid()', name: 'uuid' }, { detail: 'Generate UUID v4', kind: 'callable', label: 'uuid("v4")', name: 'uuid' }, { detail: 'Generate UUID v7', kind: 'callable', label: 'uuid("v7")', name: 'uuid' }, { detail: 'Generate ULID', kind: 'callable', label: 'ulid()', name: 'ulid' }, { detail: 'Current ISO 8601 datetime', kind: 'callable', label: 'now()', methods: [ { detail: 'Unix timestamp (seconds)', label: 'Unix()' }, { detail: 'Unix timestamp (milliseconds)', label: 'UnixMilli()' }, { detail: 'Unix timestamp (microseconds)', label: 'UnixMicro()' }, { detail: 'Unix timestamp (nanoseconds)', label: 'UnixNano()' }, ], name: 'now', }, { detail: 'Fake data generators', kind: 'namespace', label: 'faker', methods: [ { detail: 'Random full name', label: 'name()' }, { detail: 'Random first name', label: 'firstName()' }, { detail: 'Random last name', label: 'lastName()' }, { detail: 'Random male title', label: 'titleMale()' }, { detail: 'Random female title', label: 'titleFemale()' }, { detail: 'Random email', label: 'email()' }, { detail: 'Random phone number', label: 'phoneNumber()' }, { detail: 'Random URL', label: 'url()' }, { detail: 'Random domain name', label: 'domainName()' }, { detail: 'Random IPv4 address', label: 'ipv4()' }, { detail: 'Random IPv6 address', label: 'ipv6()' }, { detail: 'Random MAC address', label: 'macAddress()' }, { detail: 'Random username', label: 'username()' }, { detail: 'Random password', label: 'password()' }, { detail: 'Random word', label: 'word()' }, { detail: 'Random sentence', label: 'sentence()' }, { detail: 'Random paragraph', label: 'paragraph()' }, { detail: 'Random date', label: 'date()' }, { detail: 'Random time string', label: 'time()' }, { detail: 'Random month name', label: 'monthName()' }, { detail: 'Random day of week', label: 'dayOfWeek()' }, { detail: 'Random day of month', label: 'dayOfMonth()' }, { detail: 'Random year', label: 'year()' }, { detail: 'Random century', label: 'century()' }, { detail: 'Random timestamp', label: 'timestamp()' }, { detail: 'Random timezone', label: 'timezone()' }, { detail: 'Random unix time (int64)', label: 'unixTime()' }, { detail: 'Random credit-card number', label: 'ccNumber()' }, { detail: 'Random credit-card type', label: 'ccType()' }, { detail: 'Random currency code', label: 'currency()' }, { detail: 'Random amount with currency', label: 'amountWithCurrency()' }, { detail: 'Random hyphenated UUID', label: 'uuid()' }, { detail: 'Random digit-only UUID', label: 'uuidDigit()' }, { detail: 'Random int; (max) or (min, max)', label: 'randomInt(min, max)' }, ], name: 'faker', }, ]; interface CompletionInfoProps { completion: ReferenceCompletion; context: ReferenceContextProps; path: string; } const CompletionInfo = ({ completion, context, path }: CompletionInfoProps) => { const { data: { value }, } = useConnectSuspenseQuery(ReferenceService.method.referenceValue, { ...context, path }); return ( <>
Value:
{value}
{completion.kind === ReferenceKind.VARIABLE && (
Variable defined in environments:
    {completion.environments.map((name, index) => (
  • {name}
  • ))}
)} ); }; interface ReferenceCompletionsProps { allowFiles?: boolean | undefined; client: Client; context: ReferenceContextProps; reactRender: ReactRender; } const referenceCompletions = ({ allowFiles = false, client, context: referenceContext, reactRender, }: ReferenceCompletionsProps): CompletionSource => async (context) => { let token: string | undefined; const isExpression = context.tokenBefore(['String', 'StringExpression']) === null || context.tokenBefore(['Interpolation']) !== null; // Extract the full reference path (e.g. "response.body.data[0].name") // by scanning backwards from the cursor through valid path characters. const line = context.state.doc.lineAt(context.pos); const cursorInLine = context.pos - line.from; const textBefore = line.text.substring(0, cursorInLine); if (isExpression) { // In expression context: scan backwards for the full dotted path. // Parens are included so method chains on builtin calls (e.g. now().Unix) stay intact. let pathStart = textBefore.length; for (let i = textBefore.length - 1; i >= 0; i--) { const ch = textBefore[i]; if (/[a-zA-Z0-9_.[\]()]/.test(ch)) { pathStart = i; } else { break; } } token = textBefore.substring(pathStart); } // If not in expression context, check for {{ }} interpolation in strings if (token === undefined) { const openBraceIndex = textBefore.lastIndexOf('{{'); if (openBraceIndex >= 0) { token = textBefore.substring(openBraceIndex + 2).trim(); } } // Fallback: check for {{ }} inside JSON string tokens if (token === undefined) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any const tree = (context.state as any).syntaxTree; if (!tree) return null; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access const tokenAtCursor = tree.resolveInner(context.pos); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access if (tokenAtCursor && /string/i.test(tokenAtCursor.type.name)) { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access const stringContent = context.state.doc.sliceString(tokenAtCursor.from, tokenAtCursor.to); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const cursorOffsetInString = context.pos - tokenAtCursor.from; const textBeforeCursorInString = stringContent.substring(0, cursorOffsetInString); const varStartIndex = textBeforeCursorInString.lastIndexOf('{{'); if (varStartIndex >= 0) { token = textBeforeCursorInString.substring(varStartIndex + 2); } } } if (token === undefined) return null; // Method / namespace-member completion for builtins (VS Code-style dot chain). // - `now().` or `now().Un` → Unix(), UnixMilli(), ... (callable return) // - `faker.` or `faker.em` → email(), name(), ... (namespace member) for (const fn of BUILTIN_FUNCTIONS) { if (!fn.methods) continue; const marker = fn.kind === 'callable' ? `${fn.name}().` : `${fn.name}.`; const markerIdx = token.lastIndexOf(marker); if (markerIdx < 0) continue; const partial = token.substring(markerIdx + marker.length); if (!/^\w*$/.test(partial)) continue; return { commitCharacters: ['.', '['], filter: true, from: context.pos - partial.length, options: fn.methods.map( (m): Completion => ({ detail: m.detail, label: m.label, type: 'method', }), ), validFor: /^[a-zA-Z0-9_]*$/, }; } let options: Completion[] = []; const fileToken = '#file:'; if (allowFiles && fileToken.startsWith(token)) { options.push({ apply: async (view, completion, from) => { const { filePaths } = await window.electron.dialog('showOpenDialog', {}); const path = filePaths[0]; if (!path) return; const insert = completion.label + path; view.dispatch({ annotations: pickedCompletion.of(completion), changes: [{ from, insert }], selection: { anchor: from + insert.length }, }); }, displayLabel: fileToken, label: fileToken.replace(token, ''), }); } const items = (await client.referenceCompletion({ ...referenceContext, start: token })).items; // Builtin functions/namespaces appear only at root (no dotted prefix in the current segment). if (/^\w*$/.test(token)) { options = [ ...options, ...BUILTIN_FUNCTIONS.map( (fn): Completion => ({ detail: fn.detail, label: fn.label, type: fn.kind === 'namespace' ? 'namespace' : 'function', }), ), ]; } options = pipe( items, Array.map((_): Completion => { const type = pipe( Match.value(_.kind), Match.when(ReferenceKind.VALUE, () => 'class'), Match.when(ReferenceKind.VARIABLE, () => 'variable'), Match.when(ReferenceKind.MAP, () => 'property'), Match.when(ReferenceKind.ARRAY, () => 'property'), Match.orElse(() => undefined!), ); const detail = pipe( Match.value(_), Match.when({ kind: ReferenceKind.MAP }, (_) => `${_.itemCount} keys`), Match.when({ kind: ReferenceKind.ARRAY }, (_) => `${_.itemCount} entries`), Match.orElse(() => undefined!), ); // endIndex points to the start of the segment name within endToken // e.g. for endToken="response.body" with endIndex=9, label="body" const label = _.endToken.substring(_.endIndex); const path = _.endToken; const info = () => { if (![ReferenceKind.VALUE, ReferenceKind.VARIABLE].includes(_.kind)) return null; return reactRender(
, ); }; return { detail, info, label, type, }; }), Array.appendAll(options), ); // Calculate how many characters of the current segment the user has already typed. // All items share the same endIndex since they're at the same level. const segmentStart = items.length > 0 ? items[0].endIndex : 0; const partialLength = token.length - segmentStart; return { commitCharacters: ['.', '['], filter: true, from: context.pos - Math.max(0, partialLength), options, validFor: /^[a-zA-Z0-9_\]]*$/, }; }; interface LanguageProps extends ReferenceCompletionsProps { kind?: 'FullExpression' | 'StringExpression' | undefined; } const language = ({ kind = 'FullExpression' }: LanguageProps) => { const lrl = LRLanguage.define({ parser: parser.configure({ top: kind, props: [ styleTags({ BooleanLiteral: tags.bool, Identifier: tags.variableName, InterpolationEnd: tags.escape, InterpolationStart: tags.escape, Keyword: tags.keyword, LineComment: tags.lineComment, NilLiteral: tags.null, Number: tags.number, Operator: tags.operator, Punctuation: tags.punctuation, RawString: tags.string, SingleString: tags.string, String: tags.string, StringExpression: tags.string, }), ], }), }); return new LanguageSupport(lrl); }; const expressionBracketSpacing = EditorView.updateListener.of((update) => { if (update.changes.empty) return; // {{|}} --> {{ | }} update.changes.iterChanges((_fromA, _toA, fromB, toB, inserted) => { const doc = update.state.doc; // Handle the typical variable template insertion if ( inserted.eq(Text.of(['{}'])) && doc.sliceString(fromB - 1, fromB) === '{' && doc.sliceString(toB, toB + 1) === '}' ) { update.view.dispatch({ changes: [{ from: fromB + 1, insert: ' ' }], selection: EditorSelection.cursor(toB), }); startCompletion(update.view); } // Handle when a user types '{{' in JSON or other content // This will trigger autocompletion after typing '{{' if (inserted.eq(Text.of(['{'])) && doc.sliceString(fromB - 1, fromB) === '{') { startCompletion(update.view); } }); }); // https://discuss.codemirror.net/t/codemirror-6-single-line-and-or-avoid-carriage-return/2979/8 const singleLineModeExtensions = [ EditorState.transactionFilter.of((tr) => { if (tr.changes.empty) return tr; if (tr.newDoc.lines > 1 && !tr.isUserEvent('input.paste')) { return []; } const removeNLs: ChangeSpec[] = []; tr.changes.iterChanges((_fromA, _toA, fromB, _toB, ins) => { const lineIter = ins.iterLines().next(); if (ins.lines <= 1) return; // skip the first line let len = fromB + lineIter.value.length; lineIter.next(); // for the next lines, remove the leading NL for (; !lineIter.done; lineIter.next()) { removeNLs.push({ from: len, to: len + 1 }); len += lineIter.value.length + 1; } }); return [tr, { changes: removeNLs, sequential: true }]; }), Prec.high( keymap.of([ { key: 'ArrowUp', run: () => true }, { key: 'ArrowDown', run: () => true }, ]), ), ]; const keymaps = keymap.of([...standardKeymap, ...historyKeymap, ...closeBracketsKeymap, ...completionKeymap]); export interface BaseCodeMirrorExtensionProps extends LanguageProps { singleLineMode?: boolean; } // Additional handler to trigger completions in JSON strings const jsonStringCompletionHandler = EditorView.updateListener.of((update) => { if (!update.docChanged) return; // Look for typing "{{" in the current document const pos = update.state.selection.main.head; const line = update.state.doc.lineAt(pos); const lineText = line.text; // Check if the cursor is after a "{{" pattern in the current line const cursorPosInLine = pos - line.from; const beforeCursor = lineText.substring(0, cursorPosInLine); // Trigger completion in two scenarios: // 1. After typing '{{' anywhere if (beforeCursor.endsWith('{{')) { startCompletion(update.view); return; } // 2. When inside a JSON string that contains '{{' const openBraceIndex = beforeCursor.lastIndexOf('{{'); if (openBraceIndex >= 0) { // In a potential JSON string context if there's a quote before the {{ // and the {{ appears after the last quote const lastQuoteIndex = beforeCursor.lastIndexOf('"'); if ( lastQuoteIndex < openBraceIndex && // Make sure we're still inside the string (check for " after cursor) lineText.includes('"', cursorPosInLine) ) { startCompletion(update.view); } } }); export const baseCodeMirrorExtensions = ({ singleLineMode, ...props }: BaseCodeMirrorExtensionProps): Extension[] => { const extensions = [ tooltips({ parent: document.getElementById('cm-label-layer')!, position: 'fixed', }), keymaps, history(), closeBrackets(), autocompletion({ activateOnCompletion: () => true, override: [referenceCompletions(props)], selectOnOpen: false, }), syntaxHighlighting(defaultHighlightStyle, { fallback: true }), expressionBracketSpacing, jsonStringCompletionHandler, bracketMatching(), language(props), ]; if (singleLineMode) extensions.push(...singleLineModeExtensions); return extensions; }; ================================================ FILE: packages/client/src/features/expression/code-mirror/syntax.grammar ================================================ @top FullExpression { fullExpr* } @top StringExpression { stringContentNested* } @local tokens { InterpolationStart[closedBy=InterpolationEnd] { '{{' } stringEnd { '"' } @else stringContent } fullExpr { spaces | LineComment | Number | String | SingleString | RawString | BooleanLiteral | NilLiteral | Keyword | Operator | Punctuation | Identifier } Interpolation { InterpolationStart fullExpr* InterpolationEnd } @skip {} { InterpolationEnd[openedBy=InterpolationStart] { '}}' } stringContentNested { stringContent | Interpolation } String { stringStart stringContentNested* stringEnd } } @tokens { spaces { $[ \t\n\r]+ } Number { "0x" $[0-9a-fA-F]+ | "0o" $[0-7]+ | "0b" $[01]+ | $[0-9]+ ("." $[0-9]+)? ($[eE] $[+-]? $[0-9]+)? } Identifier { ($[a-zA-Z_] | "#") $[a-zA-Z0-9_]* } Operator { "==" | "!=" | "<=" | ">=" | "&&" | "||" | "??" | "?." | ".." | "**" | "<" | ">" | "!" | "+" | "-" | "*" | "/" | "%" | "^" | "|" | "=" } Punctuation { "." | "," | ":" | "?" | "(" | ")" | "[" | "]" | "{" | "}" | ";" } stringStart { '"' } SingleString { "'" (!['\\] | "\\" _)* "'" } RawString { "`" ![`]* "`" } LineComment { "//" ![\n]* } @precedence { Number, LineComment, stringStart, SingleString, RawString, '}}', Operator, Punctuation, Identifier, spaces } } BooleanLiteral { @specialize } NilLiteral { @specialize } Keyword { @specialize } @detectDelim ================================================ FILE: packages/client/src/features/expression/code-mirror/syntax.grammar.d.ts ================================================ import { LRParser } from '@lezer/lr'; export declare const parser: LRParser; ================================================ FILE: packages/client/src/features/expression/guess-language.tsx ================================================ import { Match, Option, pipe, Schema } from 'effect'; import { CodeMirrorMarkupLanguage } from './code-mirror/extensions'; export const guessLanguage = (code: string) => pipe( Match.value(code), Match.when( (_) => pipe(_, Schema.decodeUnknownOption(Schema.parseJson()), Option.isSome), (): CodeMirrorMarkupLanguage => 'json', ), Match.when( (_) => /<\?xml|<[a-z]+:[a-z]+/i.test(_), (): CodeMirrorMarkupLanguage => 'xml', ), Match.when( (_) => /<\/?[a-z][\s\S]*>/i.test(_), (): CodeMirrorMarkupLanguage => 'html', ), Match.orElse((): CodeMirrorMarkupLanguage => 'text'), ); ================================================ FILE: packages/client/src/features/expression/index.tsx ================================================ export { type DropFormat, referenceDropExtension } from './code-mirror/drop-extension'; export { baseCodeMirrorExtensions, type CodeMirrorMarkupLanguage, CodeMirrorMarkupLanguages, useCodeMirrorLanguageExtensions, } from './code-mirror/extensions'; export { guessLanguage } from './guess-language'; export { prettierFormat, prettierFormatQueryOptions } from './prettier'; export { ReferenceContext, ReferenceField, ReferenceTree } from './reference'; export { referenceKeysToExpression, referenceKeysToJsExpression, referenceKeysToPath } from './reference-path'; ================================================ FILE: packages/client/src/features/expression/prettier.tsx ================================================ import { queryOptions } from '@tanstack/react-query'; import { Array, Match, pipe } from 'effect'; import { format } from 'prettier/standalone'; import { CodeMirrorMarkupLanguage } from './code-mirror/extensions'; export interface PrettierFormatProps { language: CodeMirrorMarkupLanguage; text: string; } export const prettierFormat = async ({ language, text }: PrettierFormatProps) => { if (language === 'text') return text; const plugins = await pipe( Match.value(language), Match.when('json', () => [import('prettier/plugins/estree'), import('prettier/plugins/babel')]), Match.when('html', () => [import('prettier/plugins/html')]), Match.when('xml', () => [import('@prettier/plugin-xml')]), Match.exhaustive, Array.map((_) => _.then((_) => _.default)), (_) => Promise.all(_), ); const parser = pipe( Match.value(language), Match.when('json', () => 'json-stringify'), Match.orElse((_) => _), ); return await format(text, { htmlWhitespaceSensitivity: 'ignore', parser, plugins, printWidth: 100, singleAttributePerLine: true, tabWidth: 2, xmlWhitespaceSensitivity: 'ignore', }).catch(() => text); }; export const prettierFormatQueryOptions = (props: PrettierFormatProps) => queryOptions({ initialData: 'Formatting...', queryFn: () => prettierFormat(props), queryKey: ['prettier', props], }); ================================================ FILE: packages/client/src/features/expression/reference-path.ts ================================================ import { ReferenceKey, ReferenceKeyKind } from '@the-dev-tools/spec/buf/api/reference/v1/reference_pb'; /** * Strip the leading "env" GROUP key — the reference tree nests env vars under * a GROUP "env", but the expression engine resolves them at root level. */ const stripEnvGroup = (keys: ReferenceKey[]): ReferenceKey[] => keys.length > 1 && keys[0].kind === ReferenceKeyKind.GROUP && keys[0].group === 'env' ? keys.slice(1) : keys; /** Convert reference keys to a dot-separated path: `http_4.response.body.token` */ export const referenceKeysToPath = (keys: ReferenceKey[]): string => { let path = ''; for (const key of stripEnvGroup(keys)) { switch (key.kind) { case ReferenceKeyKind.ANY: path += '[*]'; break; case ReferenceKeyKind.GROUP: if (path) path += '.'; path += key.group ?? ''; break; case ReferenceKeyKind.INDEX: path += `[${String(key.index)}]`; break; case ReferenceKeyKind.KEY: if (path) path += '.'; path += key.key ?? ''; break; } } return path; }; /** Convert reference keys to an expression string based on the editor kind */ export const referenceKeysToExpression = ( keys: ReferenceKey[], kind: 'FullExpression' | 'StringExpression', ): string => { const path = referenceKeysToPath(keys); return kind === 'StringExpression' ? `{{ ${path} }}` : path; }; /** Convert reference keys to a JS expression: `ctx["http_4"].response.body.token` */ export const referenceKeysToJsExpression = (keys: ReferenceKey[]): string => { const resolved = stripEnvGroup(keys); let result = ''; for (let i = 0; i < resolved.length; i++) { const key = resolved[i]; switch (key.kind) { case ReferenceKeyKind.ANY: result += '[*]'; break; case ReferenceKeyKind.GROUP: result += i === 0 ? `ctx["${key.group ?? ''}"]` : `.${key.group ?? ''}`; break; case ReferenceKeyKind.INDEX: result += `[${String(key.index)}]`; break; case ReferenceKeyKind.KEY: result += i === 0 ? `ctx["${key.key ?? ''}"]` : `.${key.key ?? ''}`; break; } } return result; }; ================================================ FILE: packages/client/src/features/expression/reference.tsx ================================================ import { fromJson, Message, toJson } from '@bufbuild/protobuf'; import { startCompletion } from '@codemirror/autocomplete'; import { createClient } from '@connectrpc/connect'; import { useTransport } from '@connectrpc/connect-query'; import CodeMirror, { EditorView, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror'; import { Array, Match, pipe, Struct } from 'effect'; import { createContext, DragEvent, RefAttributes, use, useContext, useRef } from 'react'; import { Tree as AriaTree } from 'react-aria-components'; import { tv, VariantProps } from 'tailwind-variants'; import { ReferenceContext as ReferenceContextMessage, ReferenceKey, ReferenceKeyJson, ReferenceKeyKind, ReferenceKeySchema, ReferenceKind, ReferenceService, ReferenceTreeItem, } from '@the-dev-tools/spec/buf/api/reference/v1/reference_pb'; import { tw } from '@the-dev-tools/ui/tailwind-literal'; import { useTheme } from '@the-dev-tools/ui/theme'; import { TreeItem } from '@the-dev-tools/ui/tree'; import { useConnectSuspenseQuery } from '~/shared/api'; import { useReactRender } from '~/shared/lib'; import { referenceDropExtension } from './code-mirror/drop-extension'; import { BaseCodeMirrorExtensionProps, baseCodeMirrorExtensions } from './code-mirror/extensions'; export const makeReferenceTreeId = (keys: ReferenceKey[], value: unknown) => pipe( keys.map((_) => toJson(ReferenceKeySchema, _)), (_) => JSON.stringify([_, value]), ); export interface ReferenceContextProps extends Partial> {} export const ReferenceContext = createContext({}); interface ReferenceTreeProps extends ReferenceContextProps { onSelect?: (keys: ReferenceKey[], value: unknown) => void; } export const ReferenceTree = ({ onSelect, ...props }: ReferenceTreeProps) => { const context = useContext(ReferenceContext); const { data: { items }, } = useConnectSuspenseQuery(ReferenceService.method.referenceTree, { ...props, ...context }); return ( { if (typeof id !== 'string') return; const [keysId, value] = JSON.parse(id) as [ReferenceKeyJson[], unknown]; const keys = Array.map(keysId, (_) => fromJson(ReferenceKeySchema, _)); onSelect?.(keys, value); }} > {(_) => } ); }; const getGroupText = (key: ReferenceKey) => pipe( Match.value(key), Match.when({ kind: ReferenceKeyKind.GROUP }, (_) => _.group), Match.when({ kind: ReferenceKeyKind.KEY }, (_) => _.key), Match.orElse(() => undefined), ); const getIndexText = (key: ReferenceKey) => pipe( Match.value(key), Match.when({ kind: ReferenceKeyKind.INDEX }, (_) => _.index!.toString()), Match.when({ kind: ReferenceKeyKind.ANY }, () => 'any'), Match.orElse(() => undefined), ); interface ReferenceTreeItemProps { id: string; parentKeys: ReferenceKey[]; reference: ReferenceTreeItem; } export const ReferenceTreeItemView = ({ id, parentKeys, reference }: ReferenceTreeItemProps) => { const key = reference.key!; const keys = [...parentKeys, key]; const handleDragStart = (e: DragEvent) => { const keysJson = keys.map((_) => toJson(ReferenceKeySchema, _)); e.dataTransfer.setData('application/x-devtools-reference', JSON.stringify(keysJson)); e.dataTransfer.effectAllowed = 'copy'; }; const keyText = getGroupText(key); const items = pipe( Match.value(reference), Match.when({ kind: ReferenceKind.MAP }, (_) => _.map), Match.when({ kind: ReferenceKind.ARRAY }, (_) => _.array), Match.orElse(() => undefined), ); const kindText = pipe( Match.value(reference), Match.when({ kind: ReferenceKind.MAP }, () => 'object'), Match.when({ kind: ReferenceKind.ARRAY }, () => 'array'), Match.orElse(() => undefined), ); const indexText = getIndexText(key); const kindIndexTag = pipe( Array.fromNullable(kindText), Array.appendAll(Array.fromNullable(indexText)), Array.join(' '), (_) => _ || undefined, ); const tags = pipe( Array.fromNullable(kindIndexTag), Array.appendAll(reference.kind === ReferenceKind.VARIABLE ? reference.variable : []), ); const quantity = pipe( Match.value(reference), Match.when({ kind: ReferenceKind.MAP }, (_) => `${_.map.length} keys`), Match.when({ kind: ReferenceKind.ARRAY }, (_) => `${_.array.length} entries`), Match.orElse(() => undefined), ); return ( ( )} items={items!} onDragStart={handleDragStart} textValue={keyText ?? kindIndexTag ?? ''} > {key.kind === ReferenceKeyKind.GROUP && ( {key.group} )} {key.kind === ReferenceKeyKind.KEY && {key.key}} {tags.map((tag, index) => ( {tag} ))} {quantity && {quantity}} {reference.kind === ReferenceKind.VALUE && ( <> : {reference.value} )} ); }; const fieldStyles = tv({ base: tw`min-w-0 rounded-md border border-neutral px-3 py-0.5 text-md text-on-neutral`, variants: { variant: { 'table-cell': tw`w-full rounded-none border-transparent px-5 py-0.5 -outline-offset-4`, }, }, }); interface ReferenceFieldProps extends Partial, ReactCodeMirrorProps, RefAttributes, VariantProps {} export const ReferenceField = ({ allowFiles, kind, className, extensions = [], onFocus: onFocusParent, ref: refProp, singleLineMode = true, ...forwardedProps }: ReferenceFieldProps) => { const props = Struct.omit(forwardedProps, ...fieldStyles.variantKeys); const variantProps = Struct.pick(forwardedProps, ...fieldStyles.variantKeys); const { theme } = useTheme(); const transport = useTransport(); const client = createClient(ReferenceService, transport); const ref = useRef(null); const onFocus: typeof onFocusParent = (event) => { onFocusParent?.(event); setTimeout(() => { if (!ref.current?.view) return; startCompletion(ref.current.view); }, 0); }; const context = use(ReferenceContext); const reactRender = useReactRender(); return ( { if (typeof refProp === 'function') refProp(_); else if (refProp) refProp.current = _; ref.current = _; }} theme={theme} {...props} /> ); }; ================================================ FILE: packages/client/src/features/file-system/index.tsx ================================================ import { create } from '@bufbuild/protobuf'; import { and, eq, isUndefined, or, useLiveQuery } from '@tanstack/react-db'; import { linkOptions, ToOptions, useMatchRoute, useNavigate, useRouter } from '@tanstack/react-router'; import CodeMirror from '@uiw/react-codemirror'; import { Match, pipe } from 'effect'; import { Ulid } from 'id128'; import { createContext, RefObject, useContext, useMemo, useRef } from 'react'; import { Dialog, Heading, MenuTrigger, SubmenuTrigger, Text, Tree, TreeProps, useDragAndDrop, } from 'react-aria-components'; import { FiFolder, FiMoreHorizontal, FiWifi, FiX } from 'react-icons/fi'; import { RiAnthropicFill, RiGeminiFill, RiOpenaiFill } from 'react-icons/ri'; import { TbGauge } from 'react-icons/tb'; import { twJoin } from 'tailwind-merge'; import { Credential, CredentialKind, CredentialSchema } from '@the-dev-tools/spec/buf/api/credential/v1/credential_pb'; import { ExportService } from '@the-dev-tools/spec/buf/api/export/v1/export_pb'; import { File, FileKind, FileSchema, FileUpdate_ParentIdUnion_Kind, FolderSchema, } from '@the-dev-tools/spec/buf/api/file_system/v1/file_system_pb'; import { FlowSchema, FlowService } from '@the-dev-tools/spec/buf/api/flow/v1/flow_pb'; import { GraphQLDeltaSchema, GraphQLSchema as GraphQLItemSchema, } from '@the-dev-tools/spec/buf/api/graph_q_l/v1/graph_q_l_pb'; import { HttpDeltaSchema, HttpMethod, HttpSchema, HttpService } from '@the-dev-tools/spec/buf/api/http/v1/http_pb'; import { WebSocketSchema as WebSocketItemSchema } from '@the-dev-tools/spec/buf/api/web_socket/v1/web_socket_pb'; import { CredentialAnthropicCollectionSchema, CredentialCollectionSchema, CredentialGeminiCollectionSchema, CredentialOpenAiCollectionSchema, } from '@the-dev-tools/spec/tanstack-db/v1/api/credential'; import { FileCollectionSchema, FolderCollectionSchema } from '@the-dev-tools/spec/tanstack-db/v1/api/file_system'; import { FlowCollectionSchema } from '@the-dev-tools/spec/tanstack-db/v1/api/flow'; import { GraphQLCollectionSchema, GraphQLDeltaCollectionSchema, } from '@the-dev-tools/spec/tanstack-db/v1/api/graph_q_l'; import { HttpCollectionSchema, HttpDeltaCollectionSchema } from '@the-dev-tools/spec/tanstack-db/v1/api/http'; import { WebSocketCollectionSchema } from '@the-dev-tools/spec/tanstack-db/v1/api/web_socket'; import { Button } from '@the-dev-tools/ui/button'; import { FlowsIcon, FolderOpenedIcon } from '@the-dev-tools/ui/icons'; import { Menu, MenuItem, useContextMenuState } from '@the-dev-tools/ui/menu'; import { MethodBadge } from '@the-dev-tools/ui/method-badge'; import { Modal, useProgrammaticModal } from '@the-dev-tools/ui/modal'; import { DropIndicatorHorizontal } from '@the-dev-tools/ui/reorder'; import { tw } from '@the-dev-tools/ui/tailwind-literal'; import { TextInputField, useEditableTextState } from '@the-dev-tools/ui/text-field'; import { useTheme } from '@the-dev-tools/ui/theme'; import { TreeItem, TreeItemProps, TreeItemRouteLink } from '@the-dev-tools/ui/tree'; import { saveFile, useEscapePortal } from '@the-dev-tools/ui/utils'; import { useDeltaState } from '~/features/delta'; import { useApiCollection, useConnectMutation } from '~/shared/api'; import { eqStruct, getNextOrder, handleCollectionReorder, pick } from '~/shared/lib'; import { routes } from '~/shared/routes'; const useInsertFile = (parentFolderId?: Uint8Array) => { const { workspaceId } = routes.dashboard.workspace.route.useLoaderData(); const fileCollection = useApiCollection(FileCollectionSchema); return async (props: Pick) => fileCollection.utils.insert({ ...props, ...(parentFolderId && { parentId: parentFolderId }), order: await getNextOrder(fileCollection), workspaceId, }); }; // Module-level defaults — NEVER replace these with `create(Schema)` inline in render // as a `useLiveQuery` fallback. Creating a fresh message per render churns identity, // the collection re-keys, and React re-renders in a loop (minified error #185). const defaultFile = create(FileSchema); const defaultFolder = create(FolderSchema); const defaultHttp = create(HttpSchema); const defaultHttpDelta = create(HttpDeltaSchema); const defaultFlow = create(FlowSchema); const defaultGraphQL = create(GraphQLItemSchema); const defaultGraphQLDelta = create(GraphQLDeltaSchema); const defaultWebSocket = create(WebSocketItemSchema); const defaultCredential = create(CredentialSchema); interface FileCreateMenuProps { navigate?: boolean; parentFolderId?: Uint8Array; } export const FileCreateMenu = ({ parentFolderId, ...props }: FileCreateMenuProps) => { const router = useRouter(); const navigate = useNavigate(); const fileTreeContext = useContext(FileTreeContext); const toNavigate = props.navigate ?? fileTreeContext.navigate ?? false; const { workspaceId } = routes.dashboard.workspace.route.useLoaderData(); const folderCollection = useApiCollection(FolderCollectionSchema); const graphqlCollection = useApiCollection(GraphQLCollectionSchema); const httpCollection = useApiCollection(HttpCollectionSchema); const flowCollection = useApiCollection(FlowCollectionSchema); const websocketCollection = useApiCollection(WebSocketCollectionSchema); const insertFile = useInsertFile(parentFolderId); return ( { const folderId = Ulid.generate().bytes; folderCollection.utils.insert({ folderId, name: 'New folder' }); }} > Folder { const httpUlid = Ulid.generate(); httpCollection.utils.insert({ httpId: httpUlid.bytes, method: HttpMethod.GET, name: 'New HTTP request' }); await insertFile({ fileId: httpUlid.bytes, kind: FileKind.HTTP }); if (toNavigate) await navigate({ from: router.routesById[routes.dashboard.workspace.route.id].fullPath, params: { httpIdCan: httpUlid.toCanonical() }, to: router.routesById[routes.dashboard.workspace.http.route.id].fullPath, }); }} > HTTP request { const graphqlUlid = Ulid.generate(); graphqlCollection.utils.insert({ graphqlId: graphqlUlid.bytes, name: 'New GraphQL request' }); await insertFile({ fileId: graphqlUlid.bytes, kind: FileKind.GRAPH_Q_L }); if (toNavigate) await navigate({ from: router.routesById[routes.dashboard.workspace.route.id].fullPath, params: { graphqlIdCan: graphqlUlid.toCanonical() }, to: router.routesById[routes.dashboard.workspace.graphql.route.id].fullPath, }); }} > GraphQL request { const flowUlid = Ulid.generate(); flowCollection.utils.insert({ flowId: flowUlid.bytes, name: 'New flow', workspaceId }); await insertFile({ fileId: flowUlid.bytes, kind: FileKind.FLOW }); if (toNavigate) await navigate({ from: router.routesById[routes.dashboard.workspace.route.id].fullPath, params: { flowIdCan: flowUlid.toCanonical() }, to: router.routesById[routes.dashboard.workspace.flow.route.id].fullPath, }); }} > Flow { const websocketUlid = Ulid.generate(); websocketCollection.utils.insert({ name: 'New WebSocket', url: '', websocketId: websocketUlid.bytes }); await insertFile({ fileId: websocketUlid.bytes, kind: FileKind.WEB_SOCKET }); }} > WebSocket ); }; const CreateCredentialSubmenu = ({ navigate: toNavigate, parentFolderId }: FileCreateMenuProps) => { const router = useRouter(); const navigate = useNavigate(); const { workspaceId } = routes.dashboard.workspace.route.useLoaderData(); const credentialCollection = useApiCollection(CredentialCollectionSchema); const credentialOpenAiCollection = useApiCollection(CredentialOpenAiCollectionSchema); const credentialGeminiCollection = useApiCollection(CredentialGeminiCollectionSchema); const credentialAnthropicCollection = useApiCollection(CredentialAnthropicCollectionSchema); const insertFile = useInsertFile(parentFolderId); const insertBase = async ({ kind, name }: Pick) => { const credentialId = Ulid.generate().bytes; credentialCollection.utils.insert({ credentialId, kind, name: `${name} credential`, workspaceId }); await insertFile({ fileId: credentialId, kind: FileKind.CREDENTIAL }); return credentialId; }; const open = async (credentialId: Uint8Array) => { if (!toNavigate) return; await navigate({ from: router.routesById[routes.dashboard.workspace.route.id].fullPath, params: { credentialIdCan: Ulid.construct(credentialId).toCanonical() }, to: router.routesById[routes.dashboard.workspace.credential.id].fullPath, }); }; return ( Credential { const credentialId = await insertBase({ kind: CredentialKind.OPEN_AI, name: 'OpenAI' }); credentialOpenAiCollection.utils.insert({ credentialId }); await open(credentialId); }} > OpenAI { const credentialId = await insertBase({ kind: CredentialKind.GEMINI, name: 'Gemini' }); credentialGeminiCollection.utils.insert({ credentialId }); await open(credentialId); }} > Gemini { const credentialId = await insertBase({ kind: CredentialKind.ANTHROPIC, name: 'Anthropic' }); credentialAnthropicCollection.utils.insert({ credentialId }); await open(credentialId); }} > Anthropic ); }; interface FileTreeContext { containerRef: RefObject; kind?: FileKind; navigate?: boolean; showControls?: boolean; } const FileTreeContext = createContext({} as FileTreeContext); interface FileTreeProps extends Omit, Pick, 'onAction' | 'onSelectionChange' | 'selectedKeys' | 'selectionMode'> {} export const FileTree = ({ onAction, onSelectionChange, selectedKeys, selectionMode, ...context }: FileTreeProps) => { const { workspaceId } = routes.dashboard.workspace.route.useLoaderData(); const { kind } = context; const fileCollection = useApiCollection(FileCollectionSchema); const { data: files } = useLiveQuery( (_) => { let query = _.from({ file: fileCollection }).where((_) => and(eq(_.file.workspaceId, workspaceId), isUndefined(_.file.parentId)), ); if (kind) query = query.where((_) => or(eq(_.file.kind, kind), eq(_.file.kind, FileKind.FOLDER))); return query.orderBy((_) => _.file.order).select((_) => pick(_.file, 'fileId', 'order')); }, [fileCollection, kind, workspaceId], ); const ref = useRef(null); const { dragAndDropHooks } = useDragAndDrop({ getItems: (keys) => [...keys].map((key) => { const kind = pipe( key.toString(), (_) => fileCollection.get(_)?.kind ?? FileKind.UNSPECIFIED, (_) => `kind_${_}`, ); return { key: key.toString(), [kind]: '' }; }), shouldAcceptItemDrop: ({ dropPosition, key }, sourceKinds) => { if (dropPosition !== 'on') return false; const sourceCanMove = !sourceKinds.has(`kind_${FileKind.UNSPECIFIED}`) && !sourceKinds.has(`kind_${FileKind.HTTP_DELTA}`) && !sourceKinds.has(`kind_${FileKind.GRAPH_Q_L_DELTA}`); const targetCanAccept = fileCollection.get(key.toString())?.kind === FileKind.FOLDER; return sourceCanMove && targetCanAccept; }, onItemDrop: async ({ items, target: { dropPosition, key: targetKey } }) => { const [item] = items; if (dropPosition !== 'on' || item?.kind !== 'text' || items.length !== 1) return; const source = fileCollection.get(await item.getText('key')); const target = fileCollection.get(targetKey.toString()); if (!source || !target) return; if (source.kind === FileKind.UNSPECIFIED) return; if (target.kind !== FileKind.FOLDER) return; fileCollection.utils.update({ fileId: source.fileId, order: await getNextOrder(fileCollection), parentId: { kind: FileUpdate_ParentIdUnion_Kind.VALUE, value: target.fileId }, }); }, onReorder: handleCollectionReorder(fileCollection), renderDropIndicator: () => , }); return (
{(_) => }
); }; interface FileItemProps { id: string; } const FileItem = ({ id }: FileItemProps) => { const fileCollection = useApiCollection(FileCollectionSchema); const { fileId } = useMemo(() => fileCollection.utils.parseKeyUnsafe(id), [fileCollection.utils, id]); const { kind } = useLiveQuery( (_) => _.from({ file: fileCollection }) .where((_) => eq(_.file.fileId, fileId)) .select((_) => pick(_.file, 'kind')) .findOne(), [fileCollection, fileId], ).data ?? defaultFile; return pipe( Match.value(kind), Match.when(FileKind.FOLDER, () => ), Match.when(FileKind.HTTP, () => ), Match.when(FileKind.HTTP_DELTA, () => ), Match.when(FileKind.FLOW, () => ), Match.when(FileKind.GRAPH_Q_L, () => ), Match.when(FileKind.GRAPH_Q_L_DELTA, () => ), Match.when(FileKind.CREDENTIAL, () => ), Match.when(FileKind.WEB_SOCKET, () => ), Match.orElse(() => null), ); }; const FolderFile = ({ id }: FileItemProps) => { const fileCollection = useApiCollection(FileCollectionSchema); const { containerRef, kind, showControls } = useContext(FileTreeContext); const { fileId: folderId } = useMemo(() => fileCollection.utils.parseKeyUnsafe(id), [fileCollection.utils, id]); const folderCollection = useApiCollection(FolderCollectionSchema); const folderQuery = useLiveQuery( (_) => _.from({ folder: folderCollection }) .where((_) => eq(_.folder.folderId, folderId)) .select((_) => pick(_.folder, 'name')) .findOne(), [folderCollection, folderId], ); const { name } = folderQuery.data ?? defaultFolder; const { data: files } = useLiveQuery( (_) => { let query = _.from({ file: fileCollection }).where((_) => eq(_.file.parentId, folderId)); if (kind) query = query.where((_) => or(eq(_.file.kind, kind), eq(_.file.kind, FileKind.FOLDER))); return query.orderBy((_) => _.file.order).select((_) => pick(_.file, 'fileId', 'order')); }, [fileCollection, folderId, kind], ); const { escapeRef, escapeRender } = useEscapePortal(containerRef); const { edit, isEditing, textFieldProps } = useEditableTextState({ onSuccess: (_) => folderCollection.utils.update({ folderId, name: _ }), value: name, }); const { menuProps, menuTriggerProps, onContextMenu } = useContextMenuState(); if (!folderQuery.data) return null; return ( } items={files} onContextMenu={onContextMenu} textValue={name} > {({ isExpanded }) => ( <> {name === 'Credentials' ? ( ) : isExpanded ? ( ) : ( )} {name} {isEditing && escapeRender( , )} {showControls && ( New void edit()}>Rename pipe(fileCollection.utils.parseKeyUnsafe(id), (_) => fileCollection.utils.delete(_))} variant='danger' > Delete )} )} ); }; const HttpFile = ({ id }: FileItemProps) => { const matchRoute = useMatchRoute(); const router = useRouter(); const navigate = useNavigate(); const { theme } = useTheme(); const { workspaceId } = routes.dashboard.workspace.route.useLoaderData(); const fileCollection = useApiCollection(FileCollectionSchema); const { fileId: httpId } = useMemo(() => fileCollection.utils.parseKeyUnsafe(id), [fileCollection.utils, id]); const httpCollection = useApiCollection(HttpCollectionSchema); const httpQuery = useLiveQuery( (_) => _.from({ http: httpCollection }) .where((_) => eq(_.http.httpId, httpId)) .select((_) => pick(_.http, 'name', 'method')) .findOne(), [httpCollection, httpId], ); const { method, name } = httpQuery.data ?? defaultHttp; const deltaCollection = useApiCollection(HttpDeltaCollectionSchema); const { data: files } = useLiveQuery( (_) => _.from({ file: fileCollection }) .where((_) => eq(_.file.parentId, httpId)) .orderBy((_) => _.file.order) .select((_) => pick(_.file, 'fileId', 'order')), [fileCollection, httpId], ); const modal = useProgrammaticModal(); const duplicateMutation = useConnectMutation(HttpService.method.httpDuplicate); const exportMutation = useConnectMutation(ExportService.method.export); const exportCurlMutation = useConnectMutation(ExportService.method.exportCurl); const { containerRef, navigate: toNavigate = false, showControls } = useContext(FileTreeContext); const { escapeRef, escapeRender } = useEscapePortal(containerRef); const { edit, isEditing, textFieldProps } = useEditableTextState({ onSuccess: (_) => httpCollection.utils.update({ httpId, name: _ }), value: name, }); const { menuProps, menuTriggerProps, onContextMenu } = useContextMenuState(); const route = { from: router.routesById[routes.dashboard.workspace.route.id].fullPath, params: { httpIdCan: Ulid.construct(httpId).toCanonical() }, to: router.routesById[routes.dashboard.workspace.http.route.id].fullPath, } satisfies ToOptions; const content = ( <> {modal.children && } {name} {isEditing && escapeRender( , )} {showControls && ( { const deltaHttpId = Ulid.generate().bytes; deltaCollection.utils.insert({ deltaHttpId, httpId }); fileCollection.utils.insert({ fileId: deltaHttpId, kind: FileKind.HTTP_DELTA, order: await getNextOrder(fileCollection), parentId: httpId, workspaceId, }); if (toNavigate) await navigate({ from: router.routesById[routes.dashboard.workspace.route.id].fullPath, params: { deltaHttpIdCan: Ulid.construct(deltaHttpId).toCanonical(), httpIdCan: Ulid.construct(httpId).toCanonical(), }, to: router.routesById[routes.dashboard.workspace.http.delta.id].fullPath, }); }} > New delta void edit()}>Rename duplicateMutation.mutateAsync({ httpId })}>Duplicate Export { const { data, name } = await exportMutation.mutateAsync({ fileIds: [httpId], workspaceId }); saveFile({ blobParts: [data], name }); }} > YAML (DevTools) { const { data } = await exportCurlMutation.mutateAsync({ httpIds: [httpId], workspaceId }); modal.onOpenChange( true, {({ close }) => ( <>
cURL export
)}
, ); }} > cURL
pipe(fileCollection.utils.parseKeyUnsafe(id), (_) => fileCollection.utils.delete(_))} variant='danger' > Delete
)} ); const props = { children: content, className: toNavigate && matchRoute(route) !== false ? tw`bg-neutral` : '', id, item: (_) => , items: files, onContextMenu, textValue: name, } satisfies TreeItemProps<(typeof files)[number]>; // Hide orphan file rows — file exists in FileCollection but detail row hasn't loaded / doesn't exist. if (!httpQuery.data) return null; return toNavigate ? : ; }; const HttpDeltaFile = ({ id }: FileItemProps) => { const router = useRouter(); const matchRoute = useMatchRoute(); const { theme } = useTheme(); const { workspaceId } = routes.dashboard.workspace.route.useLoaderData(); const fileCollection = useApiCollection(FileCollectionSchema); const { fileId: deltaHttpId } = useMemo(() => fileCollection.utils.parseKeyUnsafe(id), [fileCollection.utils, id]); const deltaCollection = useApiCollection(HttpDeltaCollectionSchema); const httpDeltaQuery = useLiveQuery( (_) => _.from({ item: deltaCollection }) .where((_) => eq(_.item.deltaHttpId, deltaHttpId)) .select((_) => pick(_.item, 'httpId')) .findOne(), [deltaCollection, deltaHttpId], ); const { httpId } = httpDeltaQuery.data ?? defaultHttpDelta; const deltaOptions = { deltaId: deltaHttpId, deltaSchema: HttpDeltaCollectionSchema, originId: httpId, originSchema: HttpCollectionSchema, } as const; const [name, setName] = useDeltaState({ ...deltaOptions, valueKey: 'name' }); const [method] = useDeltaState({ ...deltaOptions, valueKey: 'method' }); const modal = useProgrammaticModal(); const exportMutation = useConnectMutation(ExportService.method.export); const exportCurlMutation = useConnectMutation(ExportService.method.exportCurl); const { containerRef, navigate: toNavigate = false, showControls } = useContext(FileTreeContext); const { escapeRef, escapeRender } = useEscapePortal(containerRef); const { edit, isEditing, textFieldProps } = useEditableTextState({ onSuccess: (_) => { if (_ === name) return; setName(_); }, value: name ?? '', }); const { menuProps, menuTriggerProps, onContextMenu } = useContextMenuState(); const route = { from: router.routesById[routes.dashboard.workspace.route.id].fullPath, params: { deltaHttpIdCan: Ulid.construct(deltaHttpId).toCanonical(), httpIdCan: Ulid.construct(httpId).toCanonical(), }, to: router.routesById[routes.dashboard.workspace.http.delta.id].fullPath, } satisfies ToOptions; const content = ( <> {modal.children && } {name} {isEditing && escapeRender( , )} {showControls && ( void edit()}>Rename Export { const { data, name } = await exportMutation.mutateAsync({ fileIds: [deltaHttpId], workspaceId }); saveFile({ blobParts: [data], name }); }} > YAML (DevTools) { const { data } = await exportCurlMutation.mutateAsync({ httpIds: [deltaHttpId], workspaceId }); modal.onOpenChange( true, {({ close }) => ( <>
cURL export
)}
, ); }} > cURL
void fileCollection.utils.delete({ fileId: deltaHttpId })} variant='danger'> Delete
)} ); const props = { children: content, className: toNavigate && matchRoute(route) !== false ? tw`bg-neutral` : '', id, onContextMenu, textValue: name ?? '', } satisfies TreeItemProps; if (!httpDeltaQuery.data) return null; return toNavigate ? : ; }; const FlowFile = ({ id }: FileItemProps) => { const router = useRouter(); const matchRoute = useMatchRoute(); const { workspaceId } = routes.dashboard.workspace.route.useLoaderData(); const fileCollection = useApiCollection(FileCollectionSchema); const { fileId: flowId } = useMemo(() => fileCollection.utils.parseKeyUnsafe(id), [fileCollection.utils, id]); const flowCollection = useApiCollection(FlowCollectionSchema); const flowQuery = useLiveQuery( (_) => _.from({ flow: flowCollection }) .where((_) => eq(_.flow.flowId, flowId)) .select((_) => pick(_.flow, 'name')) .findOne(), [flowCollection, flowId], ); const { name } = flowQuery.data ?? defaultFlow; const duplicateMutation = useConnectMutation(FlowService.method.flowDuplicate); const exportMutation = useConnectMutation(ExportService.method.export); const { containerRef, navigate: toNavigate = false, showControls } = useContext(FileTreeContext); const { escapeRef, escapeRender } = useEscapePortal(containerRef); const { edit, isEditing, textFieldProps } = useEditableTextState({ onSuccess: (_) => flowCollection.utils.update({ flowId, name: _ }), value: name, }); const { menuProps, menuTriggerProps, onContextMenu } = useContextMenuState(); const route = { from: router.routesById[routes.dashboard.workspace.route.id].fullPath, params: { flowIdCan: Ulid.construct(flowId).toCanonical() }, to: router.routesById[routes.dashboard.workspace.flow.route.id].fullPath, } satisfies ToOptions; const content = ( <> {name} {isEditing && escapeRender( , )} {showControls && ( void edit()}>Rename duplicateMutation.mutateAsync({ flowId })}>Duplicate { const { data, name } = await exportMutation.mutateAsync({ fileIds: [flowId], workspaceId }); saveFile({ blobParts: [data], name }); }} > Export YAML (DevTools) pipe(fileCollection.utils.parseKeyUnsafe(id), (_) => fileCollection.utils.delete(_))} variant='danger' > Delete )} ); const props = { children: content, className: toNavigate && matchRoute(route) !== false ? tw`bg-neutral` : '', id, onContextMenu, textValue: name, } satisfies TreeItemProps; if (!flowQuery.data) return null; return toNavigate ? : ; }; const GraphQLFile = ({ id }: FileItemProps) => { const matchRoute = useMatchRoute(); const router = useRouter(); const navigate = useNavigate(); const { theme } = useTheme(); const { workspaceId } = routes.dashboard.workspace.route.useLoaderData(); const fileCollection = useApiCollection(FileCollectionSchema); const { fileId: graphqlId } = useMemo(() => fileCollection.utils.parseKeyUnsafe(id), [fileCollection.utils, id]); const graphqlCollection = useApiCollection(GraphQLCollectionSchema); const graphqlQuery = useLiveQuery( (_) => _.from({ item: graphqlCollection }) .where((_) => eq(_.item.graphqlId, graphqlId)) .select((_) => pick(_.item, 'name')) .findOne(), [graphqlCollection, graphqlId], ); const { name } = graphqlQuery.data ?? defaultGraphQL; const deltaCollection = useApiCollection(GraphQLDeltaCollectionSchema); const { data: files } = useLiveQuery( (_) => _.from({ file: fileCollection }) .where((_) => eq(_.file.parentId, graphqlId)) .orderBy((_) => _.file.order) .select((_) => pick(_.file, 'fileId', 'order')), [fileCollection, graphqlId], ); const modal = useProgrammaticModal(); const exportMutation = useConnectMutation(ExportService.method.export); const exportCurlGraphQLMutation = useConnectMutation(ExportService.method.exportCurlGraphQL); const { containerRef, navigate: toNavigate = false, showControls } = useContext(FileTreeContext); const { escapeRef, escapeRender } = useEscapePortal(containerRef); const { edit, isEditing, textFieldProps } = useEditableTextState({ onSuccess: (_) => graphqlCollection.utils.update({ graphqlId, name: _ }), value: name, }); const { menuProps, menuTriggerProps, onContextMenu } = useContextMenuState(); const route = { from: router.routesById[routes.dashboard.workspace.route.id].fullPath, params: { graphqlIdCan: Ulid.construct(graphqlId).toCanonical() }, to: router.routesById[routes.dashboard.workspace.graphql.route.id].fullPath, } satisfies ToOptions; const content = ( <> {modal.children && } GQL {name} {isEditing && escapeRender( , )} {showControls && ( { const deltaGraphqlId = Ulid.generate().bytes; deltaCollection.utils.insert({ deltaGraphqlId, graphqlId }); fileCollection.utils.insert({ fileId: deltaGraphqlId, kind: FileKind.GRAPH_Q_L_DELTA, order: await getNextOrder(fileCollection), parentId: graphqlId, workspaceId, }); if (toNavigate) await navigate({ from: router.routesById[routes.dashboard.workspace.route.id].fullPath, params: { deltaGraphqlIdCan: Ulid.construct(deltaGraphqlId).toCanonical(), graphqlIdCan: Ulid.construct(graphqlId).toCanonical(), }, to: router.routesById[routes.dashboard.workspace.graphql.delta.id].fullPath, }); }} > New delta void edit()}>Rename Export { const { data, name } = await exportMutation.mutateAsync({ fileIds: [graphqlId], workspaceId, }); saveFile({ blobParts: [data], name }); }} > YAML (DevTools) { const { data } = await exportCurlGraphQLMutation.mutateAsync({ graphqlIds: [graphqlId], workspaceId, }); modal.onOpenChange( true, {({ close }) => ( <>
cURL export
)}
, ); }} > cURL
pipe(fileCollection.utils.parseKeyUnsafe(id), (_) => fileCollection.utils.delete(_))} variant='danger' > Delete
)} ); const props = { children: content, className: toNavigate && matchRoute(route) !== false ? tw`bg-neutral` : '', id, item: (_) => , items: files, onContextMenu, textValue: name, } satisfies TreeItemProps<(typeof files)[number]>; if (!graphqlQuery.data) return null; return toNavigate ? : ; }; const GraphQLDeltaFile = ({ id }: FileItemProps) => { const router = useRouter(); const matchRoute = useMatchRoute(); const { theme } = useTheme(); const { workspaceId } = routes.dashboard.workspace.route.useLoaderData(); const fileCollection = useApiCollection(FileCollectionSchema); const { fileId: deltaGraphqlId } = useMemo(() => fileCollection.utils.parseKeyUnsafe(id), [fileCollection.utils, id]); const deltaCollection = useApiCollection(GraphQLDeltaCollectionSchema); const graphqlDeltaQuery = useLiveQuery( (_) => _.from({ item: deltaCollection }) .where((_) => eq(_.item.deltaGraphqlId, deltaGraphqlId)) .select((_) => pick(_.item, 'graphqlId')) .findOne(), [deltaCollection, deltaGraphqlId], ); const { graphqlId } = graphqlDeltaQuery.data ?? defaultGraphQLDelta; const deltaOptions = { deltaId: deltaGraphqlId, deltaSchema: GraphQLDeltaCollectionSchema, originId: graphqlId, originSchema: GraphQLCollectionSchema, } as const; const [name, setName] = useDeltaState({ ...deltaOptions, valueKey: 'name' }); const modal = useProgrammaticModal(); const exportMutation = useConnectMutation(ExportService.method.export); const exportCurlGraphQLMutation = useConnectMutation(ExportService.method.exportCurlGraphQL); const { containerRef, navigate: toNavigate = false, showControls } = useContext(FileTreeContext); const { escapeRef, escapeRender } = useEscapePortal(containerRef); const { edit, isEditing, textFieldProps } = useEditableTextState({ onSuccess: (_) => { if (_ === name) return; setName(_); }, value: name ?? '', }); const { menuProps, menuTriggerProps, onContextMenu } = useContextMenuState(); const route = { from: router.routesById[routes.dashboard.workspace.route.id].fullPath, params: { deltaGraphqlIdCan: Ulid.construct(deltaGraphqlId).toCanonical(), graphqlIdCan: Ulid.construct(graphqlId).toCanonical(), }, to: router.routesById[routes.dashboard.workspace.graphql.delta.id].fullPath, } satisfies ToOptions; const content = ( <> {modal.children && } GQL {name} {isEditing && escapeRender( , )} {showControls && ( void edit()}>Rename Export { const { data, name } = await exportMutation.mutateAsync({ fileIds: [deltaGraphqlId], workspaceId, }); saveFile({ blobParts: [data], name }); }} > YAML (DevTools) { const { data } = await exportCurlGraphQLMutation.mutateAsync({ graphqlIds: [deltaGraphqlId], workspaceId, }); modal.onOpenChange( true, {({ close }) => ( <>
cURL export
)}
, ); }} > cURL
void fileCollection.utils.delete({ fileId: deltaGraphqlId })} variant='danger'> Delete
)} ); const props = { children: content, className: toNavigate && matchRoute(route) !== false ? tw`bg-neutral` : '', id, onContextMenu, textValue: name ?? '', } satisfies TreeItemProps; if (!graphqlDeltaQuery.data) return null; return toNavigate ? : ; }; const WebSocketFile = ({ id }: FileItemProps) => { const router = useRouter(); const matchRoute = useMatchRoute(); const { workspaceId } = routes.dashboard.workspace.route.useLoaderData(); const fileCollection = useApiCollection(FileCollectionSchema); const { fileId: websocketId } = useMemo(() => fileCollection.utils.parseKeyUnsafe(id), [fileCollection.utils, id]); const websocketCollection = useApiCollection(WebSocketCollectionSchema); const websocketQuery = useLiveQuery( (_) => _.from({ item: websocketCollection }) .where((_) => eq(_.item.websocketId, websocketId)) .select((_) => pick(_.item, 'name')) .findOne(), [websocketCollection, websocketId], ); const { name } = websocketQuery.data ?? defaultWebSocket; const exportMutation = useConnectMutation(ExportService.method.export); const { containerRef, navigate: toNavigate = false, showControls } = useContext(FileTreeContext); const { escapeRef, escapeRender } = useEscapePortal(containerRef); const { edit, isEditing, textFieldProps } = useEditableTextState({ onSuccess: (_) => websocketCollection.utils.update({ name: _, websocketId }), value: name, }); const { menuProps, menuTriggerProps, onContextMenu } = useContextMenuState(); const route = { from: router.routesById[routes.dashboard.workspace.route.id].fullPath, params: { websocketIdCan: Ulid.construct(websocketId).toCanonical() }, to: router.routesById[routes.dashboard.workspace.websocket.route.id].fullPath, } satisfies ToOptions; const content = ( <> {name} {isEditing && escapeRender( , )} {showControls && ( void edit()}>Rename Export { const { data, name } = await exportMutation.mutateAsync({ fileIds: [websocketId], workspaceId, }); saveFile({ blobParts: [data], name }); }} > YAML (DevTools) pipe(fileCollection.utils.parseKeyUnsafe(id), (_) => fileCollection.utils.delete(_))} variant='danger' > Delete )} ); const props = { children: content, className: toNavigate && matchRoute(route) !== false ? tw`bg-neutral` : '', id, onContextMenu, textValue: name, } satisfies TreeItemProps; if (!websocketQuery.data) return null; return toNavigate ? : ; }; const CredentialFile = ({ id }: FileItemProps) => { const router = useRouter(); const matchRoute = useMatchRoute(); const fileCollection = useApiCollection(FileCollectionSchema); const { fileId: credentialId } = useMemo(() => fileCollection.utils.parseKeyUnsafe(id), [fileCollection.utils, id]); const credentialCollection = useApiCollection(CredentialCollectionSchema); const credentialQuery = useLiveQuery( (_) => _.from({ item: credentialCollection }) .where(eqStruct({ credentialId })) .select((_) => pick(_.item, 'name', 'kind')) .findOne(), [credentialCollection, credentialId], ); const { kind, name } = credentialQuery.data ?? defaultCredential; const { containerRef, navigate: toNavigate = false, showControls } = useContext(FileTreeContext); const { escapeRef, escapeRender } = useEscapePortal(containerRef); const { edit, isEditing, textFieldProps } = useEditableTextState({ onSuccess: (_) => credentialCollection.utils.update({ credentialId, name: _ }), value: name, }); const { menuProps, menuTriggerProps, onContextMenu } = useContextMenuState(); const route = linkOptions({ from: router.routesById[routes.dashboard.workspace.route.id].fullPath, params: { credentialIdCan: Ulid.construct(credentialId).toCanonical() }, to: router.routesById[routes.dashboard.workspace.credential.id].fullPath, }); const content = ( <> {pipe( Match.value(kind), Match.when(CredentialKind.OPEN_AI, () => ), Match.when(CredentialKind.ANTHROPIC, () => ), Match.when(CredentialKind.GEMINI, () => ), Match.orElse(() => ), )} {name} {isEditing && escapeRender( , )} {showControls && ( void edit()}>Rename pipe(fileCollection.utils.parseKeyUnsafe(id), (_) => fileCollection.utils.delete(_))} variant='danger' > Delete )} ); const props = { children: content, // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition className: toNavigate && matchRoute(route) ? tw`bg-neutral` : '', id, onContextMenu, textValue: name, } satisfies TreeItemProps; if (!credentialQuery.data) return null; return toNavigate ? : ; }; ================================================ FILE: packages/client/src/features/form-table/index.tsx ================================================ import { Tooltip, TooltipTrigger } from 'react-aria-components'; import { LuTrash2 } from 'react-icons/lu'; import { Button } from '@the-dev-tools/ui/button'; import { tw } from '@the-dev-tools/ui/tailwind-literal'; interface ColumnActionDeleteProps { onDelete: () => void; } export const ColumnActionDelete = ({ onDelete }: ColumnActionDeleteProps) => ( Delete ); ================================================ FILE: packages/client/src/pages/credential/@x/workspace.tsx ================================================ import { resolveRoutesTo } from '../../../shared/lib/router'; export const resolveRoutesFrom = resolveRoutesTo(import.meta.dirname, '../routes'); ================================================ FILE: packages/client/src/pages/credential/routes/credential/$credentialIdCan/index.tsx ================================================ import { create } from '@bufbuild/protobuf'; import { useLiveQuery } from '@tanstack/react-db'; import { createFileRoute } from '@tanstack/react-router'; import { Match, pipe } from 'effect'; import { Ulid } from 'id128'; import { CredentialAnthropicSchema, CredentialAnthropicUpdate_BaseUrlUnion_Kind, CredentialGeminiSchema, CredentialGeminiUpdate_BaseUrlUnion_Kind, CredentialKind, CredentialOpenAiSchema, CredentialOpenAiUpdate_BaseUrlUnion_Kind, CredentialSchema, } from '@the-dev-tools/spec/buf/api/credential/v1/credential_pb'; import { Unset } from '@the-dev-tools/spec/buf/global/v1/global_pb'; import { CredentialAnthropicCollectionSchema, CredentialCollectionSchema, CredentialGeminiCollectionSchema, CredentialOpenAiCollectionSchema, } from '@the-dev-tools/spec/tanstack-db/v1/api/credential'; import { tw } from '@the-dev-tools/ui/tailwind-literal'; import { TextInputField } from '@the-dev-tools/ui/text-field'; import { CredentialTab, credentialTabId } from '~/pages/credential/tab'; import { useApiCollection } from '~/shared/api'; import { eqStruct, pick } from '~/shared/lib'; import { openTab } from '~/widgets/tabs'; const defaultCredential = create(CredentialSchema); const defaultCredentialOpenAi = create(CredentialOpenAiSchema); const defaultCredentialGemini = create(CredentialGeminiSchema); const defaultCredentialAnthropic = create(CredentialAnthropicSchema); /* eslint-disable perfectionist/sort-objects */ export const Route = createFileRoute( '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(credential)/credential/$credentialIdCan/', )({ loader: ({ params: { credentialIdCan } }) => { const credentialId = Ulid.fromCanonical(credentialIdCan).bytes; return { credentialId }; }, component: RouteComponent, onEnter: async (match) => { if (!match.loaderData) return; const { credentialId } = match.loaderData; await openTab({ id: credentialTabId(credentialId), match, node: , }); }, onStay: async (match) => { if (!match.loaderData) return; const { credentialId } = match.loaderData; await openTab({ id: credentialTabId(credentialId), match, node: , }); }, }); /* eslint-enable perfectionist/sort-objects */ function RouteComponent() { const { credentialId } = Route.useLoaderData(); const collection = useApiCollection(CredentialCollectionSchema); const { kind } = useLiveQuery( (_) => _.from({ item: collection }) .where(eqStruct({ credentialId })) .select((_) => pick(_.item, 'kind')) .findOne(), [collection, credentialId], ).data ?? defaultCredential; const content = pipe( Match.value(kind), Match.when(CredentialKind.OPEN_AI, () => ), Match.when(CredentialKind.GEMINI, () => ), Match.when(CredentialKind.ANTHROPIC, () => ), Match.orElse(() => null), ); return
{content}
; } const OpenAiCredentials = () => { const { credentialId } = Route.useLoaderData(); const collection = useApiCollection(CredentialOpenAiCollectionSchema); const data = useLiveQuery( (_) => _.from({ item: collection }).where(eqStruct({ credentialId })).findOne(), [collection, credentialId], ).data ?? defaultCredentialOpenAi; return ( <> collection.utils.updatePaced({ credentialId, token: _ })} type='password' value={data.token} /> collection.utils.updatePaced({ baseUrl: _ ? { kind: CredentialOpenAiUpdate_BaseUrlUnion_Kind.VALUE, value: _ } : { kind: CredentialOpenAiUpdate_BaseUrlUnion_Kind.UNSET, unset: Unset.UNSET }, credentialId, }) } value={data.baseUrl ?? ''} /> ); }; const GeminiCredentials = () => { const { credentialId } = Route.useLoaderData(); const collection = useApiCollection(CredentialGeminiCollectionSchema); const data = useLiveQuery( (_) => _.from({ item: collection }).where(eqStruct({ credentialId })).findOne(), [collection, credentialId], ).data ?? defaultCredentialGemini; return ( <> collection.utils.updatePaced({ apiKey: _, credentialId })} type='password' value={data.apiKey} /> collection.utils.updatePaced({ baseUrl: _ ? { kind: CredentialGeminiUpdate_BaseUrlUnion_Kind.VALUE, value: _ } : { kind: CredentialGeminiUpdate_BaseUrlUnion_Kind.UNSET, unset: Unset.UNSET }, credentialId, }) } value={data.baseUrl ?? ''} /> ); }; const AnthropicCredentials = () => { const { credentialId } = Route.useLoaderData(); const collection = useApiCollection(CredentialAnthropicCollectionSchema); const data = useLiveQuery( (_) => _.from({ item: collection }).where(eqStruct({ credentialId })).findOne(), [collection, credentialId], ).data ?? defaultCredentialAnthropic; return ( <> collection.utils.updatePaced({ apiKey: _, credentialId })} type='password' value={data.apiKey} /> collection.utils.updatePaced({ baseUrl: _ ? { kind: CredentialAnthropicUpdate_BaseUrlUnion_Kind.VALUE, value: _ } : { kind: CredentialAnthropicUpdate_BaseUrlUnion_Kind.UNSET, unset: Unset.UNSET }, credentialId, }) } value={data.baseUrl ?? ''} /> ); }; ================================================ FILE: packages/client/src/pages/credential/tab.tsx ================================================ import { useLiveQuery } from '@tanstack/react-db'; import { Match, pipe } from 'effect'; import { useEffect } from 'react'; import { RiAnthropicFill, RiGeminiFill, RiOpenaiFill } from 'react-icons/ri'; import { TbGauge } from 'react-icons/tb'; import { CredentialKind } from '@the-dev-tools/spec/buf/api/credential/v1/credential_pb'; import { CredentialCollectionSchema } from '@the-dev-tools/spec/tanstack-db/v1/api/credential'; import { tw } from '@the-dev-tools/ui/tailwind-literal'; import { useApiCollection } from '~/shared/api'; import { eqStruct, pick } from '~/shared/lib'; import { routes } from '~/shared/routes'; import { useCloseTab } from '~/widgets/tabs'; interface CredentialTabProps { credentialId: Uint8Array; } export const credentialTabId = (credentialId: Uint8Array) => JSON.stringify({ credentialId, route: routes.dashboard.workspace.flow.route.id }); export const CredentialTab = ({ credentialId }: CredentialTabProps) => { const closeTab = useCloseTab(); const collection = useApiCollection(CredentialCollectionSchema); const credential = useLiveQuery( (_) => _.from({ item: collection }) .where(eqStruct({ credentialId })) .select((_) => pick(_.item, 'name', 'kind')) .findOne(), [collection, credentialId], ).data; const credentialExists = credential !== undefined; useEffect(() => { if (!credentialExists) void closeTab(credentialTabId(credentialId)); }, [credentialExists, credentialId, closeTab]); return ( <> {pipe( Match.value(credential?.kind), Match.when(CredentialKind.OPEN_AI, () => ), Match.when(CredentialKind.ANTHROPIC, () => ( )), Match.when(CredentialKind.GEMINI, () => ), Match.orElse(() => ), )} {credential?.name} ); }; ================================================ FILE: packages/client/src/pages/dashboard/index.tsx ================================================ import { resolveRoutesTo } from '../../shared/lib/router'; export const resolveRoutesFrom = resolveRoutesTo(import.meta.dirname, 'routes'); ================================================ FILE: packages/client/src/pages/dashboard/routes/(user)/__virtual.ts ================================================ import { resolveRoutesFrom } from '../../../user/@x/dashboard'; export default resolveRoutesFrom(import.meta.dirname); ================================================ FILE: packages/client/src/pages/dashboard/routes/(workspace)/__virtual.ts ================================================ import { resolveRoutesFrom } from '../../../workspace/@x/dashboard'; export default resolveRoutesFrom(import.meta.dirname); ================================================ FILE: packages/client/src/pages/dashboard/routes/index.tsx ================================================ import { create } from '@bufbuild/protobuf'; import { timestampDate } from '@bufbuild/protobuf/wkt'; import { count, eq, useLiveQuery } from '@tanstack/react-db'; import { createFileRoute, useRouter } from '@tanstack/react-router'; import { DateTime, pipe } from 'effect'; import { Ulid } from 'id128'; import { RefObject, useMemo, useRef } from 'react'; import { Dialog, Heading, ListBox, ListBoxItem, MenuTrigger, useDragAndDrop } from 'react-aria-components'; import { FiMoreHorizontal } from 'react-icons/fi'; import TimeAgo from 'react-timeago'; import { twJoin } from 'tailwind-merge'; import { WorkspaceSchema } from '@the-dev-tools/spec/buf/api/workspace/v1/workspace_pb'; import { FileCollectionSchema } from '@the-dev-tools/spec/tanstack-db/v1/api/file_system'; import { WorkspaceCollectionSchema } from '@the-dev-tools/spec/tanstack-db/v1/api/workspace'; import { Avatar } from '@the-dev-tools/ui/avatar'; import { Button } from '@the-dev-tools/ui/button'; import { CollectionIcon } from '@the-dev-tools/ui/icons'; import { RouteLink } from '@the-dev-tools/ui/link'; import { Menu, MenuItem, useContextMenuState } from '@the-dev-tools/ui/menu'; import { Modal, useProgrammaticModal } from '@the-dev-tools/ui/modal'; import { DropIndicatorHorizontal } from '@the-dev-tools/ui/reorder'; import { tw } from '@the-dev-tools/ui/tailwind-literal'; import { TextInputField, useEditableTextState } from '@the-dev-tools/ui/text-field'; import { useEscapePortal } from '@the-dev-tools/ui/utils'; import { useApiCollection } from '~/shared/api'; import { getNextOrder, handleCollectionReorder, pick } from '~/shared/lib'; import { routes } from '~/shared/routes'; import { DashboardLayout } from '~/shared/ui'; const defaultWorkspace = create(WorkspaceSchema); export const Route = createFileRoute('/(dashboard)/')({ component: RouteComponent, }); function RouteComponent() { return ( Home}> ); } export const WorkspaceListPage = () => { const workspaceCollection = useApiCollection(WorkspaceCollectionSchema); const { data: workspaces } = useLiveQuery( (_) => _.from({ workspace: workspaceCollection }) .orderBy((_) => _.workspace.order) .select((_) => pick(_.workspace, 'workspaceId', 'name', 'order')), [workspaceCollection], ); const containerRef = useRef(null); const { dragAndDropHooks } = useDragAndDrop({ getItems: (keys) => [...keys].map((key) => ({ key: key.toString() })), onReorder: handleCollectionReorder(workspaceCollection), renderDropIndicator: () => , }); return (
{pipe(DateTime.unsafeNow(), DateTime.formatLocal({ dateStyle: 'full' }))}

Welcome to DevTools 👋

Your Workspaces
{(_) => }
); }; interface ItemProps { containerRef: RefObject; id: string; } const Item = ({ containerRef, id }: ItemProps) => { const router = useRouter(); const workspaceCollection = useApiCollection(WorkspaceCollectionSchema); const workspaceUlid = useMemo( () => pipe(workspaceCollection.utils.parseKeyUnsafe(id), (_) => Ulid.construct(_.workspaceId)), [id, workspaceCollection.utils], ); const { name, updated } = useLiveQuery( (_) => _.from({ workspace: workspaceCollection }) .where((_) => eq(_.workspace.workspaceId, workspaceUlid.bytes)) .select((_) => pick(_.workspace, 'name', 'updated')) .findOne(), [workspaceCollection, workspaceUlid], ).data ?? defaultWorkspace; const fileCollection = useApiCollection(FileCollectionSchema); const { fileCount = 0 } = useLiveQuery( (_) => _.from({ file: fileCollection }) .where((_) => eq(_.file.workspaceId, workspaceUlid.bytes)) .select((_) => ({ fileCount: count(_.file.fileId) })) .findOne(), [fileCollection, workspaceUlid], ).data ?? {}; const { menuProps, menuTriggerProps, onContextMenu } = useContextMenuState(); const { escapeRef, escapeRender } = useEscapePortal(containerRef); const { edit, isEditing, textFieldProps } = useEditableTextState({ onSuccess: (_) => workspaceCollection.utils.update({ name: _, workspaceId: workspaceUlid.bytes }), value: name, }); const deleteModal = useProgrammaticModal(); return ( {deleteModal.children && }
{name}
{name}
{isEditing && escapeRender( , )}
Created {updated && ( <>
Updated )}
Files
{fileCount}
void edit()}>Rename void deleteModal.onOpenChange( true, Delete workspace?
Are you sure you want to delete “{name}”? This action cannot be undone.
, ) } variant='danger' > Delete
); }; ================================================ FILE: packages/client/src/pages/flow/@x/workspace.tsx ================================================ import { resolveRoutesTo } from '../../../shared/lib/router'; export const resolveRoutesFrom = resolveRoutesTo(import.meta.dirname, '../routes'); ================================================ FILE: packages/client/src/pages/flow/add-node.tsx ================================================ import { MessageInitShape } from '@bufbuild/protobuf'; import * as XF from '@xyflow/react'; import { Ulid } from 'id128'; import { ReactNode, use } from 'react'; import * as RAC from 'react-aria-components'; import { FiArrowLeft, FiBriefcase, FiChevronRight, FiClock, FiPlay, FiSend, FiTerminal, FiWifi, FiX, } from 'react-icons/fi'; import { TbRobotFace } from 'react-icons/tb'; import { FileKind } from '@the-dev-tools/spec/buf/api/file_system/v1/file_system_pb'; import { HandleKind, NodeGraphQLInsertSchema, NodeHttpInsertSchema, NodeKind, } from '@the-dev-tools/spec/buf/api/flow/v1/flow_pb'; import { HttpMethod } from '@the-dev-tools/spec/buf/api/http/v1/http_pb'; import { FileCollectionSchema } from '@the-dev-tools/spec/tanstack-db/v1/api/file_system'; import { EdgeCollectionSchema, NodeAiCollectionSchema, NodeCollectionSchema, NodeConditionCollectionSchema, NodeForCollectionSchema, NodeForEachCollectionSchema, NodeGraphQLCollectionSchema, NodeHttpCollectionSchema, NodeJsCollectionSchema, NodeRunSubFlowCollectionSchema, NodeSubFlowReturnCollectionSchema, NodeSubFlowTriggerCollectionSchema, NodeWaitCollectionSchema, NodeWsConnectionCollectionSchema, NodeWsSendCollectionSchema, } from '@the-dev-tools/spec/tanstack-db/v1/api/flow'; import { GraphQLCollectionSchema } from '@the-dev-tools/spec/tanstack-db/v1/api/graph_q_l'; import { HttpCollectionSchema } from '@the-dev-tools/spec/tanstack-db/v1/api/http'; import { WebSocketCollectionSchema } from '@the-dev-tools/spec/tanstack-db/v1/api/web_socket'; import { Button } from '@the-dev-tools/ui/button'; import { FlowsIcon, ForIcon, IfIcon, SendRequestIcon } from '@the-dev-tools/ui/icons'; import { ListBoxItem } from '@the-dev-tools/ui/list-box'; import { tw } from '@the-dev-tools/ui/tailwind-literal'; import { FileTree } from '~/features/file-system'; import { useApiCollection } from '~/shared/api'; import { getNextOrder } from '~/shared/lib'; import { routes } from '~/shared/routes'; import { FlowContext } from './context'; export interface AddNodeSidebarProps { handleKind?: HandleKind | undefined; position?: undefined | XF.XYPosition; previous?: ReactNode; sourceId?: Uint8Array | undefined; targetId?: Uint8Array | undefined; } export const AddNodeSidebar = (props: AddNodeSidebarProps) => { const { setSidebar } = use(FlowContext); return ( <> } onAction={() => void setSidebar?.((_) => )} title='Flow' /> } onAction={() => void setSidebar?.((_) => )} title='Core' /> ); }; interface SidebarHeaderProps { previous?: ReactNode; title: string; } export const SidebarHeader = ({ previous, title }: SidebarHeaderProps) => { const { setSidebar } = use(FlowContext); return (
{previous && ( )}
{title}
); }; interface SidebarItemProps { description?: string; icon: ReactNode; onAction: () => void; title: string; } export const SidebarItem = ({ description, icon, onAction, title }: SidebarItemProps) => (
{icon}
{title}
{description &&
{description}
}
); interface InsertNodeProps { handleKind?: HandleKind | undefined; kind: NodeKind; name: string; nodeId: Uint8Array; position?: undefined | XF.XYPosition; sourceId?: Uint8Array | undefined; targetId?: Uint8Array | undefined; } export const useInsertNode = () => { const { flowId, setSidebar } = use(FlowContext); const { getNodes, screenToFlowPosition } = XF.useReactFlow(); const storeApi = XF.useStoreApi(); const nodeCollection = useApiCollection(NodeCollectionSchema); const edgeCollection = useApiCollection(EdgeCollectionSchema); return ({ handleKind, kind, name, nodeId, position, sourceId, targetId }: InsertNodeProps) => { const { domNode } = storeApi.getState(); const box = domNode?.getBoundingClientRect(); const defaultPosition: XF.XYPosition = box ? screenToFlowPosition({ x: box.x + box.width * 0.5, y: box.y + box.height * 0.4 }) : { x: 0, y: 0 }; nodeCollection.utils.insert({ flowId, kind, name: `${name}_${getNodes().length}`, nodeId, position: position ?? defaultPosition, }); if (sourceId) edgeCollection.utils.insert({ edgeId: Ulid.generate().bytes, flowId, sourceId, targetId: nodeId, ...(handleKind && { sourceHandle: handleKind }), }); if (targetId) edgeCollection.utils.insert({ edgeId: Ulid.generate().bytes, flowId, sourceId: nodeId, targetId, ...(handleKind && { sourceHandle: handleKind }), }); setSidebar?.(null); }; }; const AddFlowNodeSidebar = ({ handleKind, position, previous, sourceId, targetId }: AddNodeSidebarProps) => { const insertNode = useInsertNode(); const conditionCollection = useApiCollection(NodeConditionCollectionSchema); const forCollection = useApiCollection(NodeForCollectionSchema); const forEachCollection = useApiCollection(NodeForEachCollectionSchema); const runSubFlowCollection = useApiCollection(NodeRunSubFlowCollectionSchema); const subFlowReturnCollection = useApiCollection(NodeSubFlowReturnCollectionSchema); const subFlowTriggerCollection = useApiCollection(NodeSubFlowTriggerCollectionSchema); const waitCollection = useApiCollection(NodeWaitCollectionSchema); return ( <> } onAction={() => { const nodeId = Ulid.generate().bytes; insertNode({ handleKind, kind: NodeKind.MANUAL_START, name: 'manual_start', nodeId, position, sourceId, targetId, }); }} title='Manual Start' /> } onAction={() => { const nodeId = Ulid.generate().bytes; conditionCollection.utils.insert({ nodeId }); insertNode({ handleKind, kind: NodeKind.CONDITION, name: 'if', nodeId, position, sourceId, targetId }); }} title='If' /> } onAction={() => { const nodeId = Ulid.generate().bytes; forCollection.utils.insert({ nodeId }); insertNode({ handleKind, kind: NodeKind.FOR, name: 'for', nodeId, position, sourceId, targetId }); }} title='For loop' /> } onAction={() => { const nodeId = Ulid.generate().bytes; forEachCollection.utils.insert({ nodeId }); insertNode({ handleKind, kind: NodeKind.FOR_EACH, name: 'for_each', nodeId, position, sourceId, targetId }); }} title='For each loop' /> } onAction={() => { const nodeId = Ulid.generate().bytes; waitCollection.utils.insert({ durationMs: 1000n, nodeId }); insertNode({ handleKind, kind: NodeKind.WAIT, name: 'wait', nodeId, position, sourceId, targetId }); }} title='Wait' /> } onAction={() => { const nodeId = Ulid.generate().bytes; runSubFlowCollection.utils.insert({ nodeId, targetFlowName: '' }); insertNode({ handleKind, kind: NodeKind.RUN_SUB_FLOW, name: 'run_sub_flow', nodeId, position, sourceId, targetId, }); }} title='Run Sub-Flow' /> } onAction={() => { const nodeId = Ulid.generate().bytes; subFlowTriggerCollection.utils.insert({ nodeId }); insertNode({ handleKind, kind: NodeKind.SUB_FLOW_TRIGGER, name: 'sub_flow_trigger', nodeId, position, sourceId, targetId, }); }} title='Sub-Flow Trigger' /> } onAction={() => { const nodeId = Ulid.generate().bytes; subFlowReturnCollection.utils.insert({ nodeId }); insertNode({ handleKind, kind: NodeKind.SUB_FLOW_RETURN, name: 'sub_flow_return', nodeId, position, sourceId, targetId, }); }} title='Sub-Flow Return' /> ); }; const AddCoreNodeSidebar = (props: AddNodeSidebarProps) => { const { handleKind, position, previous, sourceId, targetId } = props; const { setSidebar } = use(FlowContext); const insertNode = useInsertNode(); const jsCollection = useApiCollection(NodeJsCollectionSchema); const websocketCollection = useApiCollection(WebSocketCollectionSchema); const wsConnectionCollection = useApiCollection(NodeWsConnectionCollectionSchema); const wsSendCollection = useApiCollection(NodeWsSendCollectionSchema); return ( <> } onAction={() => { const nodeId = Ulid.generate().bytes; jsCollection.utils.insert({ nodeId }); insertNode({ handleKind, kind: NodeKind.JS, name: 'js', nodeId, position, sourceId, targetId }); }} title='JavaScript' /> } onAction={() => void setSidebar?.((_) => )} title='HTTP Request' /> } onAction={() => void setSidebar?.((_) => )} title='GraphQL Request' /> } onAction={() => { const websocketId = Ulid.generate().bytes; websocketCollection.utils.insert({ name: 'New WebSocket', url: '', websocketId }); const nodeId = Ulid.generate().bytes; wsConnectionCollection.utils.insert({ nodeId, websocketId }); insertNode({ handleKind, kind: NodeKind.WS_CONNECTION, name: 'ws_connection', nodeId, position, sourceId, targetId, }); }} title='WebSocket Connection' /> } onAction={() => { const nodeId = Ulid.generate().bytes; wsSendCollection.utils.insert({ nodeId }); insertNode({ handleKind, kind: NodeKind.WS_SEND, name: 'ws_send', nodeId, position, sourceId, targetId, }); }} title='WebSocket Send' /> ); }; const AddHttpRequestNodeSidebar = ({ handleKind, position, previous, sourceId, targetId }: AddNodeSidebarProps) => { const { workspaceId } = routes.dashboard.workspace.route.useLoaderData(); const insertNode = useInsertNode(); const fileCollection = useApiCollection(FileCollectionSchema); const httpCollection = useApiCollection(HttpCollectionSchema); const nodeHttpCollection = useApiCollection(NodeHttpCollectionSchema); return ( <>
{ const nodeId = Ulid.generate().bytes; const data: MessageInitShape = { nodeId }; const file = fileCollection.get(key.toString())!; if (file.kind === FileKind.HTTP) { data.httpId = file.fileId; } else if (file.kind === FileKind.HTTP_DELTA) { data.httpId = file.parentId!; data.deltaHttpId = file.fileId; } else { return; } nodeHttpCollection.utils.insert(data); insertNode({ handleKind, kind: NodeKind.HTTP, name: 'http', nodeId, position, sourceId, targetId }); }} showControls /> ); }; const AddGraphQLRequestNodeSidebar = ({ handleKind, position, previous, sourceId, targetId }: AddNodeSidebarProps) => { const { workspaceId } = routes.dashboard.workspace.route.useLoaderData(); const insertNode = useInsertNode(); const fileCollection = useApiCollection(FileCollectionSchema); const graphqlCollection = useApiCollection(GraphQLCollectionSchema); const nodeGraphQLCollection = useApiCollection(NodeGraphQLCollectionSchema); return ( <>
{ const nodeId = Ulid.generate().bytes; const data: MessageInitShape = { nodeId }; const file = fileCollection.get(key.toString())!; if (file.kind === FileKind.GRAPH_Q_L) { data.graphqlId = file.fileId; } else { return; } nodeGraphQLCollection.utils.insert(data); insertNode({ handleKind, kind: NodeKind.GRAPH_Q_L, name: 'graphql', nodeId, position, sourceId, targetId }); }} showControls /> ); }; const AddAiNode = ({ handleKind, position, sourceId, targetId }: AddNodeSidebarProps) => { const insertNode = useInsertNode(); const collection = useApiCollection(NodeAiCollectionSchema); return ( } onAction={() => { const nodeId = Ulid.generate().bytes; collection.utils.insert({ nodeId }); insertNode({ handleKind, kind: NodeKind.AI, name: 'ai', nodeId, position, sourceId, targetId }); }} title='AI Node' /> ); }; ================================================ FILE: packages/client/src/pages/flow/agent-panel.tsx ================================================ import { eq, useLiveQuery } from '@tanstack/react-db'; import { Ulid } from 'id128'; import { KeyboardEvent, type SyntheticEvent, use, useEffect, useMemo, useRef, useState } from 'react'; import { FiArrowUp, FiChevronUp, FiEdit, FiSettings, FiX } from 'react-icons/fi'; import Markdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { NodeCollectionSchema } from '@the-dev-tools/spec/tanstack-db/v1/api/flow'; import { Button } from '@the-dev-tools/ui/button'; import { tw } from '@the-dev-tools/ui/tailwind-literal'; import { type Message, type ToolCall, useAgentChat } from '~/features/agent'; import { type AgentProvider, useAgentProviderKey } from '~/features/agent/use-agent-provider-key'; import { useApiCollection } from '~/shared/api'; import { FlowContext } from './context'; import { useFlowSelection } from './selection'; // --------------------------------------------------------------------------- // Tool call display helpers // --------------------------------------------------------------------------- const TOOL_OVERRIDES: Record = { FlowRunRequest: ['Running', 'Ran', 'Flow'], FlowStopRequest: ['Stopping', 'Stopped', 'Flow'], }; const VERB_PAIRS: Record = { Configure: ['Configuring', 'Configured'], Connect: ['Connecting', 'Connected'], Create: ['Creating', 'Created'], Delete: ['Deleting', 'Deleted'], Disconnect: ['Disconnecting', 'Disconnected'], Get: ['Retrieving', 'Retrieved'], Inspect: ['Inspecting', 'Inspected'], Update: ['Updating', 'Updated'], }; const formatToolCall = (name: string, active: boolean): [verb: string, label: string] => { const ov = TOOL_OVERRIDES[name]; if (ov) return [active ? ov[0] : ov[1], ov[2]]; const words = name .replace(/([a-z])([A-Z])/g, '$1 $2') .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2') .split(' '); const pair = VERB_PAIRS[words[0] ?? '']; const verb = pair ? (active ? pair[0] : pair[1]) : active ? 'Running' : 'Ran'; const rest = (pair ? words.slice(1) : words) .join(' ') .replace(/\bHttp\b/g, 'HTTP') .replace(/\bJs\b/g, 'JS') .replace(/\s*Request$/g, '') .trim(); return [verb, rest || name]; }; const getToolBrief = (args: Record): null | string => { if (typeof args.name === 'string' && args.name) return args.name; if (typeof args.url === 'string' && args.url) return args.url; if (typeof args.key === 'string' && args.key) return args.key; return null; }; const PROVIDER_OPTIONS: Record< AgentProvider, { keyLabel: string; keysUrl: string; label: string; placeholder: string } > = { anthropic: { keyLabel: 'Anthropic API key', keysUrl: 'https://console.anthropic.com/settings/keys', label: 'Anthropic', placeholder: 'Paste your Anthropic key', }, openai: { keyLabel: 'OpenAI API key', keysUrl: 'https://platform.openai.com/api-keys', label: 'OpenAI', placeholder: 'Paste your OpenAI key', }, openrouter: { keyLabel: 'OpenRouter API key', keysUrl: 'https://openrouter.ai/keys', label: 'OpenRouter', placeholder: 'Paste your OpenRouter key', }, }; export const AgentPanel = () => { const { flowId, setAgentPanelOpen } = use(FlowContext); const { apiKey, provider, setApiKey, setProvider } = useAgentProviderKey(); const { deselectAll, deselectNodes, selectedNodeIds } = useFlowSelection(); const { cancel, clearMessages, error, isLoading, messages, sendMessage, streamingContent } = useAgentChat({ apiKey, flowId, provider, selectedNodeIds, }); const [input, setInput] = useState(''); const messagesEndRef = useRef(null); const textareaRef = useRef(null); const completedToolCallIds = useMemo(() => { const ids = new Set(); for (const message of messages) { if (message.role === 'tool' && message.toolCallId) { ids.add(message.toolCallId); } } return ids; }, [messages]); const activeToolMessageId = useMemo(() => { if (!isLoading) return null; for (let i = messages.length - 1; i >= 0; i--) { const message = messages[i]!; if (message.role !== 'assistant' || !message.toolCalls?.length) continue; const hasPendingToolCalls = message.toolCalls.some((tc) => !completedToolCallIds.has(tc.id)); if (hasPendingToolCalls) return message.id; } return null; }, [completedToolCallIds, isLoading, messages]); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages, isLoading, streamingContent]); const autoResize = () => { const el = textareaRef.current; if (!el) return; el.style.height = '0'; el.style.height = `${el.scrollHeight}px`; }; const handleSubmit = (e?: SyntheticEvent) => { e?.preventDefault(); if (!input.trim() || isLoading) return; void sendMessage(input.trim()); setInput(''); // Reset textarea height after clearing requestAnimationFrame(() => { if (textareaRef.current) { textareaRef.current.style.height = ''; } }); }; const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSubmit(); } }; const handleProviderChange = (nextProvider: AgentProvider) => { if (nextProvider === provider) return; clearMessages(); setProvider(nextProvider); }; return (
{/* Header */}
Agent
{apiKey ? ( <> {/* Messages */}
{messages.length === 0 ? (

Ask me to create or modify workflow nodes.

e.g. "Create a JavaScript node that returns hello world"

) : (
{messages.map((message) => ( ))} {isLoading && (streamingContent ? : )}
)} {error &&
{error}
}
{/* Input */}
{selectedNodeIds.length > 0 && ( )}