项目整体架构、模块组织与交互关系(更新于 2025-12-31)
BitwardenForCommandPalette 是一个 PowerToys Command Palette 扩展,通过本地 Bitwarden CLI (bw) 与 Bitwarden 密码库交互,提供快速访问和管理密码的能力。
| 组件 | 技术 |
|---|---|
| 框架 | .NET 10.0 |
| 平台 | Windows 10.0.26100.0 |
| 打包 | MSIX |
| 扩展 SDK | Microsoft.CommandPalette.Extensions.Toolkit |
| 密码管理 | Bitwarden CLI |
| JSON 序列化 | System.Text.Json (Source Generator) |
| UI 框架 | Adaptive Cards 1.6 |
| 本地化 | WinRT ResourceLoader |
| 功能类别 | 具体功能 |
|---|---|
| 密码库管理 | 状态检测、解锁、锁定、同步 |
| 项目浏览 | 列表展示、搜索、筛选(收藏/文件夹/类型/回收站) |
| 项目操作 | 复制凭据、打开 URL、查看 TOTP |
| CRUD 操作 | 创建、编辑、删除、恢复项目 |
| 密码生成 | 随机密码生成、口令短语生成 |
| 文件夹管理 | 创建文件夹、按文件夹筛选 |
┌─────────────────────────────────────────────────────────────────────┐
│ PowerToys Command Palette │
└─────────────────────────────────────────────────────────────────────┘
│
│ COM / WinRT
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Extension Entry Point │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ BitwardenForCommandPalette.cs (IExtension) │ │
│ │ └── BitwardenForCommandPaletteCommandsProvider.cs │ │
│ └───────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Pages Layer │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 列表页面 (DynamicListPage) │ │
│ │ ┌──────────────────┐ ┌──────────────────┐ │ │
│ │ │ MainPage │ │ FilterPage │ │ │
│ │ │ - 项目列表 │ │ - 收藏筛选 │ │ │
│ │ │ - 搜索 │ │ - 文件夹筛选 │ │ │
│ │ │ - 详情面板 │ │ - 类型筛选 │ │ │
│ │ │ - 回收站视图 │ │ - 回收站 │ │ │
│ │ └──────────────────┘ └──────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 内容页面 (ContentPage + FormContent) │ │
│ │ ┌────────────────┐ ┌────────────────┐ ┌────────────────────┐│ │
│ │ │ UnlockPage │ │ CreateItemPage │ │ EditItemPage ││ │
│ │ │ - 密码输入 │ │ - 类型选择 │ │ - 字段编辑 ││ │
│ │ │ - 解锁提交 │ │ - 表单输入 │ │ - 保存/取消 ││ │
│ │ └────────────────┘ │ - 文件夹选择 │ └────────────────────┘│ │
│ │ └────────────────┘ │ │
│ │ ┌────────────────┐ ┌────────────────┐ ┌────────────────────┐│ │
│ │ │ TotpPage │ │ GeneratorPage │ │ CreateFolderPage ││ │
│ │ │ - 实时倒计时 │ │ - 密码生成器 │ │ - 名称输入 ││ │
│ │ │ - 一键复制 │ │ - 口令生成器 │ │ - 创建提交 ││ │
│ │ └────────────────┘ └────────────────┘ └────────────────────┘│ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Commands Layer │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ItemCommands.cs │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌────────────────────────┐ │ │
│ │ │ 登录命令 │ │ 银行卡命令 │ │ CRUD 命令 │ │ │
│ │ │ - Password │ │ - Number │ │ - CreateItemCommand │ │ │
│ │ │ - Username │ │ - CVV │ │ - EditItemCommand │ │ │
│ │ │ - URL │ │ - Expiry │ │ - DeleteItemCommand │ │ │
│ │ │ - TOTP │ │ - Holder │ │ - RestoreCommand │ │ │
│ │ └──────────────┘ └──────────────┘ │ - PermanentDeleteCmd │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ └────────────────────────┘ │ │
│ │ │ 身份命令 │ │ 密码库命令 │ │ │
│ │ │ - FullName │ │ - Sync │ │ │
│ │ │ - Email │ │ - Lock │ │ │
│ │ │ - Phone │ └──────────────┘ │ │
│ │ │ - Address │ ┌──────────────┐ │ │
│ │ │ - SSN, etc. │ │ 通用命令 │ │ │
│ │ └──────────────┘ │ - Notes │ │ │
│ │ │ - Field │ │ │
│ │ └──────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Services Layer │
│ ┌─────────────────────────────┐ ┌──────────────────────────────┐ │
│ │ BitwardenCliService.cs │ │ IconService.cs │ │
│ │ (Singleton) │ │ (Static + Cache) │ │
│ │ │ │ │ │
│ │ 状态管理: │ │ - GetItemIcon() │ │
│ │ - GetStatusAsync() │ │ - GetWebsiteIconUrl() │ │
│ │ - UnlockAsync() │ │ - 200 条缓存上限 │ │
│ │ - LockAsync() │ │ │ │
│ │ - SyncAsync() │ └──────────────────────────────┘ │
│ │ │ ┌──────────────────────────────┐ │
│ │ 数据获取: │ │ SettingsManager.cs │ │
│ │ - GetItemsAsync() │ │ (ISettingsManager) │ │
│ │ - GetFoldersAsync() │ │ │ │
│ │ - GetTotpAsync() │ │ - CLI Path │ │
│ │ │ │ - API Client ID/Secret │ │
│ │ CRUD 操作: │ │ - Custom Environment Vars │ │
│ │ - CreateItemAsync() │ │ │ │
│ │ - EditItemAsync() │ └──────────────────────────────┘ │
│ │ - DeleteItemAsync() │ ┌──────────────────────────────┐ │
│ │ - RestoreItemAsync() │ │ ResourceHelper.cs │ │
│ │ - PermanentDeleteAsync() │ │ (Static) │ │
│ │ - CreateFolderAsync() │ │ │ │
│ │ │ │ - GetString(key) │ │
│ │ 生成器: │ │ - 多语言支持 (en/zh) │ │
│ │ - GeneratePasswordAsync() │ │ │ │
│ │ - GeneratePassphraseAsync()│ └──────────────────────────────┘ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
│ Process.Start("bw", args)
▼
┌─────────────────────────────────────────────────────────────────────┐
│ External: Bitwarden CLI │
│ bw status | unlock | lock | sync | list | get | create | edit │
│ bw delete | restore | generate │
└─────────────────────────────────────────────────────────────────────┘
BitwardenForCommandPalette/
├── BitwardenForCommandPalette.csproj # 项目配置
├── BitwardenForCommandPalette.cs # 扩展入口点 (IExtension)
├── BitwardenForCommandPaletteCommandsProvider.cs # 命令提供者
├── Program.cs # COM 服务器启动
├── Package.appxmanifest # MSIX 清单
├── app.manifest # 应用清单
│
├── Commands/ # 命令模块
│ └── ItemCommands.cs # 所有复制/操作命令 (~650 行)
│
├── Pages/ # 页面模块
│ ├── BitwardenForCommandPalettePage.cs # 主列表页 (~800 行)
│ ├── UnlockPage.cs # 解锁表单页
│ ├── FilterPage.cs # 筛选选项页
│ ├── CreateItemPage.cs # 创建项目页 (~900 行)
│ ├── EditItemPage.cs # 编辑项目页 (~650 行)
│ ├── TotpPage.cs # TOTP 验证码页
│ ├── GeneratorPage.cs # 密码/口令生成器页 (~450 行)
│ └── VaultFilters.cs # 筛选状态管理类
│
├── Models/ # 数据模型
│ ├── BitwardenItem.cs # 密码项模型(含所有子类型)
│ └── BitwardenStatus.cs # 状态模型
│
├── Services/ # 服务层
│ ├── BitwardenCliService.cs # CLI 封装服务 (~720 行)
│ ├── IconService.cs # 图标服务
│ └── SettingsManager.cs # 设置管理服务
│
├── Helpers/ # 辅助类
│ └── ResourceHelper.cs # 多语言资源助手 (~340 行)
│
├── Strings/ # 本地化资源
│ ├── en-US/Resources.resw # 英文资源 (~900 行)
│ └── zh-CN/Resources.resw # 中文资源 (~900 行)
│
└── Assets/ # 资源文件
├── app.ico
└── *.png
| 文件 | 职责 |
|---|---|
BitwardenForCommandPalette.cs |
实现 IExtension,作为 COM 服务器入口点 |
BitwardenForCommandPaletteCommandsProvider.cs |
继承 CommandProvider,定义扩展顶级命令 |
Program.cs |
启动 COM 服务器,处理 -RegisterProcessAsComServer |
| 文件 | 基类 | 职责 |
|---|---|---|
BitwardenForCommandPalettePage.cs |
DynamicListPage |
主页面,项目列表、搜索、详情面板 |
UnlockPage.cs |
ContentPage |
解锁表单,主密码输入 |
FilterPage.cs |
DynamicListPage |
筛选页面,收藏/文件夹/类型筛选 |
CreateItemPage.cs |
ContentPage |
创建项目/文件夹,类型选择 + Adaptive Cards 表单 |
EditItemPage.cs |
ContentPage |
编辑项目,Adaptive Cards 表单 |
TotpPage.cs |
ContentPage |
TOTP 页面,实时倒计时 + 一键复制 |
GeneratorPage.cs |
ContentPage |
密码/口令生成器 |
VaultFilters.cs |
- | 筛选状态管理类 |
| 命令类别 | 命令列表 |
|---|---|
| 登录命令 | CopyPassword, CopyUsername, CopyUrl, OpenUrl, CopyTotp |
| 银行卡命令 | CopyCardNumber, CopyCardCvv, CopyCardExpiration, CopyCardholderName |
| 身份命令 | CopyFullName, CopyEmail, CopyPhone, CopyAddress, CopyCompany, CopySsn, CopyPassport, CopyLicense |
| 通用命令 | CopyNotes, CopyField |
| 密码库命令 | SyncVault, LockVault |
| CRUD 命令 | CreateItem, EditItem, DeleteItem, RestoreItem, PermanentDelete |
| 服务 | 模式 | 职责 |
|---|---|---|
BitwardenCliService |
单例 | CLI 封装,会话管理,所有 bw 命令调用 |
IconService |
静态 + 缓存 | 网站图标获取,200 条缓存上限 |
SettingsManager |
接口实现 | 设置管理,CLI 路径/API Key/环境变量 |
ResourceHelper |
静态 | 多语言资源获取 |
| 类 | 用途 |
|---|---|
BitwardenItem |
密码项主模型 |
BitwardenLogin |
登录信息 |
BitwardenCard |
银行卡信息 |
BitwardenIdentity |
身份信息 |
BitwardenSecureNote |
安全笔记 |
BitwardenField |
自定义字段 |
BitwardenFolder |
文件夹 |
BitwardenUri |
URL 项 |
BitwardenStatus |
密码库状态 |
主页面,负责展示项目列表和处理用户交互。
核心功能:
- 密码库状态检测和解锁导航
- 项目列表展示(支持回收站视图)
- 搜索过滤
- 详情面板(右侧 Markdown)
- 上下文命令菜单
关键方法:
public override IListItem[] GetItems()
{
// 1. 检查密码库状态
// 2. 加载项目列表
// 3. 应用筛选
// 4. 构建 ListItem 数组
}
private string BuildDetailsMarkdown(BitwardenItem item)
{
// 根据项目类型生成详情面板 Markdown
}
private IListItem CreateListItem(BitwardenItem item)
{
// 创建列表项,设置命令、图标、详情
}创建项目页面,使用 Adaptive Cards 实现表单。
结构:
CreateItemTypeSelectorPage (ListPage)
├── CreateItemPage (Login)
├── CreateItemPage (Card)
├── CreateItemPage (Identity)
├── CreateItemPage (SecureNote)
├── CreateFolderPage
├── PasswordGeneratorPage
└── PassphraseGeneratorPage
关键实现:
- 动态加载文件夹列表
- 动态生成 Input.ChoiceSet(文件夹选择)
- 表单提交调用
BitwardenCliService.CreateItemAsync()
编辑项目页面,加载现有项目数据到表单。
关键实现:
- 根据项目类型选择不同模板
- 预填充现有数据
- 表单提交调用
BitwardenCliService.EditItemAsync()
TOTP 验证码页面,实时显示倒计时。
关键实现:
// 定时器每秒更新
private Timer _timer;
private void UpdateTotp()
{
var totp = BitwardenCliService.Instance.GetTotpAsync(_itemId).Result;
var remaining = 30 - (DateTime.UtcNow.Second % 30);
// 更新显示
}密码/口令生成器页面。
两个生成器:
-
PasswordGeneratorPage - 随机密码
- 长度:5-128
- 选项:大写、小写、数字、特殊字符
-
PassphraseGeneratorPage - 口令短语
- 单词数:3-20
- 分隔符
- 选项:首字母大写、包含数字
核心服务,封装所有 Bitwarden CLI 调用。
单例模式:
private static BitwardenCliService? _instance;
public static BitwardenCliService Instance => _instance ??= new BitwardenCliService();Session 管理:
public string? SessionKey { get; private set; }
public async Task<bool> UnlockAsync(string password)
{
var (output, _, exitCode) = await ExecuteCommandAsync($"unlock \"{password}\" --raw");
if (exitCode == 0)
{
SessionKey = output.Trim();
return true;
}
return false;
}命令执行:
private static async Task<(string output, string error, int exitCode)> ExecuteCommandAsync(string arguments)
{
var startInfo = new ProcessStartInfo
{
FileName = SettingsManager.Instance.BitwardenCliPath ?? "bw",
Arguments = arguments,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
// 注入环境变量
foreach (var (key, value) in SettingsManager.Instance.GetEnvironmentVariables())
{
startInfo.EnvironmentVariables[key] = value;
}
// 执行并返回结果
}JSON 序列化(AOT 兼容):
[JsonSourceGenerationOptions(WriteIndented = false)]
[JsonSerializable(typeof(BitwardenStatus))]
[JsonSerializable(typeof(BitwardenItem[]))]
[JsonSerializable(typeof(BitwardenFolder[]))]
internal partial class BitwardenJsonContext : JsonSerializerContext { }图标服务,带缓存机制。
缓存策略:
private static readonly Dictionary<string, string> _iconUrlCache = new();
private const int MaxCacheSize = 200;
public static string GetWebsiteIconUrl(string domain)
{
if (_iconUrlCache.TryGetValue(domain, out var cached))
return cached;
// 缓存淘汰
if (_iconUrlCache.Count >= MaxCacheSize)
{
var keysToRemove = _iconUrlCache.Keys.Take(MaxCacheSize / 2).ToList();
foreach (var key in keysToRemove)
_iconUrlCache.Remove(key);
}
var url = $"https://icons.bitwarden.net/{domain}/icon.png";
_iconUrlCache[domain] = url;
return url;
}设置管理,实现 ISettingsManager 接口。
配置项:
BitwardenCliPath- CLI 路径BitwardenClientId- API Client IDBitwardenClientSecret- API Client SecretCustomEnvironmentVariables- 自定义环境变量
Command Palette 启动
│
▼
加载扩展 (COM)
│
▼
BitwardenForCommandPalette.GetProvider(ProviderType.Commands)
│
▼
返回 BitwardenForCommandPaletteCommandsProvider
│
▼
显示 "Bitwarden For Command Palette" 入口
用户选择扩展
│
▼
MainPage.GetItems()
│
├─── 状态未知?
│ │
│ ▼ Yes
│ GetStatusAsync() → bw status
│ │
│ ▼
│ 状态判断:
│ ├── unauthenticated → 显示登录提示
│ ├── locked → 导航到 UnlockPage
│ └── unlocked → 继续加载
│
▼
LoadItemsAsync() → bw list items
│
▼
应用筛选 (VaultFilters)
│
▼
构建 ListItem[],显示列表
用户点击 "Create Item"
│
▼
导航到 CreateItemTypeSelectorPage
│
▼
GetItems() → 加载文件夹列表
│
▼
显示类型选择列表
│
▼
用户选择类型(如 Login)
│
▼
导航到 CreateItemPage(Login, folders)
│
▼
显示 Adaptive Card 表单
├── 名称输入
├── 文件夹选择(下拉框)
├── 类型特定字段
└── 创建按钮
│
▼
用户填写并提交
│
▼
CreateItemForm.SubmitForm()
│
▼
构建 JsonObject
│
▼
CreateItemAsync() → bw create item <base64>
│
▼
显示成功/失败提示
用户点击 TOTP 命令
│
▼
导航到 TotpPage(itemId)
│
▼
启动定时器 (1秒间隔)
│
▼
每秒: GetTotpAsync() → bw get totp
│
▼
更新显示:
├── 验证码
├── 倒计时进度条
└── 剩余秒数
│
▼
用户点击复制 → 复制到剪贴板
- 创建新的页面类:
internal sealed partial class MyNewPage : DynamicListPage
{
public MyNewPage()
{
Icon = new IconInfo("\uE700");
Title = "My New Page";
Name = "Open";
}
public override void UpdateSearchText(string oldSearch, string newSearch)
{
RaiseItemsChanged();
}
public override IListItem[] GetItems()
{
// 返回列表项
}
}- 在需要的地方导航:
return new ListItem(new MyNewPage()) { Title = "Go to New Page" };internal sealed class MyFormPage : ContentPage
{
private readonly MyForm _form;
public MyFormPage()
{
_form = new MyForm();
Name = "Page Name";
}
public override IContent[] GetContent() => [_form];
}
internal sealed class MyForm : FormContent
{
public MyForm()
{
TemplateJson = GetTemplate();
DataJson = GetData();
}
private static string GetTemplate() => """
{
"type": "AdaptiveCard",
"version": "1.6",
"body": [
{
"type": "Input.Text",
"id": "myField",
"label": "${fieldLabel}"
}
],
"actions": [
{
"type": "Action.Submit",
"title": "Submit"
}
]
}
""";
private static string GetData()
{
return new JsonObject
{
["fieldLabel"] = "My Field"
}.ToJsonString();
}
public override ICommandResult SubmitForm(string payload, string data)
{
var formData = JsonNode.Parse(payload)?.AsObject();
var myField = formData?["myField"]?.GetValue<string>();
// 处理提交
return CommandResult.ShowToast("Success");
}
}public async Task<string?> MyNewMethodAsync(string param)
{
if (string.IsNullOrEmpty(SessionKey))
return null;
var (output, error, exitCode) = await ExecuteCommandAsync(
$"my-command \"{param}\" --session \"{SessionKey}\"");
return exitCode == 0 ? output.Trim() : null;
}- 在
en-US/Resources.resw添加:
<data name="MyNewString" xml:space="preserve">
<value>English text</value>
</data>- 在
zh-CN/Resources.resw添加:
<data name="MyNewString" xml:space="preserve">
<value>中文文本</value>
</data>- 在
ResourceHelper.cs添加:
public static string MyNewString => GetString("MyNewString");常见问题和解决方案请参考 Troubleshooting.md。
主要包括:
- AOT 编译相关问题
- Adaptive Cards 相关问题
- Command Palette SDK 相关问题
- Bitwarden CLI 相关问题