From 6da8ebf4213ebd275cbf433dff2455e7b5ede83a Mon Sep 17 00:00:00 2001 From: Matt Derman Date: Sat, 30 May 2026 16:32:10 +0200 Subject: [PATCH] Simplify vault cockpit commands --- README.md | 4 +- src/main.ts | 108 +++++++++++++++- src/vault-cockpit.ts | 176 ++++----------------------- src/vault-command-metadata.ts | 69 ++++++++++- src/vault-runner.ts | 2 +- styles.css | 88 ++++---------- tests/vault-command-metadata.test.ts | 70 +++++++---- 7 files changed, 263 insertions(+), 254 deletions(-) diff --git a/README.md b/README.md index 19e5c5e..26d1ec9 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/main.ts b/src/main.ts index 3f826a0..1bbb41b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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 { + await this.getVaultCockpitView(); + } + + async getVaultCockpitView(): Promise { 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 { + 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 { + 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 { + 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 { @@ -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); diff --git a/src/vault-cockpit.ts b/src/vault-cockpit.ts index c51c638..804a1d4 100644 --- a/src/vault-cockpit.ts +++ b/src/vault-cockpit.ts @@ -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(); @@ -52,9 +49,8 @@ export class VaultCockpitView extends ItemView { async onOpen(): Promise { 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"; diff --git a/src/vault-command-metadata.ts b/src/vault-command-metadata.ts index fcc00c3..e90470d 100644 --- a/src/vault-command-metadata.ts +++ b/src/vault-command-metadata.ts @@ -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 { 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); } diff --git a/src/vault-runner.ts b/src/vault-runner.ts index c1a1c52..998fe68 100644 --- a/src/vault-runner.ts +++ b/src/vault-runner.ts @@ -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, { diff --git a/styles.css b/styles.css index 5c9bf0f..b297c47 100644 --- a/styles.css +++ b/styles.css @@ -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; diff --git a/tests/vault-command-metadata.test.ts b/tests/vault-command-metadata.test.ts index 3a09345..26e1456 100644 --- a/tests/vault-command-metadata.test.ts +++ b/tests/vault-command-metadata.test.ts @@ -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"); }); });