Skip to content

Commit 375a936

Browse files
committed
feat: add missing slash commands
/compact /undo /redo /share /unshare This should fix sudo-tee#36
1 parent 08a5d56 commit 375a936

11 files changed

Lines changed: 368 additions & 13 deletions

File tree

README.md

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ Available icon keys (see implementation at lua/opencode/ui/icons.lua lines 7-29)
265265

266266
### Available Actions
267267

268-
The plugin provides the following actions that can be triggered via keymaps, commands, or the Lua API:
268+
The plugin provides the following actions that can be triggered via keymaps, commands, slash commands (typed in the input window), or the Lua API:
269269

270270
| Action | Default keymap | Command | API Function |
271271
| --------------------------------------------------- | -------------- | ---------------------------------------- | ---------------------------------------------------------------------------- |
@@ -287,6 +287,10 @@ The plugin provides the following actions that can be triggered via keymaps, com
287287
| Revert current file changes last prompt | `<leader>ort` | `:OpencodeRevertThisLastPrompt` | `require('opencode.api').diff_revert_this_last_prompt()` |
288288
| Revert all file changes since last session | `<leader>orA` | `:OpencodeRevertAllSession` | `require('opencode.api').diff_revert_all_session()` **Not implemented yet** |
289289
| Revert current file changes last session | `<leader>orT` | `:OpencodeRevertThisSession` | `require('opencode.api').diff_revert_this_session()` **Not implemented yet** |
290+
| Revert all files to a specific snapshot | - | `:OpencodeRevertAllToSnapshot` | `require('opencode.api').diff_revert_all(snapshot_id)` |
291+
| Revert current file to a specific snapshot | - | `:OpencodeRevertThisToSnapshot` | `require('opencode.api').diff_revert_this(snapshot_id)` |
292+
| Restore a file to a restore point | - | `:OpencodeRestoreSnapshotFile` | `require('opencode.api').diff_restore_snapshot_file(restore_point_id)` |
293+
| Restore all files to a restore point | - | `:OpencodeRestoreSnapshotAll` | `require('opencode.api').diff_restore_snapshot_all(restore_point_id)` |
290294
| Initialize/update AGENTS.md file | - | `:OpencodeInit` | `require('opencode.api').initialize()` |
291295
| Run prompt (continue session) [Run opts](#run-opts) | - | `:OpencodeRun <prompt> <opts>` | `require('opencode.api').run("prompt", opts)` |
292296
| Run prompt (new session) [Run opts](#run-opts) | - | `:OpencodeRunNewSession <prompt> <opts>` | `require('opencode.api').run_new_session("prompt", opts)` |
@@ -296,6 +300,11 @@ The plugin provides the following actions that can be triggered via keymaps, com
296300
| Select and switch mode/agent | - | `:OpencodeAgentSelect` | `require('opencode.api').select_agent()` |
297301
| Display list of availale mcp servers | - | `:OpencodeMCP` | `require('opencode.api').mcp()` |
298302
| Run user commands | - | `:OpencodeRunUserCommand` | `require('opencode.api').run_user_command()` |
303+
| Share current session and get a link | - | `:OpencodeShareSession` / `/share` | `require('opencode.api').share()` |
304+
| Unshare current session (disable link) | - | `:OpencodeUnshareSession` / `/unshare` | `require('opencode.api').unshare()` |
305+
| Compact current session (summarize) | - | `:OpencodeCompactSession` / `/compact` | `require('opencode.api').compact_session()` |
306+
| Undo last opencode action | - | `:OpencodeUndo` / `/undo` | `require('opencode.api').undo()` |
307+
| Redo last opencode action | - | `:OpencodeRedo` / `/redo` | `require('opencode.api').redo()` |
299308
| Insert mention (file/ agent) | `@` | - | - |
300309
| [Pick a file and add to context](#file-mentions) | `~` | - | - |
301310
| Navigate to next message | `]]` | - | - |
@@ -305,6 +314,8 @@ The plugin provides the following actions that can be triggered via keymaps, com
305314
| Toggle input/output panes | `<tab>` | - | - |
306315
| Swap Opencode pane left/right | `<leader>ox` | `:OpencodeSwapPosition` | `require('opencode.api').swap_position()` |
307316

317+
---
318+
308319
### Run opts
309320

310321
You can pass additional options when running a prompt via command or API:
@@ -371,20 +382,36 @@ You can create custom agents through your opencode config file. Each agent can h
371382

372383
See [Opencode Agents Documentation](https://opencode.ai/docs/agents/) for full configuration options.
373384

374-
## User Commands
385+
## User Commands and Slash Commands
386+
387+
You can run predefined user commands and built-in slash commands from the input window by typing `/`. This opens a command picker where you can select a command to execute. The output of the command will be included in your prompt context.
388+
389+
**Built-in slash commands** include:
375390

376-
You can run predefined user commands from the input window by typing `/`. This will open a command picker where you can select a command to execute. The output of the command will be included in your prompt context.
391+
- `/share` — Share the current session and get a link
392+
- `/unshare` — Unshare the current session
393+
- `/compact` — Compact (summarize) the current session
394+
- `/undo` — Undo the last opencode action
395+
- `/redo` — Redo the last undone action
396+
- `/init` — Initialize/update AGENTS.md
397+
- `/help` — Show help
398+
- `/mcp` — Show MCP servers
399+
- `/models` — Switch provider/model
400+
- `/sessions` — Switch session
401+
- `/child-sessions` — Switch to a child session
402+
- `/agent` — Switch agent/mode
403+
- ...and more
377404

378-
To configure user commands
405+
**User commands** are custom scripts you define. They are loaded from:
379406

380-
### Store command files in these locations:
407+
- `.opencode/command/` (project-specific)
408+
- `command/` (global, in config directory)
381409

382-
`.opencode/command/` - Project-specific commands
383-
`command/` - Global commands in config directory
410+
You can also run user commands by name with `:OpencodeRunUserCommand <name>` or `/run_user_command <name>`.
384411

385412
<img src="https://i.imgur.com/YQhhoPS.png" alt="Opencode.nvim contextual actions" width="90%" />
386413

387-
See [User Commands Documentation](https://opencode.ai/docs/commands/) for more details details.
414+
See [User Commands Documentation](https://opencode.ai/docs/commands/) for more details.
388415

389416
## 📸 Contextual Actions for Snapshots
390417

@@ -451,12 +478,10 @@ The plugin defines several highlight groups that can be customized to match your
451478
If you're new to opencode:
452479

453480
1. **What is Opencode?**
454-
455481
- Opencode is an AI coding agent built for the terminal
456482
- It offers powerful AI assistance with extensible configurations such as LLMs and MCP servers
457483

458484
2. **Installation:**
459-
460485
- Visit [Install Opencode](https://opencode.ai/docs/#install) for installation and configuration instructions
461486
- Ensure the `opencode` command is available after installation
462487

lua/opencode/api.lua

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,129 @@ function M.run_user_command(name)
381381
})
382382
end
383383

384+
--- Compacts the current session by removing unnecessary data.
385+
--- @param current_session? Session The session to compact. Defaults to the active session.
386+
function M.compact_session(current_session)
387+
current_session = current_session or state.active_session
388+
if not current_session then
389+
vim.notify('No active session to compact', vim.log.levels.WARN)
390+
return
391+
end
392+
393+
local providerId, modelId = state.current_model:match('^(.-)/(.+)$')
394+
core.run_server_api('/session/' .. current_session.name .. '/summarize', 'POST', {
395+
providerID = providerId,
396+
modelID = modelId,
397+
}, {
398+
on_done = function()
399+
vim.schedule(function()
400+
vim.notify('Session compacted successfully', vim.log.levels.INFO)
401+
end)
402+
end,
403+
on_error = function(err)
404+
vim.schedule(function()
405+
vim.notify('Failed to compact session: ' .. vim.inspect(err), vim.log.levels.ERROR)
406+
end)
407+
end,
408+
})
409+
end
410+
411+
function M.share()
412+
if not state.active_session then
413+
vim.notify('No active session to share', vim.log.levels.WARN)
414+
return
415+
end
416+
417+
core.run_server_api('/session/' .. state.active_session.name .. '/share', 'POST', {}, {
418+
on_done = function(response)
419+
vim.schedule(function()
420+
if response and response.share.url then
421+
vim.fn.setreg('+', response.share.url)
422+
vim.notify('Session link copied to clipboard successfully: ' .. response.share.url, vim.log.levels.INFO)
423+
else
424+
vim.notify('Session shared but no link received', vim.log.levels.WARN)
425+
end
426+
end)
427+
end,
428+
on_error = function(err)
429+
vim.schedule(function()
430+
vim.notify('Failed to share session: ' .. vim.inspect(err), vim.log.levels.ERROR)
431+
end)
432+
end,
433+
})
434+
end
435+
436+
function M.unshare()
437+
if not state.active_session then
438+
vim.notify('No active session to unshare', vim.log.levels.WARN)
439+
return
440+
end
441+
442+
core.run_server_api('/session/' .. state.active_session.name .. '/share', 'DELETE', {}, {
443+
on_done = function()
444+
vim.schedule(function()
445+
vim.notify('Session unshared successfully', vim.log.levels.INFO)
446+
end)
447+
end,
448+
on_error = function(err)
449+
vim.schedule(function()
450+
vim.notify('Failed to unshare session: ' .. vim.inspect(err), vim.log.levels.ERROR)
451+
end)
452+
end,
453+
})
454+
end
455+
456+
function M.undo()
457+
if not state.active_session then
458+
vim.notify('No active session to undo', vim.log.levels.WARN)
459+
return
460+
end
461+
local last_user_message = state.last_user_message
462+
if not last_user_message then
463+
vim.notify('No user message to undo', vim.log.levels.WARN)
464+
return
465+
end
466+
467+
core.run_server_api('/session/' .. state.active_session.name .. '/revert', 'POST', {
468+
messageID = last_user_message.id,
469+
}, {
470+
on_done = function(response)
471+
state.active_session.revert = response.revert
472+
vim.schedule(function()
473+
vim.notify('Last message undone successfully', vim.log.levels.INFO)
474+
ui.render_output(true)
475+
end)
476+
end,
477+
on_error = function(err)
478+
vim.schedule(function()
479+
vim.notify('Failed to undo last message: ' .. vim.inspect(err), vim.log.levels.ERROR)
480+
end)
481+
end,
482+
})
483+
end
484+
485+
function M.redo()
486+
if not state.active_session then
487+
vim.notify('No active session to undo', vim.log.levels.WARN)
488+
return
489+
end
490+
491+
core.run_server_api('/session/' .. state.active_session.name .. '/unrevert', 'POST', {}, {
492+
on_done = function(response)
493+
state.active_session.revert = response.revert
494+
vim.schedule(function()
495+
vim.notify('Last message rerterted successfully', vim.log.levels.INFO)
496+
ui.render_output(true)
497+
end)
498+
end,
499+
on_error = function(err)
500+
vim.schedule(function()
501+
vim.notify('Failed to undo last message: ' .. vim.inspect(err), vim.log.levels.ERROR)
502+
end)
503+
end,
504+
})
505+
end
506+
384507
-- Command definitions that call the API functions
385508
M.commands = {
386509
swap_position = {
@@ -713,6 +836,71 @@ M.commands = {
713836
end,
714837
args = true,
715838
},
839+
840+
compact_session = {
841+
name = 'OpencodeCompactSession',
842+
desc = 'Compacts the current session by removing unnecessary data',
843+
fn = function()
844+
if not state.active_session then
845+
vim.notify('No active session to compact', vim.log.levels.WARN)
846+
return
847+
end
848+
M.compact_session(state.active_session)
849+
end,
850+
slash_cmd = '/compact',
851+
},
852+
853+
share_session = {
854+
name = 'OpencodeShareSession',
855+
desc = 'Share the current session and get a shareable link',
856+
fn = function()
857+
if not state.active_session then
858+
vim.notify('No active session to share', vim.log.levels.WARN)
859+
return
860+
end
861+
M.share()
862+
end,
863+
slash_cmd = '/share',
864+
},
865+
866+
unshare_session = {
867+
name = 'OpencodeUnshareSession',
868+
desc = 'Unshare the current session, disabling the shareable link',
869+
fn = function()
870+
if not state.active_session then
871+
vim.notify('No active session to unshare', vim.log.levels.WARN)
872+
return
873+
end
874+
M.unshare()
875+
end,
876+
slash_cmd = '/unshare',
877+
},
878+
879+
undo = {
880+
name = 'OpencodeUndo',
881+
desc = 'Undo last opencode action',
882+
fn = function()
883+
if not state.active_session then
884+
vim.notify('No active session to undo', vim.log.levels.WARN)
885+
return
886+
end
887+
M.undo()
888+
end,
889+
slash_cmd = '/undo',
890+
},
891+
892+
redo = {
893+
name = 'OpencodeRedo',
894+
desc = 'Redo last opencode action',
895+
fn = function()
896+
if not state.active_session then
897+
vim.notify('No active session to undo', vim.log.levels.WARN)
898+
return
899+
end
900+
M.redo()
901+
end,
902+
slash_cmd = '/redo',
903+
},
716904
}
717905

718906
function M.get_slash_commands()

lua/opencode/config.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ M.defaults = {
6565
select_child_session = '<leader>oS',
6666
debug_message = '<leader>oD',
6767
debug_output = '<leader>oO',
68+
debug_session = '<leader>ods',
6869
},
6970
},
7071
ui = {

lua/opencode/session.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ function M.create_session_object(session_json)
8686
cache_path = vim.fn.stdpath('cache') .. '/opencode/session/' .. session_json.id,
8787
snapshot_path = M.get_workspace_snapshot_path(),
8888
project_id = M.project_id(),
89+
revert = session_json.revert or nil,
8990
}
9091
end
9192

lua/opencode/state.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ local config = require('opencode.config').get()
2626
---@field current_model string|nil
2727
---@field messages Message[]|nil
2828
---@field current_message Message|nil
29+
---@field last_user_message Message|nil
2930
---@field cost number
3031
---@field tokens_count number
3132
---@field opencode_server_job OpencodeServer
@@ -59,6 +60,7 @@ local _state = {
5960
-- messages
6061
messages = nil,
6162
current_message = nil,
63+
last_user_message = nil,
6264
cost = 0,
6365
tokens_count = 0,
6466
-- job

lua/opencode/types.lua

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@
2222
---@field agent string
2323
---@field model string
2424

25+
---@class SessionRevertInfo
26+
---@field messageID string
27+
---@field partID? string
28+
---@field snapshot string
29+
---@field diff string
30+
2531
---@class Session
2632
---@field workspace string
2733
---@field description string
@@ -34,6 +40,7 @@
3440
---@field snapshot_path string
3541
---@field cache_path string
3642
---@field workplace_slug string
43+
---@field revert? SessionRevertInfo
3744

3845
---@class OpencodeKeymapGlobal
3946
---@field toggle string
@@ -67,6 +74,7 @@
6774
---@field next_prompt_history string
6875
---@field focus_input string
6976
---@field debug_message string
77+
---@field debug_session string
7078
---@field debug_output string
7179
---@field switch_mode string
7280
---@field select_child_session string

lua/opencode/ui/debug_helper.lua

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,15 @@ function M.debug_message()
2626
M.open_json_file(metadata.message)
2727
end
2828

29-
return M
29+
function M.debug_session()
30+
local session = require('opencode.session')
31+
local session_path = session.get_workspace_session_path()
32+
if not state.active_session then
33+
print('No active session')
34+
return
35+
end
36+
vim.api.nvim_set_current_win(state.last_code_win_before_opencode)
37+
vim.cmd('e ' .. session_path .. '/' .. state.active_session.name .. '.json')
38+
end
3039

40+
return M

0 commit comments

Comments
 (0)