Skip to content

Interface: UiService

Defined in: packages/sdk/src/ui-service.ts:330

The user-interaction domain, exposed as ExtensionContext.ui. The host renders the chrome; an extension only asks. Interactions today:

  • Native OS dialogspickFolder, pickFile, savePath. Thin wrappers over the platform dialogs the host owns; each resolves to an absolute path or null when the user cancels.
  • Notificationsnotify shows a transient toast, optionally with a title and action buttons (see NotifyOptions). The only way an extension can proactively message the user.
  • MenusshowMenu pops a context menu or button dropdown, themed to match the rest of the app.
  • Modal dialogsconfirm and prompt pop a host-owned modal and resolve on the user's choice; showModal pops one around your own custom content (a form or bespoke layout).
  • External linksopenExternal hands a URL to the OS (browser / mail client), the host's gateway to the world outside the app.

Mirrors VS Code's window.show*. More host-rendered chrome (quick-pick, progress) is planned — see the roadmap.

Methods

pickFolder()

ts
pickFolder(opts?): Promise<string | null>;

Defined in: packages/sdk/src/ui-service.ts:337

Show the native folder picker. Resolves to the chosen absolute path, or null if the user cancelled.

Parameters

opts?
defaultPath?

string

Absolute path to open the dialog at.

Returns

Promise<string | null>


pickFile()

ts
pickFile(opts?): Promise<string | null>;

Defined in: packages/sdk/src/ui-service.ts:345

Show the native open-file picker (single selection). Resolves to the chosen absolute path, or null if the user cancelled.

Parameters

opts?
defaultPath?

string

Absolute path to open the dialog at.

filters?

FileFilter[]

Restrict the selectable file types (see FileFilter).

Returns

Promise<string | null>


savePath()

ts
savePath(opts?): Promise<string | null>;

Defined in: packages/sdk/src/ui-service.ts:356

Show the native save dialog. Resolves to the chosen destination's absolute path, or null if the user cancelled.

Parameters

opts?
defaultPath?

string

Seeds the dialog's location and suggested filename.

filters?

FileFilter[]

Restrict the file-type dropdown (see FileFilter).

Returns

Promise<string | null>


notify()

ts
notify(
   level, 
   message, 
   options?): void;

Defined in: packages/sdk/src/ui-service.ts:391

Show a transient toast notification to the user. Fire-and-forget — the host renders it (and, for info / warn without actions, auto-dismisses it). level drives the icon and accent.

Pass NotifyOptions for a bold title, footer actions, or an explicit durationMs. Errors and toasts with actions stay until dismissed (so a "View details" action isn't lost to the timer); everything else auto-dismisses after ~4s.

Parameters

level

"info" | "warn" | "error"

message

string

options?

NotifyOptions

Returns

void

Example

ts
// a plain info toast (auto-dismisses)
ctx.ui.notify("info", "Theme exported.");

// an error with a title and an action that opens the full detail in a modal
ctx.ui.notify("error", String(err), {
  title: "Commit failed",
  actions: [
    {
      label: "View details",
      run: () =>
        ctx.ui.showModal((close) => <pre>{String(err)}</pre>, {
          title: "Commit failed",
          dismissible: true,
        }),
    },
  ],
});

showMenu()

ts
showMenu(opts): Promise<void>;

Defined in: packages/sdk/src/ui-service.ts:426

Pop a menu — the same themed primitive behind every context menu and dropdown in Silo. Supply the rows and where to place it (see ShowMenuOptions); the host renders it, runs the chosen item's run, and dismisses on outside-click or Escape.

Only one menu is open at a time — calling showMenu again replaces it, except that re-opening with the same anchor toggles it closed (a second click on a dropdown button dismisses it); opt out with toggle: false. Resolves once an item runs or the menu is dismissed.

Parameters

opts

ShowMenuOptions

Returns

Promise<void>

Example

ts
// A right-click context menu at the cursor.
element.addEventListener("contextmenu", (e) => {
  e.preventDefault();
  ctx.ui.showMenu({
    items: [
      { label: "Rename", run: rename },
      { type: "separator" },
      { label: "Delete", danger: true, run: del },
    ],
  });
});

// A dropdown anchored under a button.
ctx.ui.showMenu({ items, anchor: buttonEl });

confirm()

ts
confirm(opts): Promise<boolean>;

Defined in: packages/sdk/src/ui-service.ts:445

Pop a host-rendered confirm dialog and resolve to the user's choice — true for confirm, false for cancel. Always dismissible: Escape and backdrop-click both resolve false. The dialog stacks above all host chrome via the modal manager, so it works from anywhere.

Parameters

opts

ConfirmOptions

Returns

Promise<boolean>

Example

ts
if (await ctx.ui.confirm({
  title: "Delete workspace?",
  body: `"${name}" and its saved terminals will be permanently removed.`,
  confirmLabel: "Delete",
  danger: true,
})) {
  service.delete(id);
}

prompt()

ts
prompt(opts): Promise<string | null>;

Defined in: packages/sdk/src/ui-service.ts:456

Pop a host-rendered single-line input dialog and resolve to the entered string, or null if the user cancelled (Escape / backdrop / Cancel).

Parameters

opts

PromptOptions

Returns

Promise<string | null>

Example

ts
const name = await ctx.ui.prompt({ title: "Rename", initialValue: current });
if (name !== null) rename(name);

showModal()

ts
showModal<T>(render, options?): Promise<T | undefined>;

Defined in: packages/sdk/src/ui-service.ts:493

Pop a host-rendered modal around your own custom content — the escape hatch beyond confirm / UiService.prompt | prompt when you need a form or bespoke layout. The host owns the hard parts (backdrop, central z-stacking above all chrome, focus trap, restore-focus-on-close); you own the content.

Supply a render callback that receives a close function and returns the modal's content; wire your own buttons to close(result) (or close() to cancel). The returned promise resolves with the value passed to close, or undefined if the modal was dismissed (only possible when ModalOptions.dismissible is set) or close() was called with no argument — paralleling confirmfalse / promptnull. If you must tell "dismissed" from "closed with no result" apart, pass a distinct sentinel.

Not dismissible by default: unless you set ModalOptions.dismissible, Escape and backdrop-click do nothing and the modal stays open until your content calls close. A non-dismissible modal whose content never calls close leaves the promise pending forever — by design, so staged edits can't be lost to an accidental click-away.

Type Parameters

T

T = void

The result type your content resolves with via close.

Parameters

render

(close) => ReactNode

Returns the modal content; receives close to settle it.

options?

ModalOptions

Presentation options (see ModalOptions).

Returns

Promise<T | undefined>

Example

tsx
const changes = await ctx.ui.showModal<Changes>(
  (close) => (
    <MyForm onCancel={() => close()} onSave={(c) => close(c)} />
  ),
  { title: "Properties", size: "md" },
);
if (changes) apply(changes);

confirmWithDontShowAgain()

ts
confirmWithDontShowAgain(opts): Promise<boolean>;

Defined in: packages/sdk/src/ui-service.ts:524

Pop a confirm/info dialog with a persisted "Don't show this again" checkbox — the one shape UiService.confirm has no room for. If opts.storageKey is already set in the calling extension's global storage, resolves true immediately without opening anything.

Cancelling (or dismissing, in "confirm" mode) never persists the checkbox, even if it was checked — so an accidental cancel can't silently suppress a safety warning for good. Proceeding persists it iff it was checked.

Parameters

opts

ConfirmDontShowAgainOptions

See ConfirmDontShowAgainOptions.

Returns

Promise<boolean>

true if the user proceeded (or the dialog was already suppressed), false on cancel/dismiss.

Example

ts
const ok = await ctx.ui.confirmWithDontShowAgain({
  storageKey: "installSkill.dontShowAgain",
  title: "Install skill?",
  body: `This runs ${cmd} in a new terminal.`,
  confirmLabel: "Install",
  mode: { kind: "confirm" },
});
if (ok) runInstall();

openExternal()

ts
openExternal(url): Promise<void>;

Defined in: packages/sdk/src/ui-service.ts:553

Hand a URL to the operating system — open an http/https link in the user's default browser, or a mailto: link in their mail client. The host owns the privileged platform access; this is an extension's only sanctioned way to send the user out of the app.

Scheme-guarded. Only http:, https:, and mailto: URLs are opened; any other scheme (notably file: and javascript:) is rejected — the returned promise rejects, nothing is opened. This makes it safe to pass untrusted URLs (e.g. links inside a rendered Markdown document) straight through without first vetting the scheme yourself.

Parameters

url

string

The URL to open. Must be http:, https:, or mailto:.

Returns

Promise<void>

Throws

If url has any other scheme (or is unparseable).

Example

ts
// open a docs link in the browser
await ctx.ui.openExternal("https://getsilo.dev/docs");

// route a clicked Markdown link safely — bad schemes just reject
try {
  await ctx.ui.openExternal(href);
} catch {
  ctx.ui.notify("warn", "That link can't be opened.");
}

getActiveSelectionText()

ts
getActiveSelectionText(): string | null;

Defined in: packages/sdk/src/ui-service.ts:568

The text currently selected in the focused surface — the active editor or a focused terminal — or null when nothing is selected. Reads the most-recently-focused of the two, so a command (e.g. "Find in Files") can seed itself with whatever the user has highlighted regardless of which surface has focus. Returns null (never throws) when no surface is focused or the selection is empty.

Returns

string | null

Example

ts
const seed = ctx.ui.getActiveSelectionText();
if (seed) runSearch(seed);