Skip to content

Commit 91aef8a

Browse files
committed
feat(api): add /commands to show user commands
1 parent c3413f9 commit 91aef8a

File tree

3 files changed

+93
-3
lines changed

3 files changed

+93
-3
lines changed

lua/opencode/api.lua

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ function M.select_agent()
388388
end
389389

390390
function M.switch_mode()
391-
local modes = require('opencode.config_file').get_opencode_agents()
391+
local modes = require('opencode.config_file').get_opencode_agents() --[[@as string[] ]]
392392

393393
local current_index = util.index_of(modes, state.current_mode)
394394

@@ -512,6 +512,33 @@ function M.mcp()
512512
ui.render_lines(msg)
513513
end
514514

515+
function M.commands_list()
516+
local info = require('opencode.config_file')
517+
local commands = info.get_user_commands()
518+
if not commands then
519+
vim.notify('No user commands found. Please check your opencode config file.', vim.log.levels.WARN)
520+
return
521+
end
522+
523+
state.display_route = '/commands'
524+
M.open_input()
525+
526+
local msg = M.with_header({
527+
'### Available User Commands',
528+
'',
529+
'| Name | Description |',
530+
'|------|-------------|',
531+
})
532+
533+
for name, def in pairs(commands) do
534+
local desc = def.description or ''
535+
table.insert(msg, string.format('| %s | %s |', name, desc))
536+
end
537+
538+
table.insert(msg, '')
539+
ui.render_lines(msg)
540+
end
541+
515542
--- Runs a user-defined command by name.
516543
--- @param name string The name of the user command to run.
517544
--- @param args? string[] Additional arguments to pass to the command.
@@ -1008,6 +1035,11 @@ M.commands = {
10081035
fn = M.mcp,
10091036
},
10101037

1038+
commands_list = {
1039+
desc = 'Show user-defined commands',
1040+
fn = M.commands_list,
1041+
},
1042+
10111043
permission = {
10121044
desc = 'Respond to permissions (accept/accept_all/deny)',
10131045
completions = { 'accept', 'accept_all', 'deny' },
@@ -1032,6 +1064,7 @@ M.slash_commands_map = {
10321064
['/agent'] = { fn = M.select_agent, desc = 'Select agent mode' },
10331065
['/agents_init'] = { fn = M.initialize, desc = 'Initialize AGENTS.md session' },
10341066
['/child-sessions'] = { fn = M.select_child_session, desc = 'Select child session' },
1067+
['/commands'] = { fn = M.commands_list, desc = 'Show user-defined commands' },
10351068
['/compact'] = { fn = M.compact_session, desc = 'Compact current session' },
10361069
['/mcp'] = { fn = M.mcp, desc = 'Show MCP server configuration' },
10371070
['/models'] = { fn = M.configure_provider, desc = 'Switch provider/model' },

lua/opencode/core.lua

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,6 @@ function M.before_run(opts)
165165
local is_new_session = opts and opts.new_session or not state.active_session
166166
opts = opts or {}
167167

168-
169168
M.open({
170169
new_session = is_new_session,
171170
})
@@ -271,7 +270,7 @@ local function on_opencode_server()
271270
end
272271

273272
--- Switches the current mode to the specified agent.
274-
--- @param mode string The agent/mode to switch to
273+
--- @param mode string|nil The agent/mode to switch to
275274
--- @return boolean success Returns true if the mode was switched successfully, false otherwise
276275
function M.switch_to_mode(mode)
277276
if not mode or mode == '' then

tests/unit/api_spec.lua

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,4 +276,62 @@ describe('opencode.api', function()
276276
notify_stub:revert()
277277
end)
278278
end)
279+
280+
describe('/commands command', function()
281+
it('displays user commands when available', function()
282+
local config_file = require('opencode.config_file')
283+
local original_get_user_commands = config_file.get_user_commands
284+
285+
config_file.get_user_commands = function()
286+
return {
287+
['build'] = { description = 'Build the project' },
288+
['test'] = { description = 'Run tests' },
289+
['deploy'] = { description = 'Deploy to production' },
290+
}
291+
end
292+
293+
stub(ui, 'render_lines')
294+
stub(api, 'open_input')
295+
296+
api.commands_list()
297+
298+
assert.stub(api.open_input).was_called()
299+
assert.stub(ui.render_lines).was_called()
300+
301+
local render_args = ui.render_lines.calls[1].refs[1]
302+
local rendered_text = table.concat(render_args, '\n')
303+
304+
assert.truthy(rendered_text:match('Available User Commands'))
305+
assert.truthy(rendered_text:match('Description'))
306+
assert.truthy(rendered_text:match('build'))
307+
assert.truthy(rendered_text:match('Build the project'))
308+
assert.truthy(rendered_text:match('test'))
309+
assert.truthy(rendered_text:match('Run tests'))
310+
assert.truthy(rendered_text:match('deploy'))
311+
assert.truthy(rendered_text:match('Deploy to production'))
312+
313+
config_file.get_user_commands = original_get_user_commands
314+
end)
315+
316+
it('shows warning when no user commands exist', function()
317+
local config_file = require('opencode.config_file')
318+
local original_get_user_commands = config_file.get_user_commands
319+
320+
config_file.get_user_commands = function()
321+
return nil
322+
end
323+
324+
local notify_stub = stub(vim, 'notify')
325+
326+
api.commands_list()
327+
328+
assert.stub(notify_stub).was_called_with(
329+
'No user commands found. Please check your opencode config file.',
330+
vim.log.levels.WARN
331+
)
332+
333+
config_file.get_user_commands = original_get_user_commands
334+
notify_stub:revert()
335+
end)
336+
end)
279337
end)

0 commit comments

Comments
 (0)