From 572accbd92cfb8817f9cd52151d388cfe06d29ee Mon Sep 17 00:00:00 2001 From: addisonbeck Date: Tue, 13 Jan 2026 14:51:59 -0500 Subject: [PATCH] . --- .../transcripts/2026-01-13-10-30-50.md | 2166 +++++++++++++++++ .claude/settings.local.json | 5 + CLAUDE.md | 528 ---- 3 files changed, 2171 insertions(+), 528 deletions(-) create mode 100644 .agent-shell/transcripts/2026-01-13-10-30-50.md create mode 100644 .claude/settings.local.json delete mode 100644 CLAUDE.md diff --git a/.agent-shell/transcripts/2026-01-13-10-30-50.md b/.agent-shell/transcripts/2026-01-13-10-30-50.md new file mode 100644 index 00000000000..f3c443e1bf9 --- /dev/null +++ b/.agent-shell/transcripts/2026-01-13-10-30-50.md @@ -0,0 +1,2166 @@ +# Agent Shell Transcript + +**Agent:** Claude Code +**Started:** 2026-01-13 10:30:50 +**Working Directory:** /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/ + +--- + +## User (2026-01-13 10:30:58) + +/plugin marketplace add /Users/me/binwarden/bitwarden-ai-plugins/main + +## User (2026-01-13 10:30:59) + +/plugin install bitwarden-init@bitwarden-marketplace + +## User (2026-01-13 10:31:03) + +/init + +## Agent (2026-01-13 10:31:05) + +Bobert will analyze this codebase to create a comprehensive CLAUDE.md file. Let Bobert start by exploring the repository structure and key documentation files. + +### Tool Call [completed]: Read /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/README.md + +**Tool:** read +**Timestamp:** 2026-01-13 10:31:09 + +``` +

+ Bitwarden +

+

+ GitHub Workflow browser build on main + GitHub Workflow CLI build on main + GitHub Workflow desktop build on main + GitHub Workflow web build on main + gitter chat +

+ +--- + +# Bitwarden Client Applications + +This repository houses all Bitwarden client applications except the mobile applications ([iOS](https://github.com/bitwarden/ios) | [android](https://github.com/bitwarden/android)). + +Please refer to the [Clients section](https://contributing.bitwarden.com/getting-started/clients/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started. + +## Related projects: + +- [bitwarden/server](https://github.com/bitwarden/server): The core infrastructure backend (API, database, Docker, etc). +- [bitwarden/ios](https://github.com/bitwarden/ios): Bitwarden iOS Password Manager & Authenticator apps. +- [bitwarden/android](https://github.com/bitwarden/android): Bitwarden Android Password Manager & Authenticator apps. +- [bitwarden/directory-connector](https://github.com/bitwarden/directory-connector): A tool for syncing a directory (AD, LDAP, Azure, G Suite, Okta) to an organization. + +# We're Hiring! + +Interested in contributing in a big way? Consider joining our team! We're hiring for many positions. Please take a look at our [Careers page](https://bitwarden.com/careers/) to see what opportunities are [currently open](https://bitwarden.com/careers/#open-positions) as well as what it's like to work at Bitwarden. + +# Contribute + +Code contributions are welcome! Please commit any pull requests against the `main` branch. Learn more about how to contribute by reading the [Contributing Guidelines](https://contributing.bitwarden.com/contributing/). Check out the [Contributing Documentation](https://contributing.bitwarden.com/) for how to get started with your first contribution. + +Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file. +``` + + +### Tool Call [completed]: Read /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/package.json + +**Tool:** read +**Timestamp:** 2026-01-13 10:31:09 + +``` +{ + "name": "@bitwarden/clients", + "version": "0.0.0", + "description": "Bitwarden Client Applications", + "repository": { + "type": "git", + "url": "git+https://github.com/bitwarden/clients.git" + }, + "author": "Bitwarden Inc. (https://bitwarden.com)", + "license": "GPL-3.0", + "bugs": { + "url": "https://github.com/bitwarden/clients/issues" + }, + "homepage": "https://bitwarden.com", + "scripts": { + "prepare": "husky", + "lint": "eslint . --cache --cache-strategy content && prettier --check .", + "lint:fix": "eslint . --cache --cache-strategy content --fix", + "lint:clear": "rimraf .eslintcache", + "prettier": "prettier --cache --write .", + "test": "jest", + "test:watch": "jest --clearCache && jest --watch", + "test:watch:all": "jest --watchAll", + "test:types": "node ./scripts/test-types.js", + "test:locales": "tsc --project ./scripts/tsconfig.json && node ./scripts/dist/test-locales.js", + "lint:dep-ownership": "tsc --project ./scripts/tsconfig.json && node ./scripts/dist/dep-ownership.js", + "lint:sdk-internal-versions": "tsc --project ./scripts/tsconfig.json && node ./scripts/dist/sdk-internal-versions.js", + "docs:json": "compodoc -p ./tsconfig.json -e json -d . --disableRoutesGraph", + "storybook": "ng run components:storybook", + "build-storybook": "ng run components:build-storybook", + "build-storybook:ci": "ng run components:build-storybook --webpack-stats-json", + "test-stories": "test-storybook --url http://localhost:6006", + "test-stories:watch": "test-stories --watch" + }, + "workspaces": [ + "apps/*", + "apps/desktop/desktop_native/napi", + "libs/**/*" + ], + "devDependencies": { + "@angular-devkit/build-angular": "20.3.12", + "@angular-eslint/schematics": "20.7.0", + "@angular/cli": "20.3.12", + "@angular/compiler-cli": "20.3.15", + "@babel/core": "7.28.5", + "@babel/preset-env": "7.28.5", + "@compodoc/compodoc": "1.1.32", + "@electron/notarize": "3.0.1", + "@electron/rebuild": "4.0.1", + "@eslint/compat": "2.0.0", + "@lit-labs/signals": "0.2.0", + "@ngtools/webpack": "20.3.12", + "@nx/devkit": "21.6.10", + "@nx/eslint": "21.6.10", + "@nx/jest": "21.6.10", + "@nx/js": "21.6.10", + "@nx/webpack": "21.6.10", + "@storybook/addon-a11y": "9.1.16", + "@storybook/addon-designs": "9.0.0-next.3", + "@storybook/addon-docs": "9.1.16", + "@storybook/addon-links": "9.1.16", + "@storybook/addon-themes": "9.1.16", + "@storybook/angular": "9.1.16", + "@storybook/test-runner": "0.22.0", + "@storybook/web-components-vite": "9.1.16", + "@tailwindcss/container-queries": "0.1.1", + "@types/chrome": "0.1.28", + "@types/firefox-webext-browser": "143.0.0", + "@types/inquirer": "8.2.10", + "@types/jest": "29.5.14", + "@types/jsdom": "21.1.7", + "@types/koa": "3.0.1", + "@types/koa__multer": "2.0.7", + "@types/koa__router": "12.0.4", + "@types/koa-bodyparser": "4.3.7", + "@types/koa-json": "2.0.23", + "@types/lowdb": "1.0.15", + "@types/lunr": "2.3.7", + "@types/node": "22.19.3", + "@types/node-fetch": "2.6.4", + "@types/node-forge": "1.3.14", + "@types/papaparse": "5.5.0", + "@types/proper-lockfile": "4.1.4", + "@types/retry": "0.12.5", + "@types/zxcvbn": "4.4.5", + "@typescript-eslint/rule-tester": "8.31.0", + "@typescript-eslint/utils": "8.31.0", + "@webcomponents/custom-elements": "1.6.0", + "@yao-pkg/pkg": "6.5.1", + "angular-eslint": "20.7.0", + "autoprefixer": "10.4.22", + "axe-playwright": "2.2.2", + "babel-loader": "9.2.1", + "base64-loader": "1.0.0", + "browserslist": "4.28.1", + "chromatic": "13.3.4", + "concurrently": "9.2.0", + "copy-webpack-plugin": "13.0.1", + "cross-env": "10.1.0", + "css-loader": "7.1.2", + "electron": "39.2.6", + "electron-builder": "26.0.12", + "electron-log": "5.4.3", + "electron-reload": "2.0.0-alpha.1", + "electron-store": "8.2.0", + "electron-updater": "6.6.4", + "eslint": "9.26.0", + "eslint-config-prettier": "10.1.2", + "eslint-import-resolver-typescript": "4.3.4", + "eslint-plugin-import": "2.31.0", + "eslint-plugin-rxjs": "5.0.3", + "eslint-plugin-rxjs-angular": "2.0.1", + "eslint-plugin-storybook": "9.1.16", + "eslint-plugin-tailwindcss": "3.18.0", + "html-loader": "5.1.0", + "html-webpack-injector": "1.1.4", + "html-webpack-plugin": "5.6.5", + "husky": "9.1.7", + "jest-diff": "30.2.0", + "jest-junit": "16.0.0", + "jest-mock-extended": "3.0.7", + "jest-preset-angular": "14.6.1", + "json5": "2.2.3", + "lint-staged": "16.0.0", + "mini-css-extract-plugin": "2.9.4", + "nx": "21.6.10", + "path-browserify": "1.0.1", + "postcss": "8.5.6", + "postcss-loader": "8.2.0", + "prettier": "3.7.3", + "prettier-plugin-tailwindcss": "0.7.1", + "process": "0.11.10", + "remark-gfm": "4.0.1", + "rimraf": "6.1.2", + "sass": "1.95.1", + "sass-loader": "16.0.6", + "storybook": "9.1.17", + "style-loader": "4.0.0", + "tailwindcss": "3.4.18", + "ts-jest": "29.4.5", + "ts-loader": "9.5.4", + "tsconfig-paths-webpack-plugin": "4.2.0", + "type-fest": "2.19.0", + "typescript": "5.8.3", + "typescript-eslint": "8.31.0", + "typescript-strict-plugin": "2.4.4", + "url": "0.11.4", + "util": "0.12.5", + "wait-on": "9.0.3", + "webpack": "5.103.0", + "webpack-cli": "6.0.1", + "webpack-dev-server": "5.2.2", + "webpack-node-externals": "3.0.0" + }, + "dependencies": { + "@angular/animations": "20.3.15", + "@angular/cdk": "20.2.14", + "@angular/common": "20.3.15", + "@angular/compiler": "20.3.15", + "@angular/core": "20.3.15", + "@angular/forms": "20.3.15", + "@angular/platform-browser": "20.3.15", + "@angular/platform-browser-dynamic": "20.3.15", + "@angular/router": "20.3.15", + "@bitwarden/sdk-internal": "0.2.0-main.450", + "@bitwarden/commercial-sdk-internal": "0.2.0-main.450", + "@electron/fuses": "1.8.0", + "@emotion/css": "11.13.5", + "@koa/multer": "4.0.0", + "@koa/router": "14.0.0", + "@microsoft/signalr": "8.0.7", + "@microsoft/signalr-protocol-msgpack": "8.0.7", + "@ng-select/ng-select": "20.7.0", + "big-integer": "1.6.52", + "braintree-web-drop-in": "1.46.0", + "buffer": "6.0.3", + "bufferutil": "4.1.0", + "chalk": "4.1.2", + "commander": "14.0.0", + "core-js": "3.47.0", + "form-data": "4.0.4", + "https-proxy-agent": "7.0.6", + "inquirer": "8.2.6", + "jsdom": "26.1.0", + "jszip": "3.10.1", + "koa": "3.1.1", + "koa-bodyparser": "4.4.1", + "koa-json": "2.0.2", + "lit": "3.3.1", + "lowdb": "1.0.0", + "lunr": "2.3.9", + "multer": "2.0.2", + "ngx-toastr": "19.1.0", + "node-fetch": "2.6.12", + "node-forge": "1.3.2", + "oidc-client-ts": "2.4.1", + "open": "11.0.0", + "papaparse": "5.5.3", + "proper-lockfile": "4.1.2", + "qrcode-parser": "2.1.3", + "qrious": "4.0.2", + "rxjs": "7.8.1", + "semver": "7.7.3", + "tabbable": "6.3.0", + "tldts": "7.0.19", + "ts-node": "10.9.2", + "utf-8-validate": "6.0.5", + "vite-tsconfig-paths": "5.1.4", + "zone.js": "0.15.1", + "zxcvbn": "4.4.2" + }, + "overrides": { + "eslint-plugin-rxjs": { + "eslint": "$eslint" + }, + "eslint-plugin-rxjs-angular": { + "eslint": "$eslint" + }, + "tailwindcss": "$tailwindcss", + "react": "18.3.1", + "react-dom": "18.3.1", + "@types/react": "18.3.27" + }, + "lint-staged": { + "*": "prettier --cache --ignore-unknown --write", + "*.ts": "eslint --cache --cache-strategy content --fix" + }, + "engines": { + "node": ">=22.12.0", + "npm": "~10" + } +} +``` + + +### Tool Call [completed]: `ls -la /Users/me/binwarden/bitwarden-clients/bitwarden-init-test` + +**Tool:** execute +**Timestamp:** 2026-01-13 10:31:09 +**Description:** List root directory contents +**Command:** ls -la /Users/me/binwarden/bitwarden-clients/bitwarden-init-test + +``` +total 3496 +drwxr-xr-x@ 44 me staff 1408 Jan 13 10:30 . +drwxr-xr-x 28 me staff 896 Jan 13 10:29 .. +drwxr-xr-x@ 3 me staff 96 Jan 13 10:30 .agent-shell +-rw-r--r--@ 1 me staff 53 Jan 13 10:29 .browserslistrc +drwxr-xr-x@ 3 me staff 96 Jan 13 10:29 .checkmarx +drwxr-xr-x@ 3 me staff 96 Jan 13 10:30 .claude +drwxr-xr-x@ 3 me staff 96 Jan 13 10:29 .codescene +-rw-r--r--@ 1 me staff 359 Jan 13 10:29 .editorconfig +-rw-r--r--@ 1 me staff 86 Jan 13 10:29 .git +-rw-r--r--@ 1 me staff 1394 Jan 13 10:29 .git-blame-ignore-revs +-rw-r--r--@ 1 me staff 19 Jan 13 10:29 .gitattributes +drwxr-xr-x@ 10 me staff 320 Jan 13 10:29 .github +-rw-r--r--@ 1 me staff 745 Jan 13 10:29 .gitignore +drwxr-xr-x@ 4 me staff 128 Jan 13 10:29 .husky +-rw-r--r--@ 1 me staff 187 Jan 13 10:29 .npmrc +-rw-r--r--@ 1 me staff 4 Jan 13 10:29 .nvmrc +-rw-r--r--@ 1 me staff 568 Jan 13 10:29 .prettierignore +-rw-r--r--@ 1 me staff 139 Jan 13 10:29 .prettierrc.json +drwxr-xr-x@ 9 me staff 288 Jan 13 10:29 .storybook +drwxr-xr-x@ 4 me staff 128 Jan 13 10:29 .vscode +-rw-r--r--@ 1 me staff 7261 Jan 13 10:29 angular.json +drwxr-xr-x@ 6 me staff 192 Jan 13 10:29 apps +-rw-r--r--@ 1 me staff 156 Jan 13 10:29 babel.config.json +drwxr-xr-x@ 7 me staff 224 Jan 13 10:29 bitwarden_license +-rw-r--r--@ 1 me staff 2084 Jan 13 10:29 clients.code-workspace +-rw-r--r--@ 1 me staff 313 Jan 13 10:29 CONTRIBUTING.md +drwxr-xr-x@ 3 me staff 96 Jan 13 10:29 docs +-rw-r--r--@ 1 me staff 22481 Jan 13 10:29 eslint.config.mjs +-rw-r--r--@ 1 me staff 3047 Jan 13 10:29 jest.config.js +-rw-r--r--@ 1 me staff 88 Jan 13 10:29 jest.preset.js +drwxr-xr-x@ 37 me staff 1184 Jan 13 10:29 libs +-rw-r--r--@ 1 me staff 10724 Jan 13 10:29 LICENSE_BITWARDEN.txt +-rw-r--r--@ 1 me staff 35140 Jan 13 10:29 LICENSE_GPL.txt +-rw-r--r--@ 1 me staff 820 Jan 13 10:29 LICENSE.txt +-rw-r--r--@ 1 me staff 1018 Jan 13 10:29 nx.json +-rw-r--r--@ 1 me staff 1603068 Jan 13 10:29 package-lock.json +-rw-r--r--@ 1 me staff 7582 Jan 13 10:29 package.json +-rw-r--r--@ 1 me staff 3337 Jan 13 10:29 README.md +drwxr-xr-x@ 7 me staff 224 Jan 13 10:29 scripts +-rw-r--r--@ 1 me staff 1794 Jan 13 10:29 SECURITY.md +-rw-r--r--@ 1 me staff 714 Jan 13 10:29 tailwind.config.js +-rw-r--r--@ 1 me staff 3943 Jan 13 10:29 tsconfig.base.json +-rw-r--r--@ 1 me staff 386 Jan 13 10:29 tsconfig.eslint.json +-rw-r--r--@ 1 me staff 434 Jan 13 10:29 tsconfig.json +``` + + +### Tool Call [completed]: Find `**/.cursorrules` + +**Tool:** search +**Timestamp:** 2026-01-13 10:31:09 + +``` +No files found +``` + + +### Tool Call [completed]: Find `**/.cursor/rules/**` + +**Tool:** search +**Timestamp:** 2026-01-13 10:31:09 + +``` +No files found +``` + + +### Tool Call [completed]: Find `**/.github/copilot-instructions.md` + +**Tool:** search +**Timestamp:** 2026-01-13 10:31:09 + +``` +No files found +``` +## Agent (2026-01-13 10:31:11) + +Now let Bobert explore the contributing documentation and examine the codebase structure: + +### Tool Call [completed]: Read /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/CONTRIBUTING.md + +**Tool:** read +**Timestamp:** 2026-01-13 10:31:16 + +``` +# How to Contribute + +Our [Contributing Guidelines](https://contributing.bitwarden.com/contributing/) are located in our [Contributing Documentation](https://contributing.bitwarden.com/). The documentation also includes recommended tooling, code style tips, and lots of other great information to get you started. +``` + + +### Tool Call [completed]: `ls -la /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/apps` + +**Tool:** execute +**Timestamp:** 2026-01-13 10:31:16 +**Description:** List apps directory +**Command:** ls -la /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/apps + +``` +total 0 +drwxr-xr-x@ 6 me staff 192 Jan 13 10:29 . +drwxr-xr-x@ 44 me staff 1408 Jan 13 10:30 .. +drwxr-xr-x@ 23 me staff 736 Jan 13 10:29 browser +drwxr-xr-x@ 20 me staff 640 Jan 13 10:29 cli +drwxr-xr-x@ 32 me staff 1024 Jan 13 10:29 desktop +drwxr-xr-x@ 26 me staff 832 Jan 13 10:29 web +``` + + +### Tool Call [completed]: `ls -la /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs` + +**Tool:** execute +**Timestamp:** 2026-01-13 10:31:16 +**Description:** List libs directory +**Command:** ls -la /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs + +``` +total 0 +drwxr-xr-x@ 37 me staff 1184 Jan 13 10:29 . +drwxr-xr-x@ 44 me staff 1408 Jan 13 10:30 .. +drwxr-xr-x@ 3 me staff 96 Jan 13 10:29 .vscode +drwxr-xr-x@ 10 me staff 320 Jan 13 10:29 admin-console +drwxr-xr-x@ 9 me staff 288 Jan 13 10:29 angular +drwxr-xr-x@ 12 me staff 384 Jan 13 10:29 assets +drwxr-xr-x@ 10 me staff 320 Jan 13 10:29 auth +drwxr-xr-x@ 13 me staff 416 Jan 13 10:29 auto-confirm +drwxr-xr-x@ 10 me staff 320 Jan 13 10:29 billing +drwxr-xr-x@ 12 me staff 384 Jan 13 10:29 client-type +drwxr-xr-x@ 11 me staff 352 Jan 13 10:29 common +drwxr-xr-x@ 13 me staff 416 Jan 13 10:29 components +drwxr-xr-x@ 12 me staff 384 Jan 13 10:29 core-test-utils +drwxr-xr-x@ 3 me staff 96 Jan 13 10:29 dirt +drwxr-xr-x@ 10 me staff 320 Jan 13 10:29 eslint +drwxr-xr-x@ 12 me staff 384 Jan 13 10:29 guid +drwxr-xr-x@ 8 me staff 256 Jan 13 10:29 importer +drwxr-xr-x@ 10 me staff 320 Jan 13 10:29 key-management +drwxr-xr-x@ 10 me staff 320 Jan 13 10:29 key-management-ui +drwxr-xr-x@ 12 me staff 384 Jan 13 10:29 logging +drwxr-xr-x@ 12 me staff 384 Jan 13 10:29 messaging +drwxr-xr-x@ 9 me staff 288 Jan 13 10:29 node +drwxr-xr-x@ 14 me staff 448 Jan 13 10:29 nx-plugin +drwxr-xr-x@ 10 me staff 320 Jan 13 10:29 platform +drwxr-xr-x@ 11 me staff 352 Jan 13 10:29 pricing +drwxr-xr-x@ 12 me staff 384 Jan 13 10:29 serialization +drwxr-xr-x@ 8 me staff 256 Jan 13 10:29 shared +drwxr-xr-x@ 13 me staff 416 Jan 13 10:29 state +drwxr-xr-x@ 12 me staff 384 Jan 13 10:29 state-internal +drwxr-xr-x@ 12 me staff 384 Jan 13 10:29 state-test-utils +drwxr-xr-x@ 12 me staff 384 Jan 13 10:29 storage-core +drwxr-xr-x@ 12 me staff 384 Jan 13 10:29 storage-test-utils +drwxr-xr-x@ 13 me staff 416 Jan 13 10:29 subscription +drwxr-xr-x@ 6 me staff 192 Jan 13 10:29 tools +drwxr-xr-x@ 4 me staff 128 Jan 13 10:29 ui +drwxr-xr-x@ 12 me staff 384 Jan 13 10:29 user-core +drwxr-xr-x@ 10 me staff 320 Jan 13 10:29 vault +``` + + +### Tool Call [completed]: Read /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/.vscode/settings.json + +**Tool:** read +**Timestamp:** 2026-01-13 10:31:16 + +``` +{ + "cSpell.words": ["Csprng", "Decapsulation", "decryptable", "Popout", "Reprompt", "takeuntil"], + "search.exclude": { + "**/locales/[^e]*/messages.json": true, + "**/locales/*[^n]/messages.json": true, + "**/_locales/[^e]*/messages.json": true, + "**/_locales/*[^n]/messages.json": true + }, + "rust-analyzer.linkedProjects": ["apps/desktop/desktop_native/Cargo.toml"], + "typescript.tsdk": "node_modules/typescript/lib", + "eslint.useFlatConfig": true +} +``` +## Agent (2026-01-13 10:31:18) + +Let Bobert explore a bit more about the architecture and build configurations: + +### Tool Call [completed]: Read /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/apps/browser/package.json + +**Tool:** read +**Timestamp:** 2026-01-13 10:31:22 + +``` +{ + "name": "@bitwarden/browser", + "version": "2025.12.1", + "scripts": { + "build": "npm run build:chrome", + "build:bit": "npm run build:bit:chrome", + "build:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", + "build:bit:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-browser/webpack.config.js", + "build:dev:chrome": "npm run build:chrome && npm run update:dev:chrome", + "build:bit:dev:chrome": "npm run build:bit:chrome && npm run update:dev:chrome", + "build:edge": "cross-env BROWSER=edge MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", + "build:bit:edge": "cross-env BROWSER=edge MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-browser/webpack.config.js", + "build:firefox": "cross-env BROWSER=firefox NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", + "build:bit:firefox": "cross-env BROWSER=firefox NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-browser/webpack.config.js", + "build:opera": "cross-env BROWSER=opera MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", + "build:bit:opera": "cross-env BROWSER=opera MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-browser/webpack.config.js", + "build:safari": "cross-env BROWSER=safari NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", + "build:bit:safari": "cross-env BROWSER=safari NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-browser/webpack.config.js", + "build:watch": "npm run build:watch:chrome", + "build:watch:chrome": "npm run build:chrome -- --watch", + "build:bit:watch:chrome": "npm run build:bit:chrome -- --watch", + "build:watch:edge": "npm run build:edge -- --watch", + "build:bit:watch:edge": "npm run build:bit:edge -- --watch", + "build:watch:firefox": "npm run build:firefox -- --watch", + "build:bit:watch:firefox": "npm run build:bit:firefox -- --watch", + "build:watch:opera": "npm run build:opera -- --watch", + "build:bit:watch:opera": "npm run build:bit:opera -- --watch", + "build:watch:safari": "npm run build:safari -- --watch", + "build:bit:watch:safari": "npm run build:bit:safari -- --watch", + "build:watch:firefox:mv3": "cross-env MANIFEST_VERSION=3 npm run build:watch:firefox", + "build:bit:watch:firefox:mv3": "cross-env MANIFEST_VERSION=3 npm run build:bit:watch:firefox", + "build:watch:safari:mv3": "cross-env MANIFEST_VERSION=3 npm run build:watch:safari", + "build:bit:watch:safari:mv3": "cross-env MANIFEST_VERSION=3 npm run build:bit:watch:safari", + "build:prod:chrome": "cross-env NODE_ENV=production npm run build:chrome", + "build:bit:prod:chrome": "cross-env NODE_ENV=production npm run build:bit:chrome", + "build:prod:edge": "cross-env NODE_ENV=production npm run build:edge", + "build:bit:prod:edge": "cross-env NODE_ENV=production npm run build:bit:edge", + "build:prod:firefox": "cross-env NODE_ENV=production npm run build:firefox", + "build:bit:prod:firefox": "cross-env NODE_ENV=production npm run build:bit:firefox", + "build:prod:opera": "cross-env NODE_ENV=production npm run build:opera", + "build:bit:prod:opera": "cross-env NODE_ENV=production npm run build:bit:opera", + "build:prod:safari": "cross-env NODE_ENV=production npm run build:safari", + "build:bit:prod:safari": "cross-env NODE_ENV=production npm run build:bit:safari", + "dist:chrome": "npm run build:prod:chrome && mkdir -p dist && ./scripts/compress.sh dist-chrome.zip", + "dist:bit:chrome": "npm run build:bit:prod:chrome && mkdir -p dist && ./scripts/compress.sh bit-dist-chrome.zip", + "dist:edge": "npm run build:prod:edge && mkdir -p dist && ./scripts/compress.sh dist-edge.zip", + "dist:bit:edge": "npm run build:bit:prod:edge && mkdir -p dist && ./scripts/compress.sh bit-dist-edge.zip", + "dist:firefox": "npm run build:prod:firefox && mkdir -p dist && ./scripts/compress.sh dist-firefox.zip", + "dist:bit:firefox": "npm run build:bit:prod:firefox && mkdir -p dist && ./scripts/compress.sh bit-dist-firefox.zip", + "dist:opera": "npm run build:prod:opera && mkdir -p dist && ./scripts/compress.sh dist-opera.zip", + "dist:bit:opera": "npm run build:bit:prod:opera && mkdir -p dist && ./scripts/compress.sh bit-dist-opera.zip", + "dist:safari": "npm run build:prod:safari && ./scripts/package-safari.ps1", + "dist:bit:safari": "npm run build:bit:prod:safari && ./scripts/package-safari.ps1", + "dist:firefox:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:firefox", + "dist:bit:firefox:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:bit:firefox", + "dist:opera:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:opera", + "dist:bit:opera:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:bit:opera", + "dist:safari:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:safari", + "dist:bit:safari:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:bit:safari", + "package:dev:chrome": "npm run update:dev:chrome && ./scripts/compress.sh dev-chrome.zip", + "package:bit:dev:chrome": "npm run update:dev:chrome && ./scripts/compress.sh bit-dev-chrome.zip", + "test": "jest", + "test:watch": "jest --watch", + "test:watch:all": "jest --watchAll", + "test:clearCache": "jest --clear-cache", + "update:dev:chrome": "./scripts/update-manifest-dev.sh" + } +} +``` + + +### Tool Call [completed]: Read /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/apps/desktop/package.json + +**Tool:** read +**Timestamp:** 2026-01-13 10:31:22 + +``` +{ + "name": "@bitwarden/desktop", + "description": "A secure and free password manager for all of your devices.", + "version": "2025.12.1", + "keywords": [ + "bitwarden", + "password", + "vault", + "password manager" + ], + "author": "Bitwarden Inc. (https://bitwarden.com)", + "homepage": "https://bitwarden.com", + "repository": { + "type": "git", + "url": "git+https://github.com/bitwarden/clients.git" + }, + "license": "GPL-3.0", + "scripts": { + "postinstall": "electron-rebuild", + "start": "cross-env ELECTRON_IS_DEV=0 ELECTRON_NO_UPDATER=1 electron ./build", + "build-native-macos": "cd desktop_native && ./macos_provider/build.sh && node build.js cross-platform", + "build-native": "cd desktop_native && node build.js", + "build": "concurrently -n Main,Rend,Prel -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\" \"npm run build:preload\"", + "build:dev": "concurrently -n Main,Rend,Prel -c yellow,cyan \"npm run build:main:dev\" \"npm run build:renderer:dev\" \"npm run build:preload:dev\"", + "build:preload": "cross-env NODE_ENV=production webpack --config webpack.config.js --config-name preload", + "build:preload:dev": "cross-env NODE_ENV=development webpack --config webpack.config.js --config-name preload", + "build:preload:watch": "cross-env NODE_ENV=development webpack --config webpack.config.js --config-name preload --watch", + "build:macos-extension:mac": "./desktop_native/macos_provider/build.sh && node scripts/build-macos-extension.js mac", + "build:macos-extension:mas": "./desktop_native/macos_provider/build.sh && node scripts/build-macos-extension.js mas", + "build:macos-extension:masdev": "./desktop_native/macos_provider/build.sh && node scripts/build-macos-extension.js mas-dev", + "build:main": "cross-env NODE_ENV=production webpack --config webpack.config.js --config-name main", + "build:main:dev": "cross-env NODE_ENV=development webpack --config webpack.config.js --config-name main", + "build:main:watch": "npm run build-native && cross-env NODE_ENV=development webpack --config webpack.config.js --config-name main --watch", + "build:renderer": "cross-env NODE_ENV=production webpack --config webpack.config.js --config-name renderer", + "build:renderer:dev": "cross-env NODE_ENV=development webpack --config webpack.config.js --config-name renderer", + "build:renderer:watch": "cross-env NODE_ENV=development webpack --config webpack.config.js --config-name renderer --watch", + "electron": "node ./scripts/start.js", + "electron:ignore": "node ./scripts/start.js --ignore-certificate-errors", + "flatpak:dev": "npm run clean:dist && electron-builder --dir -p never && flatpak-builder --force-clean --install --user ../../.flatpak/ ./resources/com.bitwarden.desktop.devel.yaml && flatpak run com.bitwarden.desktop", + "clean:dist": "rimraf ./dist", + "pack:dir": "npm run clean:dist && electron-builder --dir -p never", + "pack:lin:flatpak": "flatpak-builder --repo=../../.flatpak-repo ../../.flatpak ./resources/com.bitwarden.desktop.devel.yaml --install-deps-from=flathub --force-clean && flatpak build-bundle ../../.flatpak-repo/ ./dist/com.bitwarden.desktop.flatpak com.bitwarden.desktop", + "pack:lin": "npm run clean:dist && electron-builder --linux --x64 -p never && export SNAP_FILE=$(realpath ./dist/bitwarden_*.snap) && unsquashfs -d ./dist/tmp-snap/ $SNAP_FILE && mkdir -p ./dist/tmp-snap/meta/polkit/ && cp ./resources/com.bitwarden.desktop.policy ./dist/tmp-snap/meta/polkit/polkit.com.bitwarden.desktop.policy && rm $SNAP_FILE && snap pack --compression=lzo ./dist/tmp-snap/ && mv ./*.snap ./dist/ && rm -rf ./dist/tmp-snap/ && cp ./resources/com.bitwarden.desktop.desktop ./dist/linux-unpacked/resources && cp -r ./resources/icons ./dist/linux-unpacked/resources && tar -czvf ./dist/bitwarden_desktop_x64.tar.gz -C ./dist/linux-unpacked/ .", + "pack:lin:arm64": "npm run clean:dist && electron-builder --linux --arm64 -p never && export SNAP_FILE=$(realpath ./dist/bitwarden_*.snap) && unsquashfs -d ./dist/tmp-snap/ $SNAP_FILE && mkdir -p ./dist/tmp-snap/meta/polkit/ && cp ./resources/com.bitwarden.desktop.policy ./dist/tmp-snap/meta/polkit/polkit.com.bitwarden.desktop.policy && rm $SNAP_FILE && snap pack --compression=lzo ./dist/tmp-snap/ && mv ./*.snap ./dist/ && rm -rf ./dist/tmp-snap/ && cp ./resources/com.bitwarden.desktop.desktop ./dist/linux-arm64-unpacked/resources && cp -r ./resources/icons ./dist/linux-arm64-unpacked/resources && tar -czvf ./dist/bitwarden_desktop_arm64.tar.gz -C ./dist/linux-arm64-unpacked/ .", + "pack:mac": "npm run clean:dist && electron-builder --mac --universal -p never", + "pack:mac:with-extension": "npm run clean:dist && npm run build:macos-extension:mac && electron-builder --mac --universal -p never", + "pack:mac:arm64": "npm run clean:dist && electron-builder --mac --arm64 -p never", + "pack:mac:mas": "npm run clean:dist && npm run build:macos-extension:mas && electron-builder --mac mas --universal -p never", + "pack:mac:masdev": "npm run clean:dist && npm run build:macos-extension:masdev && electron-builder --mac mas-dev --universal -p never", + "pack:local:mac": "npm run clean:dist && npm run build:macos-extension:masdev && electron-builder --mac mas-dev --universal -p never -c.mac.provisioningProfile=\"\" -c.mas.provisioningProfile=\"\"", + "pack:win": "npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p never -c.win.signtoolOptions.certificateSubjectName=\"8bit Solutions LLC\"", + "pack:win:beta": "npm run clean:dist && electron-builder --config electron-builder.beta.json --win --x64 --arm64 --ia32 -p never -c.win.signtoolOptions.certificateSubjectName=\"8bit Solutions LLC\"", + "pack:win:ci": "npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p never", + "dist:dir": "npm run build && npm run pack:dir", + "dist:lin": "npm run build && npm run pack:lin", + "dist:lin:arm64": "npm run build && npm run pack:lin:arm64", + "dist:mac": "npm run build && npm run pack:mac", + "dist:mac:mas": "npm run build && npm run pack:mac:mas", + "dist:mac:masdev": "npm run build && npm run pack:mac:masdev", + "dist:win": "npm run build && npm run pack:win", + "dist:win:ci": "npm run build && npm run pack:win:ci", + "publish:lin": "npm run build && npm run clean:dist && electron-builder --linux --x64 -p always", + "publish:mac": "npm run build && npm run clean:dist && electron-builder --mac -p always", + "publish:mac:mas": "npm run dist:mac:mas && npm run upload:mas", + "publish:win": "npm run build && npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p always -c.win.signtoolOptions.certificateSubjectName=\"8bit Solutions LLC\"", + "publish:win:dev": "npm run build:dev && npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p always", + "upload:mas": "xcrun altool --upload-app --type osx --file \"$(find ./dist/mas-universal/Bitwarden*.pkg)\" --apiKey $APP_STORE_CONNECT_AUTH_KEY --apiIssuer $APP_STORE_CONNECT_TEAM_ISSUER", + "test": "jest", + "test:watch": "jest --watch", + "test:watch:all": "jest --watchAll" + } +} +``` + + +### Tool Call [completed]: Read /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/apps/web/package.json + +**Tool:** read +**Timestamp:** 2026-01-13 10:31:22 + +``` +{ + "name": "@bitwarden/web-vault", + "version": "2025.12.2", + "scripts": { + "build:oss": "webpack", + "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", + "build:oss:watch": "webpack serve", + "build:bit:watch": "webpack serve -c ../../bitwarden_license/bit-web/webpack.config.js", + "build:bit:dev": "cross-env ENV=development npm run build:bit", + "build:bit:dev:analyze": "cross-env LOGGING=false webpack -c ../../bitwarden_license/bit-web/webpack.config.js --profile --json > stats.json && npx webpack-bundle-analyzer stats.json build/", + "build:bit:dev:watch": "cross-env ENV=development NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:bit:watch", + "build:bit:qa": "cross-env NODE_ENV=production ENV=qa npm run build:bit", + "build:bit:euprd": "cross-env NODE_ENV=production ENV=euprd npm run build:bit", + "build:bit:euqa": "cross-env NODE_ENV=production ENV=euqa npm run build:bit", + "build:bit:usdev": "cross-env NODE_ENV=production ENV=usdev npm run build:bit", + "build:bit:cloud": "cross-env NODE_ENV=production ENV=cloud npm run build:bit", + "build:oss:selfhost:watch": "cross-env ENV=selfhosted npm run build:oss:watch", + "build:bit:selfhost:watch": "cross-env ENV=selfhosted npm run build:bit:watch", + "build:oss:selfhost:prod": "cross-env ENV=selfhosted NODE_ENV=production npm run build:oss", + "build:bit:selfhost:prod": "cross-env ENV=selfhosted NODE_ENV=production npm run build:bit", + "build:bit:selfhost:dev": "cross-env ENV=selfhosted NODE_ENV=production npm run build:bit", + "build:bit:ee": "cross-env NODE_ENV=production ENV=ee npm run build:bit", + "clean:l10n": "git push origin --delete l10n_master", + "dist:bit:cloud": "npm run build:bit:cloud", + "dist:oss:selfhost": "npm run build:oss:selfhost:prod", + "dist:bit:selfhost": "npm run build:bit:selfhost:prod", + "test": "jest", + "test:watch": "jest --watch", + "test:watch:all": "jest --watchAll" + } +} +``` + + +### Tool Call [completed]: Read /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/apps/cli/package.json + +**Tool:** read +**Timestamp:** 2026-01-13 10:31:22 + +``` +{ + "name": "@bitwarden/cli", + "description": "A secure and free password manager for all of your devices.", + "version": "2025.12.1", + "keywords": [ + "bitwarden", + "password", + "vault", + "password manager", + "cli" + ], + "author": "Bitwarden Inc. (https://bitwarden.com)", + "homepage": "https://bitwarden.com", + "repository": { + "type": "git", + "url": "git+https://github.com/bitwarden/clients.git" + }, + "license": "SEE LICENSE IN LICENSE.txt", + "scripts": { + "clean": "rimraf dist", + "build:oss": "webpack", + "build:oss:debug": "npm run build:oss && node --inspect ./build/bw.js", + "build:oss:watch": "webpack --watch", + "build:oss:prod": "cross-env NODE_ENV=production webpack", + "build:oss:prod:watch": "cross-env NODE_ENV=production webpack --watch", + "debug": "node --inspect ./build/bw.js", + "publish:npm": "npm run build:oss:prod && npm publish --access public", + "build:bit": "webpack -c ../../bitwarden_license/bit-cli/webpack.config.js", + "build:bit:debug": "npm run build:bit && node --inspect ./build/bw.js", + "build:bit:watch": "webpack --watch -c ../../bitwarden_license/bit-cli/webpack.config.js", + "build:bit:prod": "cross-env NODE_ENV=production npm run build:bit", + "build:bit:prod:watch": "cross-env NODE_ENV=production npm run build:bit:watch", + "dist:oss:win": "npm run build:oss:prod && npm run clean && npm run package:oss:win", + "dist:oss:mac": "npm run build:oss:prod && npm run clean && npm run package:oss:mac", + "dist:oss:mac-arm64": "npm run build:oss:prod && npm run clean && npm run package:oss:mac-arm64", + "dist:oss:lin": "npm run build:oss:prod && npm run clean && npm run package:oss:lin", + "dist:oss:lin-arm64": "npm run build:oss:prod && npm run clean && npm run package:oss:lin-arm64", + "dist:bit:win": "npm run build:bit:prod && npm run clean && npm run package:bit:win", + "dist:bit:mac": "npm run build:bit:prod && npm run clean && npm run package:bit:mac", + "dist:bit:mac-arm64": "npm run build:bit:prod && npm run clean && npm run package:bit:mac-arm64", + "dist:bit:lin": "npm run build:bit:prod && npm run clean && npm run package:bit:lin", + "dist:bit:lin-arm64": "npm run build:bit:prod && npm run clean && npm run package:bit:lin-arm64", + "package:oss:win": "pkg . --targets win-x64 --output ./dist/oss/windows/bw.exe", + "package:oss:mac": "pkg . --targets macos-x64 --output ./dist/oss/macos/bw", + "package:oss:mac-arm64": "pkg . --targets macos-arm64 --output ./dist/oss/macos-arm64/bw", + "package:oss:lin": "pkg . --targets linux-x64 --output ./dist/oss/linux/bw", + "package:oss:lin-arm64": "pkg . --targets linux-arm64 --output ./dist/oss/linux-arm64/bw", + "package:bit:win": "pkg . --targets win-x64 --output ./dist/bit/windows/bw.exe", + "package:bit:mac": "pkg . --targets macos-x64 --output ./dist/bit/macos/bw", + "package:bit:mac-arm64": "pkg . --targets macos-arm64 --output ./dist/bit/macos-arm64/bw", + "package:bit:lin": "pkg . --targets linux-x64 --output ./dist/bit/linux/bw", + "package:bit:lin-arm64": "pkg . --targets linux-arm64 --output ./dist/bit/linux-arm64/bw", + "test": "jest", + "test:watch": "jest --watch", + "test:watch:all": "jest --watchAll" + }, + "bin": { + "bw": "build/bw.js" + }, + "pkg": { + "assets": [ + "./build/**/*" + ] + }, + "dependencies": { + "@koa/multer": "4.0.0", + "@koa/router": "14.0.0", + "big-integer": "1.6.52", + "browser-hrtime": "1.1.8", + "chalk": "4.1.2", + "commander": "14.0.0", + "core-js": "3.47.0", + "form-data": "4.0.4", + "https-proxy-agent": "7.0.6", + "inquirer": "8.2.6", + "jsdom": "26.1.0", + "jszip": "3.10.1", + "koa": "3.1.1", + "koa-bodyparser": "4.4.1", + "koa-json": "2.0.2", + "lowdb": "1.0.0", + "lunr": "2.3.9", + "multer": "2.0.2", + "node-fetch": "2.6.12", + "node-forge": "1.3.2", + "open": "11.0.0", + "papaparse": "5.5.3", + "proper-lockfile": "4.1.2", + "rxjs": "7.8.1", + "semver": "7.7.3", + "tldts": "7.0.19", + "zxcvbn": "4.4.2" + } +} +``` + + +### Tool Call [completed]: Read /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/nx.json + +**Tool:** read +**Timestamp:** 2026-01-13 10:31:22 + +``` +{ + "$schema": "./node_modules/nx/schemas/nx-schema.json", + "cacheDirectory": ".nx/cache", + "defaultBase": "main", + "namedInputs": { + "default": ["{projectRoot}/**/*", "sharedGlobals"], + "production": ["default", "!{projectRoot}/**/*.spec.ts", "!{projectRoot}/tsconfig.spec.json"], + "sharedGlobals": ["{workspaceRoot}/tsconfig.base.json", "{workspaceRoot}/package.json"] + }, + "plugins": [ + { + "plugin": "@nx/js", + "options": { + "compiler": "tsc", + "configName": "tsconfig.lib.json", + "targetName": "build" + } + }, + { + "plugin": "@nx/jest/plugin", + "options": { + "targetName": "test" + } + }, + { + "plugin": "@nx/eslint/plugin", + "options": { + "targetName": "lint" + } + }, + "@bitwarden/nx-plugin" + ], + "parallel": 4, + "targetDefaults": { + "build": { + "dependsOn": ["^build"], + "inputs": ["production", "^production"], + "outputs": ["{options.outputPath}"], + "cache": true + } + } +} +``` +## Agent (2026-01-13 10:31:25) + +Let Bobert examine the docs directory and explore some key architectural patterns: + +### Tool Call [completed]: Read /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/tsconfig.base.json + +**Tool:** read +**Timestamp:** 2026-01-13 10:31:29 + +``` +{ + "compilerOptions": { + "strict": false, + "pretty": true, + "moduleResolution": "node", + "noImplicitAny": true, + "target": "ES2016", + "module": "ES2020", + "lib": ["es5", "es6", "es7", "dom", "ES2021", "ESNext.Disposable"], + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "declaration": false, + "outDir": "dist", + "baseUrl": ".", + "resolveJsonModule": true, + "allowJs": true, + "sourceMap": true, + "skipLibCheck": true, + "paths": { + "@bitwarden/admin-console/common": ["./libs/admin-console/src/common"], + "@bitwarden/angular/*": ["./libs/angular/src/*"], + "@bitwarden/assets": ["./libs/assets/src/index.ts"], + "@bitwarden/assets/svg": ["./libs/assets/src/svg/index.ts"], + "@bitwarden/auth/angular": ["./libs/auth/src/angular"], + "@bitwarden/auth/common": ["./libs/auth/src/common"], + "@bitwarden/auto-confirm": ["libs/auto-confirm/src/index.ts"], + "@bitwarden/billing": ["./libs/billing/src"], + "@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"], + "@bitwarden/browser/*": ["./apps/browser/src/*"], + "@bitwarden/cli/*": ["./apps/cli/src/*"], + "@bitwarden/client-type": ["./libs/client-type/src/index.ts"], + "@bitwarden/common/spec": ["./libs/common/spec"], + "@bitwarden/common/*": ["./libs/common/src/*"], + "@bitwarden/components": ["./libs/components/src"], + "@bitwarden/core-test-utils": ["./libs/core-test-utils/src/index.ts"], + "@bitwarden/dirt-card": ["./libs/dirt/card/src"], + "@bitwarden/generator-components": ["./libs/tools/generator/components/src"], + "@bitwarden/generator-core": ["./libs/tools/generator/core/src"], + "@bitwarden/generator-history": ["./libs/tools/generator/extensions/history/src"], + "@bitwarden/generator-legacy": ["./libs/tools/generator/extensions/legacy/src"], + "@bitwarden/generator-navigation": ["./libs/tools/generator/extensions/navigation/src"], + "@bitwarden/guid": ["./libs/guid/src/index.ts"], + "@bitwarden/importer-core": ["./libs/importer/src"], + "@bitwarden/importer-ui": ["./libs/importer/src/components"], + "@bitwarden/key-management": ["./libs/key-management/src"], + "@bitwarden/key-management-ui": ["./libs/key-management-ui/src"], + "@bitwarden/logging": ["./libs/logging/src"], + "@bitwarden/messaging": ["./libs/messaging/src/index.ts"], + "@bitwarden/node/*": ["./libs/node/src/*"], + "@bitwarden/nx-plugin": ["./libs/nx-plugin/src/index.ts"], + "@bitwarden/platform": ["./libs/platform/src"], + "@bitwarden/platform/*": ["./libs/platform/src/*"], + "@bitwarden/pricing": ["./libs/pricing/src/index.ts"], + "@bitwarden/send-ui": ["./libs/tools/send/send-ui/src"], + "@bitwarden/serialization": ["./libs/serialization/src/index.ts"], + "@bitwarden/state": ["./libs/state/src/index.ts"], + "@bitwarden/state-internal": ["./libs/state-internal/src/index.ts"], + "@bitwarden/state-test-utils": ["./libs/state-test-utils/src/index.ts"], + "@bitwarden/storage-core": ["./libs/storage-core/src/index.ts"], + "@bitwarden/storage-test-utils": ["./libs/storage-test-utils/src/index.ts"], + "@bitwarden/subscription": ["./libs/subscription/src/index.ts"], + "@bitwarden/ui-common": ["./libs/ui/common/src"], + "@bitwarden/ui-common/setup-jest": ["./libs/ui/common/src/setup-jest"], + "@bitwarden/user-core": ["./libs/user-core/src/index.ts"], + "@bitwarden/vault": ["./libs/vault/src"], + "@bitwarden/vault-export-core": ["./libs/tools/export/vault-export/vault-export-core/src"], + "@bitwarden/vault-export-ui": ["./libs/tools/export/vault-export/vault-export-ui/src"], + "@bitwarden/web-vault/*": ["./apps/web/src/*"] + }, + "plugins": [ + { + "name": "typescript-strict-plugin" + } + ], + "useDefineForClassFields": false + } +} +``` + + +### Tool Call [completed]: grep "@bitwarden/" /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/tsconfig.base.json + +**Tool:** search +**Timestamp:** 2026-01-13 10:31:29 + +``` +21: "@bitwarden/admin-console/common": ["./libs/admin-console/src/common"], +22: "@bitwarden/angular/*": ["./libs/angular/src/*"], +23: "@bitwarden/assets": ["./libs/assets/src/index.ts"], +24: "@bitwarden/assets/svg": ["./libs/assets/src/svg/index.ts"], +25: "@bitwarden/auth/angular": ["./libs/auth/src/angular"], +26: "@bitwarden/auth/common": ["./libs/auth/src/common"], +27: "@bitwarden/auto-confirm": ["libs/auto-confirm/src/index.ts"], +28: "@bitwarden/billing": ["./libs/billing/src"], +29: "@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"], +30: "@bitwarden/browser/*": ["./apps/browser/src/*"], +31: "@bitwarden/cli/*": ["./apps/cli/src/*"], +32: "@bitwarden/client-type": ["./libs/client-type/src/index.ts"], +33: "@bitwarden/common/spec": ["./libs/common/spec"], +34: "@bitwarden/common/*": ["./libs/common/src/*"], +35: "@bitwarden/components": ["./libs/components/src"], +36: "@bitwarden/core-test-utils": ["./libs/core-test-utils/src/index.ts"], +37: "@bitwarden/dirt-card": ["./libs/dirt/card/src"], +38: "@bitwarden/generator-components": ["./libs/tools/generator/components/src"], +39: "@bitwarden/generator-core": ["./libs/tools/generator/core/src"], +40: "@bitwarden/generator-history": ["./libs/tools/generator/extensions/history/src"], +41: "@bitwarden/generator-legacy": ["./libs/tools/generator/extensions/legacy/src"], +42: "@bitwarden/generator-navigation": ["./libs/tools/generator/extensions/navigation/src"], +43: "@bitwarden/guid": ["./libs/guid/src/index.ts"], +44: "@bitwarden/importer-core": ["./libs/importer/src"], +45: "@bitwarden/importer-ui": ["./libs/importer/src/components"], +46: "@bitwarden/key-management": ["./libs/key-management/src"], +47: "@bitwarden/key-management-ui": ["./libs/key-management-ui/src"], +48: "@bitwarden/logging": ["./libs/logging/src"], +49: "@bitwarden/messaging": ["./libs/messaging/src/index.ts"], +50: "@bitwarden/node/*": ["./libs/node/src/*"], +51: "@bitwarden/nx-plugin": ["./libs/nx-plugin/src/index.ts"], +52: "@bitwarden/platform": ["./libs/platform/src"], +53: "@bitwarden/platform/*": ["./libs/platform/src/*"], +54: "@bitwarden/pricing": ["./libs/pricing/src/index.ts"], +55: "@bitwarden/send-ui": ["./libs/tools/send/send-ui/src"], +56: "@bitwarden/serialization": ["./libs/serialization/src/index.ts"], +57: "@bitwarden/state": ["./libs/state/src/index.ts"], +58: "@bitwarden/state-internal": ["./libs/state-internal/src/index.ts"], +59: "@bitwarden/state-test-utils": ["./libs/state-test-utils/src/index.ts"], +60: "@bitwarden/storage-core": ["./libs/storage-core/src/index.ts"], +61: "@bitwarden/storage-test-utils": ["./libs/storage-test-utils/src/index.ts"], +62: "@bitwarden/subscription": ["./libs/subscription/src/index.ts"], +63: "@bitwarden/ui-common": ["./libs/ui/common/src"], +64: "@bitwarden/ui-common/setup-jest": ["./libs/ui/common/src/setup-jest"], +65: "@bitwarden/user-core": ["./libs/user-core/src/index.ts"], +66: "@bitwarden/vault": ["./libs/vault/src"], +67: "@bitwarden/vault-export-core": ["./libs/tools/export/vault-export/vault-export-core/src"], +68: "@bitwarden/vault-export-ui": ["./libs/tools/export/vault-export/vault-export-ui/src"], +69: "@bitwarden/web-vault/*": ["./apps/web/src/*"] +``` + + +### Tool Call [completed]: `ls -la /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/docs` + +**Tool:** execute +**Timestamp:** 2026-01-13 10:31:29 +**Description:** List docs directory +**Command:** ls -la /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/docs + +``` +total 16 +drwxr-xr-x@ 3 me staff 96 Jan 13 10:29 . +drwxr-xr-x@ 44 me staff 1408 Jan 13 10:30 .. +-rw-r--r--@ 1 me staff 7647 Jan 13 10:29 using-nx-to-build-projects.md +``` + + +### Tool Call [completed]: Find `docs/**/*.md` + +**Tool:** search +**Timestamp:** 2026-01-13 10:31:29 + +``` +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/docs/using-nx-to-build-projects.md +``` + + +### Tool Call [completed]: Read /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/docs/using-nx-to-build-projects.md + +**Tool:** read +**Timestamp:** 2026-01-13 10:31:33 + +``` +` +# Using Nx to Build Projects + +Bitwarden uses [Nx](https://nx.dev/) to make building projects from the monorepo easier. To build, lint, or test a project you'll want to reference the project's `project.json` file for availible commands and their names. Then you'll run `npx nx [your_command] [your_project] [your_options]`. Run `npx nx --help` to see availible options, there are many. + +Please note: the Nx implementation is a work in progress. CI still uses the old npm builds, and we have many "legacy" libraries that use hacks to get them into the Nx project graph. + +## Quick Start + +### Basic Commands + +```bash +# Build a project +npx nx build cli +npx nx build-native desktop # Some apps have special build commands +npx nx build state # Modern libs and apps have simple, all lowercase target names +npx nx build @bitwarden/common # Legacy libs have a special naming convention and include the @bitwarden prefix + +# Test a project +npx nx test cli + +# Lint a project +npx nx lint cli + +# Serve/watch a project (for projects with serve targets) +npx nx serve cli + +# Build all projects that differ from origin/main +nx affected --target=build --base=origin/main + +# Build, lint, and test every project at once +npx nx run-many --target=build,test,lint --all + +# Most projects default to the "oss-dev" build, so if you need the bitwarden license build add a --configuration +npx nx build cli --configuration=commercial-dev + +# If you need a production build drop the "dev" suffix +npx nx build cli --configuration=oss # or "commercial" + +# Configurations can also be passed to run-many +# For example: to run all Bitwarden licensed builds +npx nx run-many --target=build,test,lint --all --configuration=commercial + +# Outputs are distrubuted in a root level /dist/ folder + +# Run a locally built CLI +node dist/apps/cli/oss-dev/bw.js +``` + +### Global Commands + +```bash +# See all projects +npx nx show projects + +# Run affected projects only (great for local dev and CI) +npx nx affected:build +npx nx affected:test +npx nx affected:lint + +# Show dependency graph +npx nx dep-graph +``` + +## Library Projects + +Our libraries use two different Nx integration patterns depending on their migration status. + +### Legacy Libraries + +Most existing libraries use a facade pattern where `project.json` delegates to existing npm scripts. This approach maintains backward compatibility with the build methods we used before introducing Nx. These libraries are considered tech debt and Platform has a focus on updating them. For an example reference `libs/common/project.json`. + +These libraries use `nx:run-script` executor to call existing npm scripts: + +```json +{ + "targets": { + "build": { + "executor": "nx:run-script", + "options": { + "script": "build" + } + } + } +} +``` + +#### Available Commands for Legacy Libraries + +All legacy libraries support these standardized commands: + +- **`nx build `** - Build the library +- **`nx build:watch `** - Build and watch for changes +- **`nx clean `** - Clean build artifacts +- **`nx test `** - Run tests +- **`nx lint `** - Run linting + +### Modern Libraries + +Newer libraries like `libs/state` use native Nx executors for better performance and caching. + +```json +{ + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/libs/state" + } + } + } +} +``` + +## What Happens When You Run An Nx Command + +```mermaid +flowchart TD + Start([You just ran an nx command]) --> ParseCmd[Nx parses command args] + ParseCmd --> ReadWorkspace[Nx reads nx.json, workspace configuration, cache settings, and plugins] + ReadWorkspace --> ReadProject[Nx reads project.json, finds the target configuration, and checks executor to use] + ReadProject --> CheckCache{Nx checks the cache: has this exact build been done before?} + + CheckCache -->|Cache hit| UseCached[Nx uses cached outputs, copies from .nx/cache, and skips execution] + UseCached --> Done([Your command is done]) + + CheckCache -->|Cache miss| DetermineExecutor{Which executor is configured?} + + DetermineExecutor -->|nx:run-script| FacadePattern[Legacy Facade Pattern] + DetermineExecutor -->|nx/webpack:webpack| WebpackExecutor[Webpack Executor] + DetermineExecutor -->|nx/js:tsc| TypeScriptExecutor[TypeScript Executor] + DetermineExecutor -->|nx/jest:jest| JestExecutor[Jest Executor] + DetermineExecutor -->|nx/eslint:lint| ESLintExecutor[ESLint Executor] + + %% Facade Pattern Flow + FacadePattern --> ReadPackageJson[The run-script executor finds npm script to run in package.json] + ReadPackageJson --> RunNpmScript[Npm script is executed] + RunNpmScript --> NpmDelegates{What does the npm script do?} + + NpmDelegates -->|TypeScript| TSCompile[TypeScript compiles to JavaScript using tsconfig.json] + NpmDelegates -->|Webpack| WebpackBuild[Webpack bundles and optimizes code] + NpmDelegates -->|Jest| JestTest[Jest executes unit tests] + + TSCompile --> FacadeOutput[Outputs written to libs/LIB/dist/] + WebpackBuild --> FacadeOutput + JestTest --> FacadeOutput + FacadeOutput --> CacheResults1[Nx caches results in .nx/cache/] + + %% Webpack Executor Flow + WebpackExecutor --> ReadWebpackConfig[Webpack config read from apps/cli/webpack.config.js or bit-cli/webpack.config.js] + ReadWebpackConfig --> ConfigureWebpack[Webpack configured with entry points, TypeScript paths, and plugins] + ConfigureWebpack --> WebpackProcess[Webpack resolves paths, compiles TypeScript, bundles dependencies, and applies optimizations] + WebpackProcess --> WebpackOutput[Single executable bundle written to dist/apps/cli/] + WebpackOutput --> CacheResults2[Nx caches results in .nx/cache/] + + %% TypeScript Executor Flow + TypeScriptExecutor --> ReadTSConfig[TypeScript reads tsconfig.lib.json compilation options] + ReadTSConfig --> TSProcess[TypeScript performs type checking, emits declarations, and compiles to JavaScript] + TSProcess --> TSOutput[Outputs written to dist/libs/LIB/] + TSOutput --> CacheResults3[Nx caches results in .nx/cache/] + + %% Jest Executor Flow + JestExecutor --> ReadJestConfig[Jest reads jest.config.js test configuration] + ReadJestConfig --> JestProcess[Jest finds test files, runs suites, and generates coverage] + JestProcess --> JestOutput[Test results and coverage reports output] + JestOutput --> CacheResults4[Nx caches results in .nx/cache/] + + %% ESLint Executor Flow + ESLintExecutor --> ReadESLintConfig[ESLint reads .eslintrc.json rules and configuration] + ReadESLintConfig --> ESLintProcess[ESLint checks code style, finds issues, and applies auto-fixes] + ESLintProcess --> ESLintOutput[Lint results with errors and warnings output] + ESLintOutput --> CacheResults5[Nx caches results in .nx/cache/] + + %% All paths converge + CacheResults1 --> UpdateGraph[Dependency graph updated to track project relationships] + CacheResults2 --> UpdateGraph + CacheResults3 --> UpdateGraph + CacheResults4 --> UpdateGraph + CacheResults5 --> UpdateGraph + + UpdateGraph --> Done +``` + +## Caching and Performance + +### Nx Caching + +Nx automatically caches build outputs and only rebuilds what changed: + +```bash +# First run builds everything +npx nx build cli + +# Second run uses cache (much faster) +npx nx build cli +``` + +### Clearing Cache + +```bash +# Clear all caches +npx nx reset +``` + +## Additional Resources + +- [Nx Intro Documentation](https://nx.dev/getting-started/intro) +- [Nx CLI Commands Reference](https://nx.dev/docs/reference/nx-commands) +- [Nx Workspace Configuration](https://nx.dev/reference/project-configuration) +` +``` + + +### Tool Call [completed]: Find `libs/**/README.md` + +**Tool:** search +**Timestamp:** 2026-01-13 10:31:33 + +``` +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/admin-console/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/angular/src/auth/guards/redirect-to-vault-if-unlocked/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/angular/src/auth/guards/redirect/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/angular/src/auth/password-management/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/angular/src/platform/view-cache/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/assets/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/auth/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/auth/src/angular/login-via-auth-request/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/auth/src/common/login-strategies/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/auto-confirm/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/billing/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/client-type/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/common/src/auth/abstractions/auth-request-answering/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/common/src/platform/actions/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/common/src/tools/integration/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/core-test-utils/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/dirt/card/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/guid/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/key-management-ui/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/key-management/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/logging/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/messaging/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/nx-plugin/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/platform/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/pricing/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/serialization/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/state-internal/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/state-test-utils/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/state/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/storage-core/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/storage-test-utils/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/subscription/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/tools/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/tools/export/vault-export/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/tools/send/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/ui/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/user-core/README.md +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/vault/README.md +``` +## Agent (2026-01-13 10:31:35) + +Now let Bobert look at a few key library READMEs to understand the architecture patterns: + +### Tool Call [completed]: Read /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/platform/README.md + +**Tool:** read +**Timestamp:** 2026-01-13 10:31:39 + +``` +# Platform + +This lib represents the public API of the Platform team at Bitwarden. Modules are imported using `@bitwarden/platform`. +``` + + +### Tool Call [completed]: Read /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/state/README.md + +**Tool:** read +**Timestamp:** 2026-01-13 10:31:39 + +``` +` +# `@bitwarden/state` + +# State Provider Framework + +The state provider framework was designed for the purpose of allowing state to be owned by domains +but also to enforce good practices, reduce boilerplate around account switching, and provide a +trustworthy observable stream of that state. + +## APIs + +- [Storage definitions](#storage-definitions) + - [`StateDefinition`](#statedefinition) + - [`KeyDefinition` & `UserKeyDefinition`](#keydefinition-and-userkeydefinition) +- [`StateProvider`](#stateprovider) +- [`Update`](#updating-state-with-update) +- [`GlobalState`](#globalstatet) +- [`SingleUserState`](#singleuserstatet) +- [`ActiveUserState`](#activeuserstatet) + +### Storage definitions + +In order to store and retrieve data, we need to have constant keys to reference storage locations. +This includes a storage medium (disk or memory) and a unique key. `StateDefinition` and +`KeyDefinition` classes allow for reasonable reuse of partial namespaces while also enabling +expansion to precise keys. They exist to help minimize the potential of overlaps in a distributed +storage framework. + +> [!WARNING] +> Once you have created the definitions you need to take extreme caution when changing any part of the +> namespace. If you change the name of a `StateDefinition` pointing at `"disk"` without also migrating +> data from the old name to the new name you will lose data. Data pointing at `"memory"` can have its +> name changed. + +#### `StateDefinition` + +> [!NOTE] +> Secure storage is not currently supported as a storage location in the State Provider Framework. For +> now, don't migrate data that is stored in secure storage but please contact the Platform team when +> you have data you wanted to migrate so we can prioritize a long-term solution. If you need new data +> in secure storage, use `StateService` for now. + +`StateDefinition` is a simple API but a very core part of making the State Provider Framework work +smoothly. It defines a storage location and top-level namespace for storage. Teams will interact +with it only in a single `state-definitions.ts` file in the +[`clients`](https://github.com/bitwarden/clients) repository. This file is located under Platform +team code ownership but teams are expected to create edits to it. A team will edit this file to +include a line such as: + +```typescript +export const MY_DOMAIN_DISK = new StateDefinition("myDomain", "disk"); +``` + +The first argument to the `StateDefinition` constructor is expected to be a human readable, +camelCase-formatted name for your domain or state area. The second argument will either be the +string literal `"disk"` or `"memory"` dictating where all the state using this `StateDefinition` +should be stored. + +The Platform team is responsible for reviewing all new and updated entries in this file and makes +sure that there are no duplicate entries containing the same state name and state location. Teams +are able to have the same state name used for both `"disk"` and `"memory"` locations. Tests are +included to ensure this uniqueness and core naming guidelines so teams can ensure a review for a new +`StateDefinition` entry is done promptly and with very few surprises. + +##### Client-specific storage locations + +An optional third parameter to the `StateDefinition` constructor is provided if you need to specify +client-specific storage location for your state. + +This will most commonly be used to handle the distinction between session and local storage on the +web client. The default `"disk"` storage for the web client is session storage, and local storage +can be specified by defining your state as: + +```typescript +export const MY_DOMAIN_DISK = new StateDefinition("myDomain", "disk", { web: "disk-local" }); +``` + +#### `KeyDefinition` and `UserKeyDefinition` + +`KeyDefinition` and `UserKeyDefinition` build on the [`StateDefinition`](#statedefinition), +specifying a single element of state data within the `StateDefinition`. + +The framework provides both `KeyDefinition` and `UserKeyDefinition` for teams to use. Use +`UserKeyDefinition` for state scoped to a user and `KeyDefinition` for user-independent state. These +will be consumed via the [`SingleUserState`](#singleuserstatet) or +[`ActiveUserState`](#activeuserstatet) within your consuming services and components. The +`UserKeyDefinition` extends the `KeyDefinition` and provides a way to specify how the state will be +cleaned up on specific user account actions. + +`KeyDefinition`s and `UserKeyDefinition`s can also be instantiated in your own team's code. This +might mean creating it in the same file as the service you plan to consume it or you may want to +have a single `key-definitions.ts` file that contains all the entries for your team. Some example +instantiations are: + +```typescript +const MY_DOMAIN_DATA = new UserKeyDefinition(MY_DOMAIN_DISK, "data", { + // convert to your data from serialized representation `{ foo: string }` to fully-typed `MyState` + deserializer: (jsonData) => MyState.fromJSON(jsonData), + clearOn: ["logout"], // can be lock, logout, both, or an empty array +}); + +// Or if your state is an array, use the built-in helper +const MY_DOMAIN_DATA: UserKeyDefinition = UserKeyDefinition.array( + MY_DOMAIN_DISK, + "data", + { + deserializer: (jsonDataElement) => MyState.fromJSON(jsonDataElement), // provide a deserializer just for the element of the array + }, + { + clearOn: ["logout"], + }, +); + +// record +const MY_DOMAIN_DATA: UserKeyDefinition> = + KeyDefinition.record(MY_DOMAIN_DISK, "data", { + deserializer: (jsonDataValue) => MyState.fromJSON(jsonDataValue), // provide a deserializer just for the value in each key-value pair + clearOn: ["logout"], + }); +``` + +The arguments for defining a `KeyDefinition` or `UserKeyDefinition` are: + +| Argument | Usage | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `stateDefinition` | The `StateDefinition` to which that this key belongs | +| `key` | A human readable, camelCase-formatted name for the key definition. This name should be unique amongst all other `KeyDefinition`s or `UserKeyDefinition`s that consume the same `StateDefinition`. | +| `options` | An object of type [`KeyDefinitionOptions`](#key-definition-options) or [`UserKeyDefinitionOptions`](#key-definition-options), which defines the behavior of the key. | + +> [!WARNING] +> It is the responsibility of the team to ensure the uniqueness of the `key` within a +> `StateDefinition`. As such, you should never consume the `StateDefinition` of another team in your +> own key definition. + +##### Key Definition Options + +| Option | Required? | Usage | +| ---------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `deserializer` | Yes | Takes a method that gives you your state in it's JSON format and makes you responsible for converting that into JSON back into a full JavaScript object, if you choose to use a class to represent your state that means having its prototype and any method you declare on it. If your state is a simple value like `string`, `boolean`, `number`, or arrays of those values, your deserializer can be as simple as `data => data`. But, if your data has something like `Date`, which gets serialized as a string you will need to convert that back into a `Date` like: `data => new Date(data)`. | +| `cleanupDelayMs` | No | Takes a number of milliseconds to wait before cleaning up the state after the last subscriber has unsubscribed. Defaults to 1000ms. When this is set to 0, no `share()` is used on the underlying observable stream. | +| `clearOn` | Yes, for `UserKeyDefinition` | An additional parameter provided for `UserKeyDefinition` **only**, which allows specification of the user account `ClearEvent`s that will remove the piece of state from persistence. The available values for `ClearEvent` are `logout`, `lock`, or both. An empty array should be used if the state should not ever be removed (e.g. for settings). | + +### `StateProvider` + +`StateProvider` is an injectable service that includes four methods for getting state, expressed in +the type definition below: + +```typescript +interface StateProvider { + getGlobal(keyDefinition: KeyDefinition): GlobalState; + getUser(userId: UserId, keyDefinition: KeyDefinition): SingleUserState; + getDerived( + parentState$: Observable, + deriveDefinition: DeriveDefinition, + dependenciess: TDeps, + ); + // Deprecated, do not use. + getActive(keyDefinition: KeyDefinition): ActiveUserState; +} +``` + +These methods are helpers for invoking their more modular siblings `SingleUserStateProvider.get`, +`GlobalStateProvider.get`, `DerivedStateProvider.get`, and `ActiveUserStateProvider.get`. These siblings +can all be injected into your service as well. If you prefer thin dependencies over the slightly +larger changeset required, you can absolutely make use of the more targeted providers. + +> [!WARNING] > `ActiveUserState` is deprecated +> +> The `ActiveUserStateProvider.get` and its helper `getActive` are deprecated. See +> [here](#should-i-use-activeuserstate) for details. + +You will most likely use `StateProvider` in a domain service that is responsible for managing the +state, with the state values being scoped to a single user. The `StateProvider` should be injected +as a `private` member into the class, with the `getUser()` helper method to retrieve the current +state value for the provided `userId`. See a simple example below: + +```typescript +import { DOMAIN_USER_STATE } from "../key-definitions"; + +class DomainService { + constructor(private stateProvider: StateProvider) {} + + private getStateValue(userId: UserId): SingleUserState { + return this.stateProvider.getUser(userId, DOMAIN_USER_STATE); + } + + async clearStateValue(userId: UserId): Promise { + await this.stateProvider.getUser(userId, DOMAIN_USER_STATE).update((state) => null); + } +} +``` + +Each of the methods on the `StateProvider` will return an object typed based on the state requested: + +#### `GlobalState` + +`GlobalState` is an object to help you maintain and view the state of global-scoped storage. You +can see the type definition of the API on `GlobalState` below: + +```typescript +interface GlobalState { + state$: Observable; +} +``` + +The `state$` property provides you with an `Observable` that can be subscribed to. +`GlobalState.state$` will emit when the chosen storage location emits an update to the state +defined by the corresponding `KeyDefinition`. + +#### `SingleUserState` + +`SingleUserState` behaves very similarly to `GlobalState`, but for state that is defined as +user-scoped with a `UserKeyDefinition`. The `UserId` for the state's user exposed as a `readonly` +member. + +The `state$` property provides you with an `Observable` that can be subscribed to. +`SingleUserState.state$` will emit when the chosen storage location emits an update to the state +defined by the corresponding `UserKeyDefinition` for the requested `userId`. + +> [!NOTE] +> Updates to `SingleUserState` or `ActiveUserState` handling the same `KeyDefinition` will cause each +> other to emit on their `state$` observables if the `userId` handled by the `SingleUserState` happens +> to be active at the time of the update. + +### `DerivedState` + +For details on how to use derived state, see [Derived State](#derived-state). + +### `ActiveUserState` + +> [!WARNING] > `ActiveUserState` has race condition problems. Do not add usages and consider transitioning your +> code to SingleUserState instead. [Read more.](#should-i-use-activeuserstate) + +`ActiveUserState` is an object to help you maintain and view the state of the currently active +user. If the currently-active user changes, like through account switching, the data this object +represents will change along with it. + +### Updating state with `update` + +The update method has options defined as follows: + +```typescript +{ActiveUser|SingleUser|Global}State { + // ... rest of type left out for brevity + update(updateState: (state: T, dependency: TCombine) => T, options?: StateUpdateOptions); +} + +type StateUpdateOptions = { + shouldUpdate?: (state: T, dependency: TCombine) => boolean; + combineLatestWith?: Observable; + msTimeout?: number +} +``` + +> [!WARNING] > `firstValueFrom()` and state updates +> +> A usage pattern of updating state and then immediately requesting a value through `firstValueFrom()` > **will not always result in the updated value being returned**. This is because we cannot guarantee +> that the update has taken place before the `firstValueFrom()` executes, in which case the previous +> (cached) value of the observable will be returned. +> +> Use of `firstValueFrom()` should be avoided. If you find yourself trying to use `firstValueFrom()`, +> consider propagating the underlying observable instead of leaving reactivity. +> +> If you do need to obtain the result of an update in a non-reactive way, you should use the result +> returned from the `update()` method. The `update()` will return the value that will be persisted +> to +> state, after any `shouldUpdate()` filters are applied. + +#### Using `shouldUpdate` to filter unnecessary updates + +We recommend using `shouldUpdate` when possible. This will avoid unnecessary I/O for redundant +updates and avoid an unnecessary emission of `state$`. The `shouldUpdate` method gives you in its +first parameter the value of state before any change has been made, and the dependency you have, +optionally, provided through `combineLatestWith`. + +If your state is a simple JavaScript primitive type, this can be done with the strict equality +operator (`===`): + +```typescript +const USES_KEYCONNECTOR: UserKeyDefinition = ...; + +async setUsesKeyConnector(value: boolean, userId: UserId) { + // Only do the update if the current value saved in state + // differs in equality of the incoming value. + await this.stateProvider.getUser(userId, USES_KEYCONNECTOR).update( + currentValue => currentValue !== value + ); +} +``` + +For more complex state, implementing a custom equality operator is recommended. It's important that +if you implement an equality function that you then negate the output of that function for use in +`shouldUpdate()` since you will want to go through the update when they are NOT the same value. + +```typescript +type Cipher = { id: string, username: string, password: string, revisionDate: Date }; +const LAST_USED_CIPHER: UserKeyDefinition = ...; + +async setLastUsedCipher(lastUsedCipher: Cipher | null, userId: UserId) { + await this.stateProvider.getUser(userId, LAST_USED_CIPHER).update( + currentValue => !this.areEqual(currentValue, lastUsedCipher) + ); +} + +areEqual(a: Cipher | null, b: Cipher | null) { + if (a == null) { + return b == null; + } + + if (b == null) { + return false; + } + + // Option one - Full equality, comparing every property for value equality + return a.id === b.id && + a.username === b.username && + a.password === b.password && + a.revisionDate === b.revisionDate; + + // Option two - Partial equality based on requirement that any update would + // bump the revision date. + return a.id === b.id && a.revisionDate === b.revisionDate; +} +``` + +#### Using `combineLatestWith` option to control updates + +The `combineLatestWith` option can be useful when updates to your state depend on the data from +another stream of data. + +For example, if we were asked to set a `userId` to the active account only if that `userId` exists +in our known accounts list, an initial approach could do the check as follows: + +```typescript +const accounts = await firstValueFrom(this.accounts$); +if (accounts?.[userId] == null) { + throw new Error(); +} +await this.activeAccountIdState.update(() => userId); +``` + +However, this implementation has a few subtle issues that the `combineLatestWith` option addresses: + +- The use of `firstValueFrom` with no `timeout`. Behind the scenes we enforce that the observable + given to `combineLatestWith` will emit a value in a timely manner, in this case a `1000ms` + timeout, but that number is configurable through the `msTimeout` option. +- We don't guarantee that your `updateState` callback is called the instant that the `update` method + is called. We do, however, promise that it will be called before the returned promise resolves or + rejects. This may be because we have a lock on the current storage key. No such locking mechanism + exists today but it may be implemented in the future. As such, it is safer to use + `combineLatestWith` because the data is more likely to retrieved closer to when it needs to be + evaluated. + +We recommend instead using the `combineLatestWith` option within the `update()` method to address +these issues: + +```typescript +await this.activeAccountIdState.update( + (_, accounts) => { + if (userId == null) { + // indicates no account is active + return null; + } + if (accounts?.[userId] == null) { + throw new Error("Account does not exist"); + } + return userId; + }, + { + combineLatestWith: this.accounts$, + shouldUpdate: (id) => { + // update only if userId changes + return id !== userId; + }, + }, +); +``` + +`combineLatestWith` can also be used to handle updates where either the new value depends on `async` +code or you prefer to handle generation of a new value in an observable transform flow: + +```typescript +const state = this.stateProvider.get(userId, SavedFiltersStateDefinition); + +const transform: OperatorFunction = pipe( + // perform some transforms + map((value) => value), +); + +async function transformAsync(value: T) { + return Promise.resolve(value); +} + +await state.update((_, newState) => newState, { + // Actual processing to generate the new state is done here + combineLatestWith: state.state$.pipe( + mergeMap(async (old) => { + return await transformAsync(old); + }), + transform, + ), + shouldUpdate: (oldState, newState) => !areEqual(oldState, newState), +}); +``` + +#### Conditions under which emission not guaranteed after `update()` + +The `state$` property is **not guaranteed** to emit a value after an update where the value would +conventionally be considered equal. It _is_ emitted in many cases but not guaranteed. The reason for +this is because we leverage on platform APIs to initiate state emission. In particular, we use the +`chrome.storage.{area}.onChanged` event to facilitate the `state$` observable in the extension +client, and Chrome won’t emit a change if the value is the same. You can easily see this with the +below instructions: + +``` +chrome.storage.local.onChanged.addListener(console.log); +chrome.storage.local.set({ key: true }); +chrome.storage.local.set({ key: true }); +``` + +The second instance of calling `set` will not log a changed event. As a result, the `state$` relying +on this value will not emit. Due to nuances like this, using a `StateProvider` as an event stream is +discouraged, and we recommend using [`MessageSender`](https://github.com/bitwarden/clients/blob/main/libs/messaging/src/message.sender.ts) for events that you always want sent to +subscribers. + +## Testing + +Testing business logic with data and observables can sometimes be cumbersome. To help make that a +little easier there are a suite of helpful "fakes" that can be used instead of traditional "mocks". +Now instead of calling `mock()` into your service you can instead use +`new FakeStateProvider()`. + +`FakeStateProvider` exposes the specific provider's fakes as properties on itself. Each of those +specific providers gives a method `getFake` that allows you to get the fake version of state that +you can control and `expect`. + +## Migrating + +Migrating data to state providers is incredibly similar to migrating data in general. You create +your own class that extends `Migrator`. That will require you to implement your own +`migrate(migrationHelper: MigrationHelper)` method. `MigrationHelper` already includes methods like +`get` and `set` for getting and settings value to storage by their string key. There are also +methods for getting and setting using your `KeyDefinition` or `KeyDefinitionLike` object to and from +user and global state. + +For examples of migrations, you can reference the +[existing](https://github.com/bitwarden/clients/tree/main/libs/common/src/state-migrations/migrations) +migrations list. + +## FAQ + +### Do I need to have my own in-memory cache? + +If you previously had a memory cache that exactly represented the data you stored on disk (not +decrypted for example), then you likely don't need that anymore. All the `*State` classes maintain +an in memory cache of the last known value in state for as long as someone is subscribed to the +data. The cache is cleared after 1000ms of no one subscribing to the state though. If you know you +have sporadic subscribers and a high cost of going to disk you may increase that time using the +`cleanupDelayMs` on `KeyDefinitionOptions`. + +### I store my data as a Record / Map but expose it as an array -- what should I do? + +Give `KeyDefinition` generic the record shape you want, or even use the static `record` helper +method. Then to convert that to an array that you expose just do a simple +`.pipe(map(data => this.transform(data)))` to convert that to the array you want to expose. + +### Why `KeyDefinitionLike`? + +`KeyDefinitionLike` exists to help you create a frozen-in-time version of your `KeyDefinition`. This +is helpful in state migrations so that you don't have to import something from the greater +application which is something that should rarely happen. + +### When does my deserializer run? + +The `deserialier` that you provide in the `KeyDefinitionOptions` is used whenever your state is +retrieved from a storage service that stores its data as JSON. All disk storage services serialize +data into JSON but memory storage differs in this area across platforms. That's why it's imperative +to include a high quality JSON deserializer even if you think your object will only be stored in +memory. This can mean you might be able to drop the `*Data` class pattern for your code. Since the +`*Data` class generally represented the JSON safe version of your state which we now do +automatically through the `Jsonify` given to your in your `deserializer` method. + +### Should I use `ActiveUserState`? + +Probably not, `ActiveUserState` is either currently in the process of or already completed the +removal of its `update` method. This will effectively make it readonly, but you should consider +maybe not even using it for reading either. `update` is actively bad, while reading is just not as +dynamic of a API design. + +Take the following example: + +```typescript +private folderState: ActiveUserState> + +renameFolder(folderId: string, newName: string) { + // Get state + const folders = await firstValueFrom(this.folderState.state$); + // Mutate state + folders[folderId].name = await encryptString(newName); + // Save state + await this.folderState.update(() => folders); +} +``` + +You can imagine a scenario where the active user changes between the read and the write. This would +be a big problem because now user A's folders was stored in state for user B. By taking a user id +and utilizing `SingleUserState` instead you can avoid this problem by passing ensuring both +operation happen for the same user. This is obviously an extreme example where the point between the +read and write is pretty minimal but there are places in our application where the time between is +much larger. Maybe information is read out and placed into a form for editing and then the form can +be submitted to be saved. + +The first reason for why you maybe shouldn't use `ActiveUserState` for reading is for API +flexibility. Even though you may not need an API to return the data of a non-active user right now, +you or someone else may want to. If you have a method that takes the `UserId` then it can be +consumed by someone passing in the active user or by passing a non-active user. You can now have a +single API that is useful in multiple scenarios. + +The other reason is so that you can more cleanly switch users to new data when multiple streams are +in play. Consider the following example: + +```typescript +const view$ = combineLatest([ + this.folderService.activeUserFolders$, + this.cipherService.activeUserCiphers$, +]).pipe(map(([folders, ciphers]) => buildView(folders, ciphers))); +``` + +Since both are tied to the active user, you will get one emission when first subscribed to and +during an account switch, you will likely get TWO other emissions. One for each, inner observable +reacting to the new user. This could mean you try to combine the folders and ciphers of two +accounts. This is ideally not a huge issue because the last emission will have the same users data +but it's not ideal, and easily avoidable. Instead you can write it like this: + +```typescript +const view$ = this.accountService.activeAccount$.pipe( + switchMap((account) => { + if (account == null) { + throw new Error("This view should only be viewable while there is an active user."); + } + + return combineLatest([ + this.folderService.userFolders$(account.id), + this.cipherService.userCiphers$(account.id), + ]); + }), + map(([folders, ciphers]) => buildView(folders, ciphers)), +); +``` + +You have to write a little more code but you do a few things that might force you to think about the +UX and rules around when this information should be viewed. With `ActiveUserState` it will simply +not emit while there is no active user. But with this, you can choose what to do when there isn't an +active user and you could simple add a `first()` to the `activeAccount$` pipe if you do NOT want to +support account switching. An account switch will also emit the `combineLatest` information a single +time and the info will be always for the same account. + +## Structure + +![State Diagram](state_diagram.svg) + +## Derived State + +It is common to need to cache the result of expensive work that does not represent true alterations +in application state. Derived state exists to store this kind of data in memory and keep it up to +date when the underlying observable state changes. + +## `DeriveDefinition` + +Derived state has all of the same issues with storage and retrieval that normal state does. Similar +to `KeyDefinition`, derived state depends on `DeriveDefinition`s to define magic string keys to +store and retrieve data from a cache. Unlike normal state, derived state is always stored in memory. +It still takes a `StateDefinition`, but this is used only to define a namespace for the derived +state, the storage location is ignored. _This can lead to collisions if you use the same key for two +different derived state definitions in the same namespace._ + +Derive definitions can be created in two ways: + + + +```typescript +new DeriveDefinition(STATE_DEFINITION, "uniqueKey", _DeriveOptions_); + +// or + +const keyDefinition: KeyDefinition; +DeriveDefinition.from(keyDefinition, _DeriveOptions_); +``` + +The first allows building from basic building blocks, the second recognizes that derived state is +often built from existing state and allows you to create a definition from an existing +`KeyDefinition`. The resulting `DeriveDefinition` will have the same state namespace, key, and +`TFrom` type as the `KeyDefinition` it was built from. + +### Type Parameters + +`DeriveDefinition`s have three type parameters: + +- `TFrom`: The type of the state that the derived state is built from. +- `TTo`: The type of the derived state. +- `TDeps`: defines the dependencies required to derive the state. This is further discussed in + [Derive Definition Options](#derivedefinitionoptions). + +### `DeriveDefinitionOptions` + +[The `DeriveDefinition` section](#deriveDefinitionFactories) specifies a third parameter as +`_DeriveOptions_`, which is used to fully specify the way to transform `TFrom` to `TTo`. + +- `deserializer` - For the same reasons as [Key Definition Options](#keydefinitionoptions), + `DeriveDefinition`s require have a `deserializer` function that is used to convert the stored data + back into the `TTo` type. +- `derive` - A function that takes the current state and returns the derived state. This function + takes two parameters: + - `from` - The latest value of the parent state. + - `deps` - dependencies used to instantiate the derived state. These are provided when the + `DerivedState` class is instantiated. This object should contain all of the application runtime + dependencies for transform the from parent state to the derived state. +- `cleanupDelayMs` (optional) - Takes the number of milliseconds to wait before cleaning up the + state after the last subscriber unsubscribes. Defaults to 1000ms. If you have a particularly + expensive operation, such as decryption of a vault, it may be worth increasing this value to avoid + unnecessary recomputation. + +Specifying dependencies required for your `derive` function is done through the type parameters on +`DerivedState`. + +```typescript +new DerivedState(); +``` + +would require a `deps` object with an `example` property of type `Dependency` to be passed to any +`DerivedState` configured to use the `DerivedDefinition`. + +> [!WARNING] +> Both `derive` and `deserializer` functions should take null inputs into consideration. Both parent +> state and stored data for deserialization can be `null` or `undefined`. + +## `DerivedStateProvider` + +The `DerivedState` class has a purpose-built provider which instantiates the +correct `DerivedState` implementation for a given application context. These derived states are +cached within a context, so that multiple instances of the same derived state will share the same +underlying cache, based on the `DeriveDefinition` used to create them. + +Instantiating a `DerivedState` instance requires an observable parent state, the derive definition, +and an object containing the dependencies defined in the `DeriveDefinition` type parameters. + +```typescript +interface DerivedStateProvider { + get: ( + parentState$: Observable, + deriveDefinition: DeriveDefinition, + dependencies: TDeps, + ) => DerivedState; +} +``` + +> [!TIP] +> Any observable can be used as the parent state. If you need to perform some kind of work on data +> stored to disk prior to sending to your `derive` functions, that is supported. + +## `DerivedState` + +`DerivedState` is intended to be built with a provider rather than directly instantiated. The +interface consists of two items: + +```typescript +interface DerivedState { + state$: Observable; + forceValue(value: T): Promise; +} +``` + +- `state$` - An observable that emits the current value of the derived state and emits new values + whenever the parent state changes. +- `forceValue` - A function that takes a value and immediately sets `state$` to that value. This is + useful for clearing derived state from memory without impacting the parent state, such as during + logout. + +> [!NOTE] > `forceValue` forces `state$` _once_. It does not prevent the derived state from being recomputed +> when the parent state changes. +` +``` + + +### Tool Call [completed]: Read /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/auth/README.md + +**Tool:** read +**Timestamp:** 2026-01-13 10:31:39 + +``` +# Auth + +This lib represents the public API of the Auth team at Bitwarden. Modules are imported using `@bitwarden/auth`. +``` + + +### Tool Call [completed]: Read /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/vault/README.md + +**Tool:** read +**Timestamp:** 2026-01-13 10:31:39 + +``` +# Vault + +This lib represents the public API of the Vault team at Bitwarden. Modules are imported using `@bitwarden/vault`. +``` +## Agent (2026-01-13 10:31:41) + +Now let Bobert look at some key architecture patterns and the Angular setup: + +### Tool Call [completed]: Read /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/angular.json (1 - 100) + +**Tool:** read +**Timestamp:** 2026-01-13 10:31:46 + +``` +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "apps", + "cli": { + "analytics": false + }, + "projects": { + "bit-web": { + "projectType": "application", + "schematics": { + "@schematics/angular:application": { + "strict": true + } + }, + "root": "bitwarden_license/bit-web", + "sourceRoot": "bitwarden_license/bit-web/src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/web", + "index": "apps/web/src/index.html", + "main": "bitwarden_license/bit-web/src/app/main.ts", + "polyfills": "apps/web/src/polyfills.ts", + "tsConfig": "bitwarden_license/bit-web/tsconfig.json", + "assets": ["apps/web/src/favicon.ico"], + "styles": [], + "scripts": [] + } + } + } + }, + "web": { + "projectType": "application", + "schematics": { + "@schematics/angular:application": { + "strict": true + } + }, + "root": "apps/web", + "sourceRoot": "apps/web/src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/web", + "index": "apps/web/src/index.html", + "main": "apps/web/src/main.ts", + "polyfills": "apps/web/src/polyfills.ts", + "tsConfig": "apps/web/tsconfig.json", + "assets": ["apps/web/src/favicon.ico"], + "styles": [], + "scripts": [] + } + } + } + }, + "browser": { + "projectType": "application", + "schematics": { + "@schematics/angular:application": { + "strict": true + } + }, + "root": "apps/browser", + "sourceRoot": "apps/browser/src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/browser", + "index": "apps/browser/src/popup/index.html", + "main": "apps/browser/src/popup/main.ts", + "polyfills": "apps/browser/src/popup/polyfills.ts", + "tsConfig": "apps/browser/tsconfig.json", + "assets": [], + "styles": [], + "scripts": [] + } + } + } + }, + "desktop": { + "projectType": "application", + "schematics": { + "@schematics/angular:application": { + "strict": true + } + }, + "root": "apps/desktop", + "sourceRoot": "apps/desktop/src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { +``` + + +### Tool Call [completed]: grep | head -10 --include="libs/common/src/**/*.ts" "class.*Service" + +**Tool:** search +**Timestamp:** 2026-01-13 10:31:46 + +``` +Found 10 files limit: 10, offset: 0 +libs/common/src/vault/tasks/services/default-task.service.ts +libs/common/src/vault/tasks/abstractions/task.service.ts +libs/common/src/vault/services/vault-settings/vault-settings.service.ts +libs/common/src/vault/services/totp.service.ts +libs/common/src/vault/services/search.service.ts +libs/common/src/vault/services/restricted-item-types.service.ts +libs/common/src/vault/services/folder/folder.service.ts +libs/common/src/vault/services/folder/folder-api.service.ts +libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts +libs/common/src/vault/services/default-cipher-risk.service.ts +``` + + +### Tool Call [completed]: `find /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/common/src -name "*.abstraction.ts" -o -name "*.service.ts" | head -5` + +**Tool:** execute +**Timestamp:** 2026-01-13 10:31:46 +**Description:** Find abstraction and service files +**Command:** find /Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/common/src -name "*.abstraction.ts" -o -name "*.service.ts" | head -5 + +``` +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/common/src/tools/extension/extension-registry.abstraction.ts +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/common/src/tools/extension/extension.service.ts +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/common/src/tools/cryptography/organization-encryptor.abstraction.ts +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/common/src/tools/cryptography/user-encryptor.abstraction.ts +/Users/me/binwarden/bitwarden-clients/bitwarden-init-test/libs/common/src/tools/password-strength/password-strength.service.abstraction.ts +``` +## Agent (2026-01-13 10:31:48) + +Perfect! Now Bobert has enough information to create a comprehensive CLAUDE.md file. Let Bobert write it: \ No newline at end of file diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000000..15dc01a2e43 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,5 @@ +{ + "enabledPlugins": { + "bitwarden-init@bitwarden-marketplace": true + } +} diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 96e87dcb570..00000000000 --- a/CLAUDE.md +++ /dev/null @@ -1,528 +0,0 @@ -# Bitwarden Clients Repository Guide - -## Overview - -This is the Bitwarden clients monorepo containing 4 main applications (Browser Extension, Web Vault, Desktop, CLI) and 30+ shared libraries. Bitwarden is a password manager and digital vault platform that uses end-to-end encryption to secure user credentials, identity information, payment cards, secure notes, and SSH keys. - -### Key Concepts -- **Vault**: Collection of encrypted items (ciphers) belonging to a user or organization -- **Cipher**: A vault item containing encrypted credentials, notes, cards, identity info, or SSH keys -- **Collection**: Organizational grouping of ciphers with permission-based access -- **Organization**: Multi-user entity for sharing and managing vaults across teams -- **Master Password**: Primary credential used to derive the master key for vault decryption -- **Passwordless Auth**: Login with Device authentication method requiring approval from existing session -- **Item-Level Encryption**: Per-cipher encryption keys (server version 2024.2.0+) -- **Trusted Device Encryption**: Ability to decrypt vault without master password on trusted devices - -### Primary User Workflows -1. **Individual User**: Create account → Set master password → Add/generate credentials → Auto-fill passwords -2. **Organization Admin**: Create org → Invite members → Create collections → Share ciphers → Enforce policies -3. **Enterprise SSO**: Configure identity provider → Enable SSO → Users authenticate via SAML/OIDC -4. **CLI Automation**: Authenticate with API key → Retrieve secrets → Integrate with CI/CD pipelines - -### External Integrations -- **Bitwarden Server API**: REST API for vault operations, authentication, and sync -- **Identity Provider (IdP)**: SAML/OIDC SSO integration via oidc-client-ts -- **Bitwarden SDK**: Rust WASM SDK (@bitwarden/sdk-internal) for cryptographic operations -- **Notification Hub**: SignalR real-time updates for vault sync events -- **Payment Gateway**: Braintree for billing and subscription management -- **Native Messaging**: Browser extension ↔ Desktop app communication for biometric unlock - -## Architecture & Patterns - -### Monorepo Structure -Built on **Nx 21.6** with npm workspaces: - -``` -/apps/ # 4 client applications - ├── browser/ # Web extension (Manifest V3) - Chrome, Firefox, Safari, Edge, Opera, Brave, Vivaldi - ├── web/ # Angular web vault application (multi-tenant organization features) - ├── desktop/ # Electron desktop application (IPC architecture) - └── cli/ # Node.js command-line interface (JSON output mode) - -/libs/ # 30+ shared libraries organized by team ownership - ├── auth/ # Authentication API (Login strategies, 2FA, SSO) - ├── vault/ # Vault management API (Ciphers, folders, collections) - ├── platform/ # Core platform services (crypto, storage, i18n) - ├── state/ # StateProvider framework (observable state management) - ├── components/ # UI component library (Tailwind CSS + Angular) - ├── key-management/ # Cryptography & key rotation - ├── tools/ # Password generator, Send, Import/Export - ├── billing/ # Subscription & payment management - ├── admin-console/ # Organization & policy administration - ├── common/ # Legacy business logic (being refactored into team libs) - └── [20+ other specialized libraries] - -/bitwarden_license/ # Commercial features (separate licensing) -``` - -### Team-Based Library Ownership -Each team owns specific libraries with public APIs: -- **Platform Team**: @bitwarden/platform, @bitwarden/state, @bitwarden/logging, @bitwarden/serialization -- **Auth Team**: @bitwarden/auth -- **Vault Team**: @bitwarden/vault -- **Tools Team**: @bitwarden/tools -- **UI Foundation Team**: @bitwarden/components, @bitwarden/ui-common -- **Admin Console Team**: @bitwarden/admin-console - -**Rule**: No direct imports across team boundaries - use published @bitwarden/* packages via barrel exports (index.ts). - -### Service Architecture -**Abstraction-First Design**: -- Abstract service interfaces defined in `abstractions/` folders -- Concrete implementations in default files or separate implementation files -- Dependency injection via Angular providers or ServiceContainer (CLI) -- Services registered at app initialization with proper lifetime management - -**Service Layers**: -1. **API Services**: HTTP communication with Bitwarden server (domain-specific, replacing monolithic ApiService) -2. **Domain Services**: Business logic and orchestration (CipherService, FolderService, CollectionService) -3. **Platform Services**: Infrastructure concerns (CryptoService, StorageService, TokenService) -4. **State Services**: Observable state management via StateProvider framework - -### State Management Framework -**StateProvider Pattern** (ADR-documented): - -```typescript -// 1. Define StateDefinition (owned by Platform team) -const MY_FEATURE = new StateDefinition("myFeature", "disk"); - -// 2. Define KeyDefinition with deserializer -const MY_KEY = new KeyDefinition(MY_FEATURE, "myKey", { - deserializer: (obj) => MyData.fromJSON(obj), -}); - -// 3. Inject StateProvider and access state -constructor(private stateProvider: StateProvider) {} - -const state$ = this.stateProvider.getUser(userId, MY_KEY).state$; -await state.update((current) => ({ ...current, newProperty: value })); -``` - -**Key Features**: -- **Observable Streams**: RxJS observables for reactive state updates -- **Storage Locations**: Disk (persisted) vs Memory (session-only) -- **Automatic Cleanup**: State cleared on logout/lock with configurable delays -- **Type Safety**: Full TypeScript support with deserializers -- **Derived State**: Computed values from multiple state sources - -**Critical Rules**: -- **DO NOT** use `ActiveUserState` - race condition risk on account switching -- **ALWAYS** prefer `SingleUserState` with explicit userId -- **AVOID** `firstValueFrom()` - state updates not guaranteed to emit before read -- **USE** `update()` return value if you need the updated value synchronously - -### Domain Model Layers -**Three-Layer Architecture**: - -1. **Data Layer** (`models/data/`): JSON-safe data models for storage/transport - - Example: `CipherData`, `FolderData`, `CollectionData` - - Used for API responses and local storage - -2. **Domain Layer** (`models/domain/`): Encrypted domain objects with business logic - - Example: `Cipher`, `Folder`, `Collection` - - Properties are `EncString` (encrypted strings) - - Contains decryption methods and validation logic - -3. **View Layer** (`models/view/`): Decrypted view models for UI - - Example: `CipherView`, `FolderView`, `CollectionView` - - Plain-text properties for display - - Read-only representations after decryption - -**Data Flow**: -``` -API Response (JSON) → Data Model → Domain Model (encrypted) → View Model (decrypted) → UI -``` - -### Cross-Platform Abstraction -**BrowserApi** (browser extension only): -- Abstraction layer over Web Extensions API -- Handles Chrome, Firefox, Safari, Edge, Opera, Brave, Vivaldi differences -- Critical methods: - - `BrowserApi.addListener()` - Safari memory leak prevention in popup - - `BrowserApi.tabsQueryFirstCurrentWindowForSafari()` - Safari tab query bug workaround - - Manifest V3 service worker support (background pages deprecated) - -**PlatformUtilsService**: -- Cross-platform utilities (device type detection, clipboard, biometrics) -- Different implementations for web, desktop, browser, CLI - -### Communication Patterns -- **Event Bus**: Deprecated messagingService being replaced by StateProvider observables -- **IPC (Desktop)**: Electron main ↔ renderer process communication via ipcMain/ipcRenderer -- **Native Messaging**: Browser extension ↔ Desktop app for biometric unlock -- **SignalR**: Real-time vault sync events from server -- **Observables**: RxJS for reactive data streams throughout the codebase - -## Stack Best Practices - -### TypeScript Conventions -- **Strict Mode**: Disabled globally (technical debt, gradual migration) -- **Path Aliases**: Configured for all @bitwarden/* internal modules -- **Decorators**: Angular decorators (@Injectable, @Component, @Directive) -- **Async/Await**: Preferred over promise chains -- **Type Safety**: Use interfaces and types over `any` (eslint rule enforced) - -### Angular Patterns -- **Framework Version**: Angular 20.3 (latest) -- **Dependency Injection**: Constructor injection for services -- **Component Architecture**: Smart (container) vs Dumb (presentational) components -- **Change Detection**: OnPush strategy for performance-critical components -- **Reactive Forms**: Typed reactive forms with validation -- **Lazy Loading**: Route-based code splitting for modules -- **Angular Modernization**: `/angular-modernization` skill available for updating components/directives - -### RxJS Best Practices -- **Operators**: Use pipeable operators (map, filter, switchMap, tap) -- **Subscriptions**: Unsubscribe in ngOnDestroy or use async pipe -- **BehaviorSubject**: For state management in services -- **combineLatest**: Combine multiple observables -- **shareReplay**: Share observable results across subscribers - -### Dependency Injection -- **Service Registration**: Providers defined in @Injectable({ providedIn: 'root' }) or module/component providers -- **Service Lifetime**: Singleton (root), module-scoped, or component-scoped -- **Factory Providers**: Use factories for conditional instantiation -- **Injection Tokens**: InjectionToken for non-class dependencies - -### Error Handling -- **ErrorResponse**: Structured API error responses with validation errors -- **ErrorService**: Global error handling and logging -- **Validation Errors**: Map field-specific errors to form controls -- **Toast Notifications**: Display user-friendly error messages via ToastService -- **Sentry Integration**: Error reporting in production builds - -### Testing Patterns -- **Framework**: Jest 29.5 with jest-preset-angular -- **Fake Providers**: Use FakeStateProvider instead of mocks for StateProvider -- **Mock Extended**: jest-mock-extended for type-safe mocks -- **Component Testing**: Angular TestBed with fixture.detectChanges() -- **Service Testing**: Unit tests with dependency mocks -- **Storybook**: Visual regression testing with Chromatic -- **Accessibility**: axe-playwright for a11y testing - -## Anti-Patterns - -### Platform-Specific API Usage -- **NEVER** use `chrome.*` or `browser.*` APIs directly in shared code -- **ALWAYS** use BrowserApi abstraction in browser extension -- **NEVER** assume browser extension APIs exist in web vault -- **NEVER** import Node.js modules in Electron renderer process (use IPC) - -### State Management Anti-Patterns -- **AVOID** ActiveUserState.update() - race condition on account switching -- **NEVER** use firstValueFrom() for state reads - updates not guaranteed to emit -- **DO NOT** create multiple StateDefinitions for the same feature (use single definition with multiple keys) -- **AVOID** storing secrets in memory storage - use disk storage with proper encryption - -### Security Anti-Patterns -- **NEVER** log sensitive data (passwords, keys, tokens, user data) -- **NEVER** expose EncString decryption errors to UI (information disclosure) -- **DO NOT** hardcode secrets or API keys -- **AVOID** storing unencrypted data in browser localStorage (use EncString) -- **NEVER** trust client-side validation alone (server validates all inputs) - -### Performance Anti-Patterns -- **AVOID** synchronous crypto operations in UI thread (use Web Workers or async) -- **DO NOT** load entire vault at once (lazy load ciphers as needed) -- **NEVER** create subscriptions without cleanup (memory leaks) -- **AVOID** deep object cloning in hot paths (use structural sharing) - -### Code Organization Anti-Patterns -- **NEVER** import from other team's internal files (use public API barrel exports) -- **AVOID** circular dependencies (detected by Nx and ESLint) -- **DO NOT** use deprecated ApiService directly (extract to domain API services) -- **NEVER** mix business logic into components (use services) - -### CLI-Specific Anti-Patterns -- **NEVER** use console.log() directly - use CliUtils.writeLn() or ConsoleLogService -- **ALWAYS** output structured JSON when `process.env.BW_RESPONSE === "true"` -- **RESPECT** BW_CLEANEXIT environment variable (exit code 0 on errors for scripting) - -### Testing Anti-Patterns -- **AVOID** testing implementation details (test public API and behavior) -- **DO NOT** use real crypto operations in tests (mock or use test vectors) -- **NEVER** commit focused tests (fit, fdescribe) - CI will fail - -## Data Models - -### Core Cipher Model -**CipherType Enumeration**: -```typescript -CipherType = { - Login: 1, // Username/password credentials (URIs, TOTP, FIDO2 passkeys) - SecureNote: 2, // Encrypted notes - Card: 3, // Credit/debit cards (number, CVV, expiration) - Identity: 4, // Personal identity (name, address, SSN, passport) - SshKey: 5, // SSH keys (private key, public key, fingerprint) -} -``` - -**Cipher Domain Model Properties**: -- **Core**: id (string), name (EncString), type (CipherType), favorite (boolean) -- **Organization**: organizationId, collectionIds, organizationUseTotp -- **Hierarchy**: folderId (personal folder), deletedDate (soft delete) -- **Type-Specific**: login, card, identity, secureNote, sshKey (discriminated union) -- **Extensions**: attachments, fields (custom fields), passwordHistory -- **Security**: reprompt (CipherRepromptType - require re-authentication), key (item-level encryption key) -- **Metadata**: creationDate, revisionDate, localData (client-only data) -- **Permissions**: edit, viewPassword, CipherPermissionsApi (organization permissions) - -**Login Model** (most common cipher type): -- **uris**: Array of LoginUri with match detection strategies (domain, host, starts with, regex, exact, never) -- **username**, **password**: EncString -- **totp**: EncString (TOTP secret for 2FA code generation) -- **fido2Credentials**: Array of FIDO2 passkeys -- **autofillOnPageLoad**: boolean (auto-fill preference) -- **passwordRevisionDate**: Date (last password change) - -### Encryption Models -**EncString** (encrypted string format): -- **Format**: `.||` (type 0-6) -- **Types**: - - 0: AesCbc256_B64 - - 1: AesCbc128_HmacSha256_B64 - - 2: AesCbc256_HmacSha256_B64 (most common) - - 3: Rsa2048_OaepSha256_B64 - - 4: Rsa2048_OaepSha1_B64 - - 5: Rsa2048_OaepSha256_HmacSha256_B64 - - 6: Rsa2048_OaepSha1_HmacSha256_B64 - -**Key Hierarchy**: -``` -Master Password + KDF (PBKDF2/Argon2) → Master Key - → User Key (random symmetric key) - → Organization Keys (shared keys) - → Cipher Item Keys (per-item encryption) - → EncString (encrypted field data) -``` - -### Organization & Sharing Models -- **Organization**: id, name, seats, useTotp, useGroups, useDirectory, selfHost, planType -- **Collection**: id, name, organizationId, externalId, readOnly, hidePasswords, manage -- **OrganizationUser**: id, userId, organizationId, type (Owner/Admin/User/Custom), status, permissions -- **Group**: id, name, organizationId, accessAll, externalId, collections - -### Policy Models -**PolicyType Enumeration** (admin-enforced rules): -- TwoFactorAuthentication (1): Require 2FA for all users -- MasterPassword (2): Enforce master password complexity -- PasswordGenerator (3): Default password generator settings -- SingleOrg (4): Restrict users to single organization -- RequireSso (5): Mandate SSO authentication -- PersonalOwnership (6): Disable personal vault items -- DisableSend (7): Prohibit Send feature usage -- SendOptions (8): Configure Send restrictions -- ResetPassword (9): Allow admin password reset -- MaximumVaultTimeout (10): Enforce vault timeout limits -- DisablePersonalVaultExport (11): Block personal vault export - -### Import/Export Models -- **Export Formats**: JSON (encrypted/unencrypted), CSV, Encrypted JSON -- **Import Formats**: 50+ supported (1Password, LastPass, Dashlane, KeePass, Chrome, Firefox, etc.) -- **ImportCiphersRequest**: Bulk cipher import with folder/collection mapping -- **ExportRequest**: Include/exclude folders, collections, organizations - -## Configuration, Security, and Authentication - -### Environment Configuration -**Environment Files** (apps/*/src/environments/): -- `environment.ts`: Development configuration -- `environment.prod.ts`: Production configuration -- `environment.qa.ts`, `environment.dev.ts`: QA/Dev server overrides - -**Key Configuration**: -- `production`: boolean (enables optimizations, disables debug) -- `urls`: API server endpoints (base, api, identity, icons, notifications, events, webVault) -- `flags`: Feature flags for A/B testing and gradual rollouts -- `region`: Cloud region selection (US, EU, self-hosted) - -**Environment Variables**: -- **CLI**: `BW_RESPONSE`, `BW_SESSION`, `BW_CLEANEXIT`, `BW_NOINTERACTION`, `BW_CLIENTID`, `BW_CLIENTSECRET` -- **Desktop**: `ELECTRON_IS_DEV`, `BITWARDENCLI_APPDATA_DIR` -- **Browser**: Set via build configurations (oss-dev, oss, commercial-dev, commercial) - -### Secrets Management -- **1Password Integration**: Desktop app can read secrets from 1Password vaults -- **Key Storage**: Platform-specific secure storage - - macOS: Keychain - - Windows: Credential Manager (DPAPI) - - Linux: libsecret (GNOME Keyring, KWallet) - - Browser: extension.storage with encryption -- **DO NOT**: Store secrets in code, environment files, or logs - -### Authentication Flows - -**5 Authentication Methods**: - -1. **Master Password** (PasswordLoginStrategy) - - User enters email + master password - - Derive master key using KDF (PBKDF2 SHA-256 or Argon2id) - - POST /connect/token with grant_type=password - - Receive access/refresh tokens + encrypted keys - -2. **Login with Device** (AuthRequestLoginStrategy) - - Passwordless authentication via existing device approval - - User requests auth on new device - - Existing device receives push/email notification - - Approve → new device receives encrypted keys - - No master password required on trusted devices - -3. **Single Sign-On** (SsoLoginStrategy) - - Organization configures SAML/OIDC identity provider - - User redirects to IdP for authentication - - Callback with authorization code - - POST /connect/token with grant_type=authorization_code - - Decrypt organization vault with user key - -4. **WebAuthn/Passkey** (WebAuthnLoginStrategy) - - PRF (Pseudo-Random Function) extension for key derivation - - Biometric or security key authentication - - Browser WebAuthn API integration - - Derive encryption keys from passkey - -5. **API Key** (UserApiLoginStrategy - CLI only) - - Generate API key (client_id + client_secret) in web vault - - POST /connect/token with grant_type=client_credentials - - Non-interactive authentication for automation - -**Common Flow**: -``` -LoginComponent - → Build credentials (email, masterPassword, etc.) - → LoginStrategyService.logIn(credentials) - → Initialize appropriate LoginStrategy - → POST /connect/token (TokenRequest) - → Process IdentityTokenResponse - → Success: AuthResult with tokens - → TwoFactor: Prompt for 2FA code - → DeviceVerification: Email verification required - → Handle AuthResult - → Navigate to vault - → Store tokens (TokenService) - → Initialize vault state (SyncService) -``` - -### Two-Factor Authentication -**Supported Providers**: -- Authenticator (TOTP): Google Authenticator, Authy, etc. -- Email: Verification codes via email -- YubiKey: Hardware security key (OTP mode) -- FIDO2 WebAuthn: Security keys and platform authenticators -- Duo: Duo Security push notifications -- OrganizationDuo: Organization-managed Duo - -**2FA Flow**: -1. Initial login returns TwoFactorProviderType in error response -2. Prompt user for 2FA code based on enabled providers -3. Retry login with `twoFactorToken` and `twoFactorProvider` parameters -4. Optional "remember device" to skip 2FA for 30 days - -### Session Management -**Token Lifecycle**: -- **Access Token**: Short-lived (1 hour), sent in Authorization header -- **Refresh Token**: Long-lived (30 days), used to obtain new access tokens -- **Auto-Refresh**: TokenService automatically refreshes before expiration -- **Revocation**: Tokens invalidated on logout, password change, or admin action - -**Vault Timeout**: -- **Lock**: Vault locked, master password/biometric required to unlock -- **Logout**: Complete logout, removes all data (configurable per timeout) -- **Timeout Options**: Immediate, 1/5/15/30/60 minutes, 4/24 hours, never, on system idle, on system lock - -**Biometric Unlock** (Desktop/Mobile): -- Store master key encrypted with biometric-protected key -- Native platform biometric APIs (Touch ID, Face ID, Windows Hello, fingerprint) -- Browser extension can request Desktop app unlock via native messaging - -### API Security -**Request Authentication**: -- **Authorization Header**: `Bearer ` -- **Device Identification**: Device-type, device-name, device-id headers -- **Client Version**: Client-version header for compatibility checks - -**Rate Limiting**: -- Server returns 429 Too Many Requests with Retry-After header -- Client respects rate limits with exponential backoff -- Critical endpoints: /connect/token, /accounts/register, /api/accounts/password - -**Data Validation**: -- Server validates all inputs (client validation is UX only) -- ErrorResponse with field-specific validation errors -- Client maps errors to form controls for display - -### Compliance & Security Controls -- **Encryption at Rest**: All vault data encrypted with user keys -- **Zero-Knowledge Architecture**: Server never has access to unencrypted vault data -- **Audit Logs**: Organization events logged for compliance -- **Data Residency**: EU cloud option for GDPR compliance -- **SOC 2 Type 2**: Annual compliance audit -- **Password Policies**: Configurable complexity requirements -- **Session Recording Prevention**: Sensitive fields flagged for anti-screen-capture - -### Security Best Practices for Development -- **Input Validation**: Validate and sanitize all user inputs (XSS prevention) -- **SQL Injection**: Server uses parameterized queries (DO NOT concatenate SQL) -- **CSRF Protection**: Anti-forgery tokens on state-changing operations -- **Content Security Policy**: Strict CSP headers in web vault -- **Dependency Scanning**: Automated vulnerability scanning in CI -- **Secrets in Code**: Use git-secrets pre-commit hook to prevent committing secrets -- **Logging**: NEVER log passwords, keys, tokens, or PII -- **Error Messages**: DO NOT expose sensitive information in error messages to UI - ---- - -## Quick Reference - -### Build Commands -```bash -# Build applications -npx nx build browser --configuration=commercial-dev -npx nx build web -npx nx build desktop -npx nx build cli - -# Test -npx nx test [project] - -# Lint & Format -npm run lint -npm run lint:fix -npm run prettier - -# Serve -npx nx serve web - -# Storybook -npm run storybook -``` - -### Critical Development Rules -1. **Browser APIs**: Always use BrowserApi abstraction (never chrome.* or browser.* directly) -2. **State Management**: Prefer SingleUserState over ActiveUserState (avoid race conditions) -3. **CLI Output**: Use CliUtils.writeLn() and respect BW_RESPONSE environment variable -4. **Cross-Team Imports**: Only import from @bitwarden/* barrel exports (no internal paths) -5. **ApiService**: Deprecated - extract methods to domain-specific API services -6. **Logging**: NEVER log sensitive data (passwords, keys, tokens, PII) -7. **Security**: Validate inputs, avoid SQL injection, use parameterized queries, respect CSP - -### App-Specific Context Files -- `apps/browser/CLAUDE.md`: Browser extension Manifest V3 patterns -- `apps/web/CLAUDE.md`: Web vault multi-tenant features -- `apps/desktop/CLAUDE.md`: Electron IPC architecture -- `apps/cli/CLAUDE.md`: CLI JSON output rules - -### Key Documentation -- `docs/using-nx-to-build-projects.md`: Comprehensive Nx build guide -- `libs/state/README.md`: StateProvider framework documentation -- `libs/auth/src/common/login-strategies/README.md`: Authentication flow diagrams - -### License Structure -- **GPL-3.0**: Open-source code (main repository) -- **Bitwarden License Agreement**: Commercial code (bitwarden_license/ directory) - ---- - -**Last Updated**: 2026-01-13 by Bitwarden Init Plugin - -For additional context, see app-specific CLAUDE.md files and team-owned library README files throughout the monorepo.