Simplify vault cockpit commands

This commit is contained in:
Matt Derman 2026-05-30 16:32:10 +02:00
parent 9c2daa353f
commit 6da8ebf421
7 changed files with 263 additions and 254 deletions

View file

@ -28,9 +28,9 @@ Context Nine is desktop-only. Vault Command Center and background sync commands
## Vault Command Center
Vault Command Center runs the local `vault` command dispatcher without leaving Obsidian. Default buttons include refresh, sync, context, content schedules, attachment dry run/apply, and profile sync.
Vault Command Center shows local command output in the right sidebar and keeps a Git Commit action close at hand. Day-to-day vault commands live in the Obsidian command palette and are loaded from the vault command metadata file.
Normal click runs a command directly. `Cmd+Click` opens an arguments modal and appends parsed arguments to the base command. Output streams live into the panel with stdout, stderr, status, exit code, and timestamps visible after completion.
Output streams live into the panel with stdout, stderr, status, exit code, and timestamps visible after completion.
## Development

View file

@ -1,4 +1,4 @@
import { Notice, Plugin, PluginSettingTab, Setting, TFile, type WorkspaceLeaf } from "obsidian";
import { Modal, Notice, Plugin, PluginSettingTab, Setting, TFile, type App, type WorkspaceLeaf } from "obsidian";
import { spawn, type ChildProcessWithoutNullStreams } from "child_process";
import { join } from "path";
import { AttachmentRouter, noticeRouteResult } from "./attachment-router";
@ -9,6 +9,10 @@ import { VAULT_COCKPIT_VIEW_TYPE, VaultCockpitView } from "./vault-cockpit";
import { TaskContextRouterService } from "./task-context-router";
import { TaskNotesUxService } from "./tasknotes-ux";
import { TaskNotesModalUiService } from "./tasknotes-modal-ui";
import {
loadVaultCommandMetadata,
type VaultCommandDefinition,
} from "./vault-command-metadata";
export default class ContextNinePlugin extends Plugin {
settings: MasterPluginSettings;
@ -52,6 +56,8 @@ export default class ContextNinePlugin extends Plugin {
},
});
await this.registerVaultPaletteCommands();
this.addCommand({
id: "focus-main-pane-1",
name: "Focus first main pane",
@ -204,22 +210,62 @@ export default class ContextNinePlugin extends Plugin {
}
getVaultCommand(command: string, cwd: string): string {
return command === "vault" ? join(cwd, "master/system/scripts/vault.py") : command;
return command === "vault" ? join(cwd, "_master/system/scripts/vault.py") : command;
}
async openVaultCockpit(): Promise<void> {
await this.getVaultCockpitView();
}
async getVaultCockpitView(): Promise<VaultCockpitView | null> {
const existing = this.app.workspace.getLeavesOfType(VAULT_COCKPIT_VIEW_TYPE)[0];
if (existing) {
await this.app.workspace.revealLeaf(existing);
return;
return existing.view instanceof VaultCockpitView ? existing.view : null;
}
const leaf = this.app.workspace.getRightLeaf(false);
if (!leaf) {
new Notice("Could not open the right sidebar.");
return;
return null;
}
await leaf.setViewState({ type: VAULT_COCKPIT_VIEW_TYPE, active: true });
await this.app.workspace.revealLeaf(leaf);
return leaf.view instanceof VaultCockpitView ? leaf.view : null;
}
private async registerVaultPaletteCommands(): Promise<void> {
const result = await loadVaultCommandMetadata(this.app);
if (result.warning) {
console.warn(`[Context Nine] ${result.warning}`);
}
for (const command of result.commands.filter((item) => item.palette)) {
this.addCommand({
id: `vault-${command.id}`,
name: `Vault ${command.label}`,
callback: () => {
void this.runPaletteVaultCommand(command);
},
});
}
}
private async runPaletteVaultCommand(command: VaultCommandDefinition): Promise<void> {
if (command.promptArgs?.length) {
new VaultPromptArgsModal(this.app, command, (extraArgs) => {
void this.runVaultCommandInCockpit(command, extraArgs);
}).open();
return;
}
await this.runVaultCommandInCockpit(command);
}
private async runVaultCommandInCockpit(command: VaultCommandDefinition, extraArgs: string[] = []): Promise<void> {
const view = await this.getVaultCockpitView();
if (!view) {
new Notice("Could not open the vault command center.");
return;
}
view.runCommand(command, extraArgs);
}
private async focusMainPane(index: number, createIfMissing = false): Promise<void> {
@ -369,6 +415,60 @@ export default class ContextNinePlugin extends Plugin {
}
}
class VaultPromptArgsModal extends Modal {
private values: string[];
constructor(
app: App,
private readonly command: VaultCommandDefinition,
private readonly onSubmit: (args: string[]) => void
) {
super(app);
this.values = new Array(command.promptArgs?.length ?? 0).fill("");
}
onOpen(): void {
this.contentEl.empty();
this.containerEl.addClass("omp-modal");
this.titleEl.setText(this.command.label);
const promptArgs = this.command.promptArgs ?? [];
promptArgs.forEach((promptArg, index) => {
new Setting(this.contentEl).setName(promptArg.label).addText((text) => {
text.setPlaceholder(promptArg.placeholder ?? "").onChange((value) => {
this.values[index] = value.trim();
});
text.inputEl.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
event.preventDefault();
this.submit();
}
});
if (index === 0) {
window.setTimeout(() => text.inputEl.focus(), 0);
}
});
});
const buttons = this.contentEl.createDiv({ cls: "omp-button-row" });
buttons.createEl("button", { text: "Cancel" }).addEventListener("click", () => this.close());
const run = buttons.createEl("button", { text: "Run" });
run.addClass("mod-cta");
run.addEventListener("click", () => this.submit());
}
private submit(): void {
const promptArgs = this.command.promptArgs ?? [];
const missing = promptArgs.find((_, index) => !this.values[index]);
if (missing) {
new Notice(`${missing.label} required.`);
return;
}
this.close();
this.onSubmit(this.values);
}
}
class ObsidianMasterSettingTab extends PluginSettingTab {
constructor(private readonly plugin: ContextNinePlugin) {
super(plugin.app, plugin);

View file

@ -1,6 +1,5 @@
import { ItemView, Modal, Notice, Setting, type App, type IconName, type WorkspaceLeaf } from "obsidian";
import { ItemView, Notice, type IconName, type WorkspaceLeaf } from "obsidian";
import type ContextNinePlugin from "./main";
import { parseAdditionalArgs } from "./vault-args";
import {
FALLBACK_VAULT_COMMANDS,
loadVaultCommandMetadata,
@ -18,14 +17,12 @@ interface LogEntry {
export class VaultCockpitView extends ItemView {
private readonly runner = new VaultCommandRunner();
private commands: VaultCommandDefinition[] = FALLBACK_VAULT_COMMANDS;
private metadataWarning: string | null = null;
private status: VaultRunStatus = "idle";
private activeCommandId: string | null = null;
private actionsExpanded = false;
private outputExpanded = false;
private logEntries: LogEntry[] = [];
private statusEl!: HTMLElement;
private warningEl!: HTMLElement;
private logContainerEl!: HTMLElement;
private logEl: HTMLElement | null = null;
private buttons = new Map<string, HTMLButtonElement>();
@ -52,9 +49,8 @@ export class VaultCockpitView extends ItemView {
async onOpen(): Promise<void> {
const result = await loadVaultCommandMetadata(this.app);
this.commands = result.commands;
this.metadataWarning = result.warning ?? null;
if (this.metadataWarning) {
this.logEntries.push({ stream: "system", text: `Warning: ${this.metadataWarning}\n` });
if (result.warning) {
this.logEntries.push({ stream: "system", text: `Warning: ${result.warning}\n` });
}
this.render();
}
@ -80,28 +76,12 @@ export class VaultCockpitView extends ItemView {
this.statusEl = primaryRow.createDiv({ cls: "omp-vault-cockpit-status", text: labelForStatus(this.status) });
this.statusEl.dataset.status = this.status;
const commitButton = primaryRow.createEl("button", {
cls: "omp-vault-cockpit-actions-toggle",
attr: {
"aria-label": "Git commit",
title: "Git commit",
},
});
commitButton.createSpan({
cls: "omp-vault-cockpit-actions-toggle-icon",
text: "✓",
});
commitButton.addEventListener("click", () => {
this.runGitCommit();
});
this.buttons.set("git-commit", commitButton);
const actionsToggle = primaryRow.createEl("button", {
cls: "omp-vault-cockpit-actions-toggle",
attr: {
"aria-label": this.actionsExpanded ? "Hide vault actions" : "Show vault actions",
"aria-label": this.actionsExpanded ? "Hide console controls" : "Show console controls",
"aria-expanded": String(this.actionsExpanded),
title: this.actionsExpanded ? "Hide vault actions" : "Show vault actions",
title: this.actionsExpanded ? "Hide console controls" : "Show console controls",
},
});
actionsToggle.createSpan({
@ -114,12 +94,12 @@ export class VaultCockpitView extends ItemView {
});
if (this.actionsExpanded) {
this.renderCommandSections(containerEl);
this.logContainerEl = containerEl.createDiv({ cls: "omp-vault-cockpit-output" });
this.renderOutput();
} else {
this.logEl = null;
}
this.logContainerEl = containerEl.createDiv({ cls: "omp-vault-cockpit-output" });
this.renderStatus();
this.renderOutput();
}
private createCommandButton(parent: HTMLElement, command: VaultCommandDefinition, extraClass = ""): HTMLButtonElement {
@ -131,18 +111,16 @@ export class VaultCockpitView extends ItemView {
"aria-label": command.description,
},
});
button.addEventListener("click", (event) => {
if (event.metaKey) {
new VaultArgsModal(this.app, command, (extraArgs) => {
this.runCommand(command, extraArgs);
}).open();
return;
}
button.addEventListener("click", () => {
this.runCommand(command);
});
return button;
}
private findCommand(id: string): VaultCommandDefinition | undefined {
return this.commands.find((command) => command.id === id);
}
private runGitCommit(): void {
if (this.runner.running) {
new Notice("A vault command is already running.");
@ -189,61 +167,7 @@ export class VaultCockpitView extends ItemView {
}
}
private renderCommandSections(containerEl: HTMLElement): void {
const sections = [
{
title: "Maintenance",
commands: ["sync", "context", "content"],
},
{
title: "Attachments",
commands: ["attachments-dry-run", "attachments-apply"],
},
{
title: "System",
commands: ["profile"],
},
];
const commandList = containerEl.createDiv({ cls: "omp-vault-cockpit-actions" });
const primaryCommand = this.findCommand("refresh") ?? this.commands[0];
const renderedIds = new Set(primaryCommand ? [primaryCommand.id] : []);
for (const section of sections) {
const commands = section.commands.map((id) => this.findCommand(id)).filter((command): command is VaultCommandDefinition => Boolean(command));
if (commands.length === 0) {
continue;
}
const sectionEl = commandList.createDiv({ cls: "omp-vault-cockpit-section" });
sectionEl.createDiv({ cls: "omp-vault-cockpit-section-title", text: section.title });
const grid = sectionEl.createDiv({ cls: "omp-vault-cockpit-grid" });
for (const command of commands) {
const button = this.createCommandButton(grid, command);
this.buttons.set(command.id, button);
renderedIds.add(command.id);
}
}
const uncategorized = this.commands.filter((command) => !renderedIds.has(command.id));
if (uncategorized.length === 0) {
return;
}
const sectionEl = commandList.createDiv({ cls: "omp-vault-cockpit-section" });
sectionEl.createDiv({ cls: "omp-vault-cockpit-section-title", text: "Other" });
const grid = sectionEl.createDiv({ cls: "omp-vault-cockpit-grid" });
for (const command of uncategorized) {
const button = this.createCommandButton(grid, command);
this.buttons.set(command.id, button);
}
}
private findCommand(id: string): VaultCommandDefinition | undefined {
return this.commands.find((command) => command.id === id);
}
private runCommand(command: VaultCommandDefinition, extraArgs: string[] = []): void {
runCommand(command: VaultCommandDefinition, extraArgs: string[] = []): void {
if (this.runner.running) {
new Notice("A vault command is already running.");
return;
@ -315,7 +239,6 @@ export class VaultCockpitView extends ItemView {
for (const [id, button] of this.buttons) {
button.disabled = this.status === "running" && id === this.activeCommandId;
}
this.renderOutput();
}
private renderOutput(): void {
@ -323,15 +246,10 @@ export class VaultCockpitView extends ItemView {
return;
}
this.logContainerEl.empty();
if (!this.actionsExpanded) {
this.logEl = null;
return;
}
const row = this.logContainerEl.createDiv({ cls: "omp-vault-cockpit-output-row" });
const toggle = row.createEl("button", {
cls: "omp-vault-cockpit-output-toggle",
text: `${this.outputExpanded ? "Hide" : "Show"} Output`,
text: `${this.outputExpanded ? "Hide" : "Show"} Console Output`,
attr: {
"aria-expanded": String(this.outputExpanded),
},
@ -341,12 +259,13 @@ export class VaultCockpitView extends ItemView {
this.renderOutput();
});
if (this.metadataWarning) {
this.warningEl = this.logContainerEl.createDiv({
cls: "omp-vault-cockpit-warning",
text: this.metadataWarning,
});
}
const commitButton = row.createEl("button", { cls: "omp-vault-cockpit-git-commit", text: "Git Commit" });
commitButton.disabled = this.status === "running";
commitButton.addEventListener("click", () => this.runGitCommit());
this.buttons.set("git-commit", commitButton);
this.statusEl = row.createDiv({ cls: "omp-vault-cockpit-status", text: labelForStatus(this.status) });
this.statusEl.dataset.status = this.status;
if (!this.outputExpanded) {
this.logEl = null;
@ -373,55 +292,6 @@ export class VaultCockpitView extends ItemView {
}
}
class VaultArgsModal extends Modal {
private value = "";
constructor(
app: App,
private readonly command: VaultCommandDefinition,
private readonly onSubmit: (extraArgs: string[]) => void
) {
super(app);
}
onOpen(): void {
this.contentEl.empty();
this.containerEl.addClass("omp-modal");
this.titleEl.setText(`${this.command.label} arguments`);
this.contentEl.createEl("p", {
text: `Base command: vault ${this.command.args.join(" ")}`,
});
new Setting(this.contentEl).setName("Additional arguments").addText((text) => {
text.setPlaceholder("--flag value").onChange((value) => {
this.value = value;
});
text.inputEl.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
event.preventDefault();
this.submit();
}
});
});
const buttons = this.contentEl.createDiv({ cls: "omp-button-row" });
buttons.createEl("button", { text: "Cancel" }).addEventListener("click", () => this.close());
const run = buttons.createEl("button", { text: "Run" });
run.addClass("mod-cta");
run.addEventListener("click", () => this.submit());
}
private submit(): void {
const parsed = parseAdditionalArgs(this.value);
if (parsed.error) {
new Notice(parsed.error);
return;
}
this.close();
this.onSubmit(parsed.args);
}
}
function labelForStatus(status: VaultRunStatus): string {
if (status === "idle") return "Idle";
if (status === "running") return "Running";

View file

@ -6,6 +6,15 @@ export interface VaultCommandDefinition {
description: string;
args: string[];
aliases?: string[];
cockpit?: boolean;
palette?: boolean;
promptArgs?: VaultCommandPromptArg[];
}
export interface VaultCommandPromptArg {
label: string;
placeholder?: string;
argName?: string;
}
export interface VaultCommandLoadResult {
@ -13,7 +22,8 @@ export interface VaultCommandLoadResult {
warning?: string;
}
export const VAULT_COMMAND_METADATA_PATH = "master/system/scripts/vault-commands.json";
export const VAULT_COMMAND_METADATA_PATH = "_master/system/scripts/vault-commands.json";
const LEGACY_VAULT_COMMAND_METADATA_PATH = "master/system/scripts/vault-commands.json";
export const FALLBACK_VAULT_COMMANDS: VaultCommandDefinition[] = [
{
@ -21,6 +31,22 @@ export const FALLBACK_VAULT_COMMANDS: VaultCommandDefinition[] = [
label: "Refresh",
description: "Ingest configured Apple Notes, then regenerate agent context.",
args: ["refresh"],
palette: true,
},
{
id: "folder-register",
label: "Folder Register",
description: "Register an existing context folder and regenerate vault wiring.",
args: ["folder", "register"],
palette: true,
promptArgs: [{ label: "Context folder", placeholder: "impression", argName: "name" }],
},
{
id: "upgrade",
label: "Upgrade",
description: "Apply public bootstrap vault updates.",
args: ["upgrade", "--apply"],
palette: true,
},
{
id: "sync",
@ -106,10 +132,15 @@ export async function loadVaultCommandMetadata(app: App): Promise<VaultCommandLo
const json = await app.vault.adapter.read(VAULT_COMMAND_METADATA_PATH);
return parseVaultCommandMetadata(json);
} catch (error) {
return {
commands: FALLBACK_VAULT_COMMANDS,
warning: `Could not read ${VAULT_COMMAND_METADATA_PATH}: ${messageForError(error)}`,
};
try {
const json = await app.vault.adapter.read(LEGACY_VAULT_COMMAND_METADATA_PATH);
return parseVaultCommandMetadata(json);
} catch {
return {
commands: FALLBACK_VAULT_COMMANDS,
warning: `Could not read ${VAULT_COMMAND_METADATA_PATH}: ${messageForError(error)}`,
};
}
}
}
@ -132,6 +163,18 @@ function normalizeCommand(value: unknown): VaultCommandDefinition | null {
) {
return null;
}
if (value.cockpit !== undefined && typeof value.cockpit !== "boolean") {
return null;
}
if (value.palette !== undefined && typeof value.palette !== "boolean") {
return null;
}
if (
value.promptArgs !== undefined &&
(!Array.isArray(value.promptArgs) || !value.promptArgs.every(isPromptArg))
) {
return null;
}
return {
id: value.id,
@ -139,6 +182,9 @@ function normalizeCommand(value: unknown): VaultCommandDefinition | null {
description: value.description,
args: value.args,
aliases: value.aliases,
cockpit: value.cockpit,
palette: value.palette,
promptArgs: value.promptArgs,
};
}
@ -146,6 +192,19 @@ function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
function isPromptArg(value: unknown): value is VaultCommandPromptArg {
if (!isRecord(value) || typeof value.label !== "string") {
return false;
}
if (value.placeholder !== undefined && typeof value.placeholder !== "string") {
return false;
}
if (value.argName !== undefined && typeof value.argName !== "string") {
return false;
}
return true;
}
function messageForError(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}

View file

@ -44,7 +44,7 @@ export class VaultCommandRunner {
}
const startedAt = new Date();
const resolvedCommand = command === "vault" ? join(cwd, "master/system/scripts/vault.py") : command;
const resolvedCommand = command === "vault" ? join(cwd, "_master/system/scripts/vault.py") : command;
events.onStart({ spec, command: resolvedCommand, cwd, startedAt });
const child = spawn(resolvedCommand, spec.args, {

View file

@ -37,25 +37,6 @@
padding: 8px;
}
.omp-vault-cockpit-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.omp-vault-cockpit-header h3 {
margin: 0;
font-size: var(--font-ui-medium);
}
.omp-vault-cockpit-primary-row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto 28px 28px;
align-items: center;
gap: 8px;
}
.omp-vault-cockpit-status {
border: 1px solid var(--background-modifier-border);
border-radius: 999px;
@ -76,6 +57,13 @@
color: var(--text-error);
}
.omp-vault-cockpit-primary-row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto 28px;
align-items: center;
gap: 8px;
}
.omp-vault-cockpit-actions-toggle {
display: inline-flex;
align-items: center;
@ -94,70 +82,32 @@
line-height: 1;
}
.omp-vault-cockpit-actions {
display: grid;
flex: 0 1 auto;
gap: 10px;
min-height: 0;
max-height: 40%;
overflow-y: auto;
padding-top: 2px;
}
.omp-vault-cockpit-section {
display: grid;
gap: 5px;
}
.omp-vault-cockpit-section-title {
color: var(--text-muted);
font-size: var(--font-ui-smaller);
font-weight: var(--font-semibold);
}
.omp-vault-cockpit-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(92px, 1fr));
gap: 5px;
}
.omp-vault-cockpit-command {
min-height: 26px;
min-height: 30px;
padding: 3px 7px;
border-color: var(--background-modifier-border);
background-color: var(--background-modifier-border);
border-color: color-mix(in srgb, var(--background-primary) 82%, black);
background-color: color-mix(in srgb, var(--background-primary) 88%, black);
color: var(--text-normal);
font-size: var(--font-ui-smaller);
font-weight: var(--font-semibold);
line-height: 1.2;
white-space: normal;
}
.omp-vault-cockpit-command.omp-vault-cockpit-refresh {
min-height: 30px;
border-color: color-mix(in srgb, var(--background-primary) 82%, black);
background-color: color-mix(in srgb, var(--background-primary) 88%, black);
color: var(--text-normal);
font-weight: var(--font-semibold);
}
.omp-vault-cockpit-command.omp-vault-cockpit-refresh:hover {
.omp-vault-cockpit-command:hover {
border-color: color-mix(in srgb, var(--background-primary) 72%, black);
background-color: color-mix(in srgb, var(--background-primary) 80%, black);
color: var(--text-normal);
}
.omp-vault-cockpit-command:hover {
background-color: var(--background-modifier-border-hover);
}
.omp-vault-cockpit-output {
display: flex;
flex: 1 1 auto;
flex: 1 1 min-content;
flex-direction: column;
gap: 6px;
min-height: 0;
margin-top: 2px;
overflow: hidden;
overflow: auto;
}
.omp-vault-cockpit-output-row {
@ -173,6 +123,12 @@
font-size: var(--font-ui-smaller);
}
.omp-vault-cockpit-git-commit {
min-height: 24px;
padding: 2px 7px;
font-size: var(--font-ui-smaller);
}
.omp-vault-cockpit-log-actions {
display: flex;
gap: 6px;
@ -192,8 +148,8 @@
}
.omp-vault-cockpit-log {
flex: 1 1 auto;
min-height: 0;
flex: 1 1 0;
min-height: 120px;
overflow: auto;
border: 1px solid var(--background-modifier-border);
border-radius: 6px;

View file

@ -1,45 +1,69 @@
import { describe, expect, it } from "vitest";
import { FALLBACK_VAULT_COMMANDS, parseVaultCommandMetadata } from "../src/vault-command-metadata";
import { parseVaultCommandMetadata } from "../src/vault-command-metadata";
describe("vault command metadata", () => {
it("loads valid command metadata", () => {
it("keeps legacy command metadata compatible", () => {
const result = parseVaultCommandMetadata(
JSON.stringify([
{
id: "refresh",
label: "Refresh",
description: "Refresh everything.",
description: "Run refresh",
args: ["refresh"],
aliases: ["r"],
},
])
);
expect(result.warning).toBeUndefined();
expect(result.commands).toEqual([
{
id: "refresh",
label: "Refresh",
description: "Refresh everything.",
args: ["refresh"],
aliases: ["r"],
},
]);
expect(result.commands[0]).toMatchObject({
id: "refresh",
palette: undefined,
promptArgs: undefined,
});
});
it("falls back when JSON cannot be parsed", () => {
const result = parseVaultCommandMetadata("{nope");
expect(result.commands).toBe(FALLBACK_VAULT_COMMANDS);
expect(result.warning).toContain("Could not parse");
});
it("falls back when command args are not an array of strings", () => {
it("parses palette flags and prompt args", () => {
const result = parseVaultCommandMetadata(
JSON.stringify([{ id: "refresh", label: "Refresh", description: "Refresh", args: "refresh" }])
JSON.stringify([
{
id: "folder-register",
label: "Folder Register",
description: "Register a folder",
args: ["folder", "register"],
cockpit: false,
palette: true,
promptArgs: [
{
label: "Context folder",
placeholder: "impression",
argName: "name",
},
],
},
])
);
expect(result.warning).toBeUndefined();
expect(result.commands[0]).toMatchObject({
cockpit: false,
palette: true,
promptArgs: [{ label: "Context folder", placeholder: "impression", argName: "name" }],
});
});
it("rejects malformed prompt args", () => {
const result = parseVaultCommandMetadata(
JSON.stringify([
{
id: "folder-register",
label: "Folder Register",
description: "Register a folder",
args: ["folder", "register"],
promptArgs: [{ placeholder: "impression" }],
},
])
);
expect(result.commands).toBe(FALLBACK_VAULT_COMMANDS);
expect(result.warning).toContain("invalid command");
});
});