-
Notifications
You must be signed in to change notification settings - Fork 41
example register command
The AI Assistant palette (⌘K / Ctrl+K) is extensible. Plugins can contribute slash-commands with wp.os.registerCommand(). Typing / in the palette shows every registered command; the handler receives whatever the user typed after the slug.
Registrations are live — if the palette is open when you call registerCommand, the new command shows up in the list immediately, no page reload required.
The smallest possible example. Types /echo hello → assistant replies hello.
my-plugin.php
<?php
/** Plugin Name: My Echo Command */
defined( 'ABSPATH' ) || exit;
add_action( 'admin_enqueue_scripts', function () {
wp_enqueue_script(
'my-echo',
plugins_url( 'my-echo.js', __FILE__ ),
array( 'openstation' ), // <- hooks into the shell
'1.0.0',
true
);
} );my-echo.js
( function () {
// Wait until `wp.os` is available — the shell script loads
// independently of this one, so we use the `os.init`
// action which fires after the public API is mounted.
wp.os.ready( function () {
wp.os.registerCommand( {
slug: 'echo',
label: 'Echo',
description: 'Repeat the arguments back as a message.',
hint: '[text]',
icon: 'dashicons-format-chat',
run: function ( args ) {
return args.trim() || 'Usage: /echo [text]';
},
} );
} );
} )();Press ⌘K, type /echo hello world → the assistant shows hello world.
Type /echo with no argument → it shows the usage hint.
A more realistic command: parses a post ID argument, hits a plugin REST endpoint, and reports success.
my-comments.js
wp.os.ready( function () {
wp.os.registerCommand( {
slug: 'turn_on_comments',
label: 'Turn on comments',
description: 'Re-enable the comments section on a given post.',
hint: '[post id]',
icon: 'dashicons-admin-comments',
run: async function ( args, ctx ) {
const id = parseInt( args.trim(), 10 );
if ( ! id ) {
return 'Usage: /turn_on_comments [post id]';
}
const res = await fetch(
'/wp-json/my-plugin/v1/enable-comments/' + id,
{
method: 'POST',
headers: { 'X-WP-Nonce': openStationConfig.restNonce },
}
);
if ( ! res.ok ) {
return 'Failed to enable comments — status ' + res.status;
}
// Dismiss the palette since we're done.
ctx.close();
return 'Comments enabled on post **' + id + '**.';
},
} );
} );Note the markdown support — **bold**, *italic*, [links](https://…), inline `code`, bullet and ordered lists all render in the response bubble.
Commands don't have to return a message. Calling ctx.openInWindow() and returning void is a clean shortcut for "open this page and get out of my way".
wp.os.registerCommand( {
slug: 'open_dashboard',
label: 'Open the Dashboard',
icon: 'dashicons-dashboard',
run: ( _args, ctx ) => {
ctx.openInWindow( '/wp-admin/index.php', 'Dashboard', 'dashicons-dashboard' );
ctx.close();
// No return value → silent, no bubble.
},
} );The shell ships with one built-in: /open [window], which autocompletes every admin menu entry (dock + taskbar). Plugins that register native windows or custom destinations add themselves to its list via the os.open-command.items filter:
wp.hooks.addFilter(
'os.open-command.items',
'my-plugin/jorvy-in-open',
function ( items ) {
return [
...items,
{
id: 'jorvy',
label: 'Jorvy',
description: 'Marvel quotes window',
icon: 'dashicons-star-filled',
open: () => {
// Focus if already open, otherwise open fresh.
wp.os.registerWindow( {
id: 'jorvy',
title: 'Jorvy',
icon: 'dashicons-star-filled',
render: ( body ) => renderJorvy( body ),
} );
},
},
];
}
);The filter runs on every /open keystroke, so you can show/hide entries dynamically — e.g. only contribute your entry when the user has a specific capability:
wp.hooks.addFilter( 'os.open-command.items', 'my-plugin/gate', ( items ) => {
if ( ! openStationConfig.currentUserIsAdmin ) {
return items;
}
return [ ...items, { id: 'admin-tools', label: 'Admin Tools', ... } ];
} );For commands whose arguments come from a finite list, define a suggest() function. The palette will render it under the input as the user types; ↑/↓ to navigate, Tab to fill, Enter to commit.
wp.os.registerCommand( {
slug: 'switch_theme',
label: 'Switch theme',
hint: '[theme slug]',
icon: 'dashicons-admin-appearance',
// Suggestions are the static list of installed themes — loaded
// once at registration time for this example.
suggest: ( args ) => {
const themes = openStationConfig.installedThemes || []; // hypothetical
const q = args.trim().toLowerCase();
return themes
.filter( ( t ) => t.name.toLowerCase().includes( q ) )
.map( ( t ) => ( {
value: t.slug,
label: t.name,
description: t.author,
icon: 'dashicons-admin-appearance',
} ) );
},
run: async ( slug, ctx ) => {
await fetch( `/wp-json/my-plugin/v1/switch-theme/${ slug }`, {
method: 'POST',
headers: { 'X-WP-Nonce': openStationConfig.restNonce },
} );
ctx.close();
return `Switched to **${ slug }**.`;
},
} );suggest() may also return a Promise of suggestions — useful when the list comes from a REST call (search users, posts, etc.). The assistant handles async cleanly: older in-flight suggest() results are discarded when the user types something new.
A destructive command that uses every plugin point: ctx.confirm() for the user prompt, windowManager.closeAll() for the batch op, and the os.windows.close-all filter so any plugin can keep specific windows alive.
wp.os.ready( function () {
wp.os.registerCommand( {
slug: 'close_all_windows',
label: 'Close all windows',
description: 'Close every open window on every desktop.',
icon: 'dashicons-dismiss',
run: async ( _args, ctx ) => {
const before = wp.os.windowManager.getAll().length;
if ( before === 0 ) return 'No windows are open.';
const ok = await ctx.confirm(
'Close every open window?',
'You\'ll lose any unsaved state inside iframe windows.'
);
if ( ! ok ) return 'Cancelled.';
const closed = wp.os.windowManager.closeAll();
ctx.close();
return `Closed **${ closed }** window${ closed === 1 ? '' : 's' }.`;
},
} );
} );
// Optional protect-list — keep the OpenStation Preferences window alive.
wp.hooks.addFilter(
'os.windows.close-all',
'my-plugin/keep-os-settings',
( windows ) => windows.filter( ( w ) => w.id !== 'os-settings' )
);exceptIds on the call site does the same thing:
wp.os.windowManager.closeAll( { exceptIds: [ 'os-settings' ] } );The difference is scope: exceptIds applies only to one call site; the filter applies to every batch close anywhere on the page.
| Method | What it does |
|---|---|
ctx.close() |
Dismiss the AI Assistant panel. |
ctx.openInWindow( url, title, icon? ) |
Open a wp-admin URL in a legacy iframe window inside the desktop. |
ctx.confirm( message, details? ) |
Prompt the user to confirm a destructive action. Returns Promise<boolean>. |
| Return | Renders as |
|---|---|
undefined / void
|
Nothing (silent success — use with ctx.close()) |
"a string" |
Chat bubble with the string as the message (markdown supported) |
{ message, answer_type?, admin_links?, entity? } |
Full AI-answer shape. admin_links render as clickable cards; entity as an entity card. |
- Thrown errors in
runare caught automatically and rendered as an error bubble — your command can't crash the panel. - Slugs must match
/^[a-z0-9_/-]+$/(slashes allowed forvendor/sub-idnamespacing) — invalid registrations log a console warning and are silently dropped. - Re-registering the same slug replaces the previous definition (matches WordPress's
register_*semantics). - The palette list re-renders live when your plugin registers commands asynchronously (e.g. after a REST fetch).
- JavaScript reference ›
registerCommand - React to window events — for event-driven plugin UI that isn't a slash-command.
This wiki is generated from the docs/ directory — edits made here are overwritten by the next sync.
To change a page, open a pull request against docs/.
Guides
- Development guide
- Releasing openstation
- Agents security model
- API Index
- Architecture
- Bridge protocol — wiring overview
- <os-*> component reference
- Native Desktop Host — Experimental
- Desktop themes
- Dock customization — two registries, one mental model
- The event-driven framework
- Files on the Desktop
- Folder sharing
- Getting Started
- Hooks Reference
- Icons
- JavaScript Reference
- The Living Tree — algorithm definition
- Mio
- Native Windows & Framework Interop
- Plugin compatibility layer
- Progressive Web App (PWA)
- Station Home
- Using openstation from your own plugin
Migration notes
- Migration: built-in activity channels move to the os/ namespace
- Migration: window, wallpaper and widget bundles load on demand
- Migration — the navigation model
- Migration: a native window's tabs move to the window chrome
All examples
- AI Agents — extend and invoke from a plugin
- wp.os.ai.ask() — programmatic AI Copilot
- Tune the AI model config
- Custom arrange-menu action
- Open a child window its owner can't cover
- Style a specific admin page inside the iframe
- Code Blue — register your plugin's log file
- Open a file in the Code editor (deep-link from any window)
- Connect to a window — title-bar button + iframe pub/sub
- Content changes — live-refresh every window listing your type
- Custom window chrome (Experimental)
- Register a custom unfocused-window effect
- Example: render a data table
- Real file storage — react to uploads, gate policy, share from PHP
- React to a window being set free onto the real desktop
- Cross-window devtools — instrumentation primitives
- Add a dock item with a badge
- Decorate the dock without forking the renderer
- Replace the dock rail entirely
- Retune the Drafts widget's AI writing assistant
- Gate OpenStation by role
- Iframe-initiated window opens
- Build a feed reader without the bookkeeping
- Inject data into openStationConfig
- Render a list without losing clicks — renderKeyedList()
- Example: layout primitives (body → panel → row → col)
- Use <os-*> components from a plugin that ships as a zip
- Restyle and drive Mio
- Add an action that works on a whole selection
- WP Explorer — custom post types and their folder
- Add an action button to a WP Explorer preview pane
- Example: native Posts window
- Example: native window with tabs
- Native windows
- Customize note → post conversion
- Send a notification
- OAuth relay — connect to an external service
- OS-file drop
- <os-flyout> — window-scoped sliding card
- Plugins window — extras
- Track who's around — wp.os.presence
- Example: progress bar
- PWA install — surface your own button
- React to window events
- Example: extend the Trash
- Register a slash-command
- Register a desktop theme from a plugin
- Register a game
- Example: register a desktop icon (Jorvy)
- Register a wallpaper
- Register a widget
- Related entities — extend the title bar's "Related" menu
- The native-window render ctx
- Programmatic folder sharing
- Share state across multi-bundle plugins — wp.os.createSharedStore()
- Example: loading spinner
- Add an opt-in card to Station Home
- Accept drops on your desktop icon
- Give a tile two icons, one per state
- Add a row to a window's ⋯ menu
- Example: window activity & the status ring
- Window controls
- Subscribe to window lifecycle events
- Window links — relate windows and restyle the ties (Experimental)
- Window loading state — spinner overlay & ready signal
- Show a banner at the top of a window
- Pulse a window's icon — Window.requestAttention()
- Register a custom window reveal
- Window slots
- Window themes
- Native window with bundle-bound config