Rework run into a nice to use shell

This commit is contained in:
Raven Scott
2026-08-13 14:27:19 -04:00
parent 0d08dfd0d2
commit 993514f796
12 changed files with 1652 additions and 177 deletions
@@ -28,6 +28,67 @@ var BARE_OS_DISCORD_COLOR = 0x5865f2
var BARE_OS_DISCORD_COLOR_OK = 0x57f287
var BARE_OS_DISCORD_COLOR_WARN = 0xfee75c
var BARE_OS_DISCORD_COLOR_ERR = 0xed4245
var BARE_OS_DISCORD_SH_TTL_MS = 30 * 60 * 1000
var BARE_OS_DISCORD_SH_SESSIONS = Object.create(null)
var BARE_OS_DISCORD_SH_BINS = null
var BARE_OS_DISCORD_SH_BINS_AT = 0
var BARE_OS_DISCORD_SH_EXEC_MS = 20000
var BARE_OS_DISCORD_SH_BUILTINS = [
'alias',
'unalias',
'barerc',
'cd',
'export',
'unset',
'set',
'true',
'false',
'echo',
'pwd',
'type',
'command',
'umask',
'readonly',
'test',
'[',
'jobs',
'wait',
':',
'help'
]
var BARE_OS_DISCORD_SH_TTY = {
edit: 1,
nano: 1,
vim: 1,
vi: 1,
btop: 1,
baretop: 1,
irc: 1,
chat: 1,
dhttop: 1,
swarmmap: 1,
summon: 1,
'holepunch-view': 1,
routeview: 1
}
var BARE_OS_DISCORD_SH_FLAGS = {
ls: ['-a', '-l', '-la', '-lh', '-h', '-1', '-F'],
grep: ['-i', '-v', '-n', '-r', '-E', '-F'],
find: ['-name', '-type', '-size', '-path'],
rm: ['-r', '-f', '-rf', '-v'],
cp: ['-r', '-v', '-n'],
mv: ['-v', '-n'],
cat: ['-n', '-A'],
head: ['-n'],
tail: ['-n', '-f'],
chmod: ['-R'],
chown: ['-R'],
df: ['-h', '-T'],
du: ['-h', '-s', '-a'],
ps: ['-e', '-f'],
mkdir: ['-p'],
tar: ['-xvf', '-cvf', '-tzf']
}
var BARE_OS_DISCORD_RUN_ALLOW = {
uname: 1,
whoami: 1,
@@ -826,7 +887,7 @@ function discordNavRows() {
{ id: 'sys:ps', label: 'Processes' },
{ id: 'net:summary', label: 'Network' },
{ id: 'say:compose', label: 'Say…', style: 3 },
{ id: 'run:compose', label: 'Run…' }
{ id: 'run:compose', label: 'Shell' }
])
const c = discordButtons([
{ id: 'edit:new', label: 'Edit file…', style: 1 },
@@ -1155,13 +1216,277 @@ async function discordSuggestMan(ctx, q) {
return discordFilterChoices(names, q)
}
function discordSuggestRun(q) {
const names = []
for (const k in BARE_OS_DISCORD_RUN_ALLOW) {
if (Object.prototype.hasOwnProperty.call(BARE_OS_DISCORD_RUN_ALLOW, k)) names.push(k)
function discordShKey(interaction) {
return discordInteractionUserId(interaction) || 'anon'
}
function discordShGc() {
const now = Date.now()
for (const k in BARE_OS_DISCORD_SH_SESSIONS) {
const r = BARE_OS_DISCORD_SH_SESSIONS[k]
if (!r || now - r.atMs > BARE_OS_DISCORD_SH_TTL_MS) delete BARE_OS_DISCORD_SH_SESSIONS[k]
}
names.sort()
return discordFilterChoices(names, q)
}
function discordShGet(interaction, ctx) {
discordShGc()
const key = discordShKey(interaction)
let rec = BARE_OS_DISCORD_SH_SESSIONS[key]
const env = discordCmdEnv(ctx)
const home = env.HOME || '/home/' + discordSessionUser(ctx)
if (!rec) {
rec = {
cwd: env.PWD || home || '~',
oldpwd: home,
hist: [],
lastCmd: '',
lastExit: 0,
lastMs: 0,
atMs: Date.now()
}
BARE_OS_DISCORD_SH_SESSIONS[key] = rec
}
rec.atMs = Date.now()
if (!rec.cwd) rec.cwd = env.PWD || home || '~'
return rec
}
function discordShPrettyCwd(ctx, cwd) {
const env = discordCmdEnv(ctx)
const home = env.HOME || '/home/' + discordSessionUser(ctx)
const c = String(cwd || '~')
if (c === '~' || c === home) return '~'
if (home && c.indexOf(home + '/') === 0) return '~' + c.slice(home.length)
return c
}
function discordShPrompt(ctx, rec) {
return (
discordSessionUser(ctx) +
'@' +
(discordCmdEnv(ctx).HOSTNAME || 'bare-os') +
':' +
discordShPrettyCwd(ctx, rec.cwd)
)
}
async function discordShPrepare(ctx, rec) {
const env = discordCmdEnv(ctx)
const cwd = rec.cwd || env.HOME || '~'
rec.cwd = cwd
env.PWD = cwd
if (ctx.env && ctx.env !== env) ctx.env.PWD = cwd
if (ctx.vfs && typeof ctx.vfs.chdir === 'function') {
try {
await ctx.vfs.chdir(cwd)
} catch {
/* keep env PWD */
}
}
}
function discordShHarvest(ctx, rec) {
const env = discordCmdEnv(ctx)
if (env.PWD) rec.cwd = String(env.PWD)
if (ctx.vfs && ctx.vfs.env && ctx.vfs.env.PWD) rec.cwd = String(ctx.vfs.env.PWD)
}
function discordShPushHist(rec, cmd, exit, ms, preview) {
rec.lastCmd = cmd
rec.lastExit = exit
rec.lastMs = ms
rec.hist = rec.hist.filter(function (h) {
return h && h.cmd !== cmd
})
rec.hist.unshift({
cmd: cmd,
exit: exit,
ms: ms,
preview: String(preview || '').slice(0, 80)
})
if (rec.hist.length > 25) rec.hist.length = 25
}
async function discordShCommandNames(ctx) {
const now = Date.now()
if (BARE_OS_DISCORD_SH_BINS && now - BARE_OS_DISCORD_SH_BINS_AT < 60000) {
return BARE_OS_DISCORD_SH_BINS
}
const set = Object.create(null)
for (let i = 0; i < BARE_OS_DISCORD_SH_BUILTINS.length; i++) {
set[BARE_OS_DISCORD_SH_BUILTINS[i]] = 1
}
for (const k in BARE_OS_DISCORD_RUN_ALLOW) {
if (Object.prototype.hasOwnProperty.call(BARE_OS_DISCORD_RUN_ALLOW, k) && k.indexOf(' ') < 0) {
set[k] = 1
}
}
if (ctx && ctx.shellAliases && typeof ctx.shellAliases === 'object') {
const aks = Object.keys(ctx.shellAliases)
for (let i = 0; i < aks.length; i++) set[aks[i]] = 1
}
try {
if (ctx && ctx.vfs && typeof ctx.vfs.readdir === 'function') {
const n = await ctx.vfs.readdir('/bin')
if (Array.isArray(n)) {
for (let i = 0; i < n.length; i++) set[String(n[i])] = 1
}
}
} catch {
/* ignore */
}
const man = await discordCmdReadText(ctx, '/share/man/man.json')
const j = discordTryJson(man)
if (j && Array.isArray(j.pages)) {
for (let i = 0; i < j.pages.length; i++) {
if (j.pages[i] && j.pages[i].name) set[String(j.pages[i].name)] = 1
}
}
const names = Object.keys(set).sort()
BARE_OS_DISCORD_SH_BINS = names
BARE_OS_DISCORD_SH_BINS_AT = now
return names
}
function discordShSplitLine(q) {
const s = String(q || '')
const m = /^(.*?)(\S*)$/.exec(s)
return { prefix: m ? m[1] : '', token: m ? m[2] : s }
}
function discordShFirstWord(q) {
const s = String(q || '').trim()
const i = s.search(/\s/)
return i < 0 ? s : s.slice(0, i)
}
async function discordShCompletePath(ctx, rec, token, dirsOnly) {
let dir = rec.cwd || '~'
let base = token
const slash = token.lastIndexOf('/')
if (slash >= 0) {
const head = token.slice(0, slash + 1)
base = token.slice(slash + 1)
if (head.charAt(0) === '/' || head.charAt(0) === '~') dir = head === '/' ? '/' : head.replace(/\/$/, '') || '/'
else dir = discordJoinPath(rec.cwd, head.replace(/\/$/, '') || '.')
}
let names = []
try {
if (ctx.vfs && typeof ctx.vfs.readdir === 'function') {
const n = await ctx.vfs.readdir(dir)
if (Array.isArray(n)) names = n
}
} catch {
names = []
}
const out = []
const want = String(base || '').toLowerCase()
for (let i = 0; i < names.length && out.length < 20; i++) {
const name = String(names[i])
if (want && name.toLowerCase().indexOf(want) !== 0) continue
const full = discordJoinPath(dir, name)
let isDir = false
try {
const st = await discordFmStat(ctx, full)
isDir = discordIsDirStat(st)
} catch {
isDir = false
}
if (dirsOnly && !isDir) continue
const shown = (slash >= 0 ? token.slice(0, slash + 1) : '') + name + (isDir ? '/' : '')
out.push({
label: shown,
value: shown,
description: isDir ? 'directory' : 'file'
})
}
return out
}
function discordShPushChoice(out, seen, name, value, description) {
const v = String(value || '').slice(0, 100)
if (!v || seen[v]) return
if (out.length >= 25) return
seen[v] = 1
out.push({
name: String(name || v).slice(0, 100),
value: v,
description: description ? String(description).slice(0, 100) : undefined
})
}
async function discordSuggestRun(ctx, interaction, q) {
const rec = discordShGet(interaction, ctx)
const raw = String(q || '')
const split = discordShSplitLine(raw)
const first = discordShFirstWord(raw)
const out = []
const seen = Object.create(null)
if (!raw.trim() || (!split.prefix && first === split.token)) {
for (let i = 0; i < rec.hist.length && out.length < 6; i++) {
const h = rec.hist[i]
if (!h || !h.cmd) continue
if (split.token && h.cmd.indexOf(split.token) !== 0) continue
discordShPushChoice(out, seen, '↵ ' + h.cmd, h.cmd, 'history · exit ' + h.exit)
}
const cmds = await discordShCommandNames(ctx)
const needle = split.token.toLowerCase()
for (let i = 0; i < cmds.length && out.length < 25; i++) {
if (needle && cmds[i].toLowerCase().indexOf(needle) !== 0) continue
const kind = BARE_OS_DISCORD_SH_TTY[cmds[i]]
? 'TUI (needs a real TTY)'
: ctx.shellAliases && ctx.shellAliases[cmds[i]]
? 'alias'
: BARE_OS_DISCORD_SH_BUILTINS.indexOf(cmds[i]) >= 0
? 'builtin'
: 'command'
discordShPushChoice(out, seen, cmds[i], cmds[i], kind)
}
return out
}
const flags = BARE_OS_DISCORD_SH_FLAGS[first]
if (split.token.charAt(0) === '-' && flags) {
for (let i = 0; i < flags.length && out.length < 25; i++) {
if (split.token && flags[i].indexOf(split.token) !== 0) continue
const line = (split.prefix + flags[i]).slice(0, 100)
discordShPushChoice(out, seen, line, line, first + ' flag')
}
return out
}
const pathish =
!split.token ||
split.token.charAt(0) === '.' ||
split.token.charAt(0) === '/' ||
split.token.charAt(0) === '~' ||
split.token.indexOf('/') >= 0 ||
first === 'cd' ||
first === 'ls' ||
first === 'cat' ||
first === 'rm' ||
first === 'mv' ||
first === 'cp' ||
first === 'mkdir' ||
first === 'stat' ||
first === 'head' ||
first === 'tail' ||
first === 'edit'
if (pathish) {
const paths = await discordShCompletePath(ctx, rec, split.token, first === 'cd')
for (let i = 0; i < paths.length && out.length < 25; i++) {
const line = (split.prefix + paths[i].value).slice(0, 100)
discordShPushChoice(out, seen, line, line, paths[i].description)
}
}
if (!out.length && split.token) {
const cmds = await discordShCommandNames(ctx)
const needle = split.token.toLowerCase()
for (let i = 0; i < cmds.length && out.length < 25; i++) {
if (cmds[i].toLowerCase().indexOf(needle) !== 0) continue
const line = (split.prefix + cmds[i]).slice(0, 100)
discordShPushChoice(out, seen, line, line, 'command')
}
}
return out
}
function discordFmKey(interaction) {
@@ -1450,7 +1775,7 @@ async function discordCmdHandleBare(ctx, sub) {
discordField('/net', 'Swarm — peers, summary'),
discordField('/files /edit /create', 'Browse and edit files under `~/` and `/tmp`'),
discordField('/settings', 'Live session knobs (theme, shell, Discord, agent, aliases)'),
discordField('/run /journal /say /man', 'Allowlisted command, logs, speak, man pages')
discordField('/run /journal /say /man', 'Full shell (cwd + autocomplete), logs, speak, man pages')
]
})
)
@@ -2280,50 +2605,186 @@ function discordCmdSayBox(text) {
return [bar, body.join('\n'), bar, ' \\', ' cow-ish · bare-os'].join('\n')
}
async function discordCmdHandleRun(ctx, raw) {
const cmd = String(raw || '').trim()
if (!cmd) return { text: 'command required — pick from autocomplete or use Run…', ephemeral: true }
if (/[;&|`$<>(){}]/.test(cmd)) {
return { text: 'metacharacters are not allowed', ephemeral: true }
function discordShHud(ctx, interaction, note) {
const rec = discordShGet(interaction, ctx)
const fields = [
discordField('Directory', '`' + discordShPrettyCwd(ctx, rec.cwd) + '`', true),
discordField('Last exit', rec.lastCmd ? String(rec.lastExit) : '—', true),
discordField('User', discordSessionUser(ctx), true)
]
if (rec.lastCmd) {
fields.push(discordField('Last command', '`' + rec.lastCmd + '`', false))
}
const key = cmd.replace(/\s+/g, ' ')
const bin = key.split(' ')[0]
if (!BARE_OS_DISCORD_RUN_ALLOW[key] && !BARE_OS_DISCORD_RUN_ALLOW[bin]) {
const hist = rec.hist.slice(0, 6)
if (hist.length) {
fields.push(
discordField(
'History',
hist
.map(function (h) {
return '`' + h.cmd + '` · ' + h.exit
})
.join('\n')
)
)
}
const histSel = discordSelect(
'sh:hist',
'Replay a command…',
rec.hist.slice(0, 20).map(function (h, i) {
return {
label: h.cmd.slice(0, 90),
value: String(i),
description: 'exit ' + h.exit + (h.ms ? ' · ' + h.ms + 'ms' : '')
}
})
)
const acts = discordButtons([
{ id: 'sh:again', label: 'Repeat', style: 1 },
{ id: 'sh:up', label: 'cd ..' },
{ id: 'sh:home', label: 'cd ~' },
{ id: 'sh:pwd', label: 'pwd' }
])
return discordResult(
discordEmbed({
title: 'Shell · ' + discordShPrompt(ctx, rec),
desc:
(note ? note + '\n' : '') +
'Type **`/run cmd:`** and keep going — `cd`, pipes, `&&`, redirects, and `$VAR` all work.\n' +
'Working directory sticks for this Discord user.',
fields: fields,
color: rec.lastCmd && rec.lastExit ? BARE_OS_DISCORD_COLOR_WARN : BARE_OS_DISCORD_COLOR_OK,
footer: discordShPrompt(ctx, rec) + ' · /run cmd:'
}),
{ components: [histSel, acts].filter(Boolean) }
)
}
async function discordCmdHandleRun(ctx, interaction, raw) {
const rec = discordShGet(interaction, ctx)
const cmd = String(raw || '').trim()
if (!cmd) return discordShHud(ctx, interaction, '')
if (cmd.length > 4000) {
return { text: 'Command line is too long (max 4000).', ephemeral: true }
}
const first = discordShFirstWord(cmd)
if (first === 'exit') {
rec.lastCmd = cmd
rec.lastExit = 0
return discordShHud(ctx, interaction, 'Stayed attached — `exit` does not stop the Discord bot.')
}
if (first === 'logout' || first === 'login') {
return {
text:
'not in allowlist. Try: uname, whoami, hostname, date, uptime, id, pwd, arch, nproc, help, motd, df, ps, procstat, uname -a',
'`login` / `logout` need the guest TTY. This Discord shell stays as **' +
discordSessionUser(ctx) +
'**.',
ephemeral: true
}
}
if (typeof ctx.execLine !== 'function') {
return { text: 'execLine unavailable', ephemeral: true }
return { text: 'execLine unavailable in this session', ephemeral: true }
}
await discordShPrepare(ctx, rec)
const t0 = Date.now()
let out = ''
let errNote = ''
try {
out = await discordCmdCapture(ctx, function () {
return ctx.execLine(cmd, { timeoutMs: BARE_OS_DISCORD_SH_EXEC_MS })
})
} catch (err) {
errNote = String((err && err.message) || err)
out = (out ? out + '\n' : '') + errNote
}
const ms = Date.now() - t0
const exit = ctx.exitCode == null ? (errNote ? 1 : 0) : Number(ctx.exitCode) || 0
discordShHarvest(ctx, rec)
discordShPushHist(rec, cmd, exit, ms, out)
const tty = BARE_OS_DISCORD_SH_TTY[first]
if (!String(out || '').trim() && (first === 'cd' || first === 'export' || first === 'unset' || first === 'alias')) {
return discordShHud(ctx, interaction, '`$ ' + cmd + '` · exit **' + exit + '** · ' + ms + 'ms')
}
const out = await discordCmdCapture(ctx, function () {
return ctx.execLine(key)
})
const parsed = discordTryJson(out)
if (parsed && typeof parsed === 'object') {
return discordPrettySnapshot('$ ' + key, parsed)
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && String(out).length < 800) {
return discordPrettySnapshot(discordShPrompt(ctx, rec) + ' $ ' + cmd, parsed, {
color: exit ? BARE_OS_DISCORD_COLOR_WARN : BARE_OS_DISCORD_COLOR_OK
})
}
if (!out) {
const lead =
'`' +
discordShPrompt(ctx, rec) +
'`\n`$ ' +
cmd +
'`' +
(tty ? '\n_Interactive TUI — needs a real terminal; output may be empty._' : '')
if (!String(out || '').trim()) {
return discordResult(
discordEmbed({
title: '$ ' + key,
footer: 'ran as ' + discordSessionUser(ctx),
desc: '(no output, exit ' + String(ctx.exitCode || 0) + ')'
})
title: 'Shell',
desc: lead + '\n(no output)',
color: exit ? BARE_OS_DISCORD_COLOR_WARN : BARE_OS_DISCORD_COLOR_OK,
footer: 'exit ' + exit + ' · ' + ms + 'ms · ' + discordShPrettyCwd(ctx, rec.cwd)
}),
{
components: [
discordButtons([
{ id: 'sh:again', label: 'Repeat', style: 1 },
{ id: 'sh:hud', label: 'Shell' }
])
]
}
)
}
return {
more: {
title: '$ ' + key,
title: 'Shell',
lead: lead,
body: discordCmdRedact(out),
fence: true,
footer: 'ran as ' + discordSessionUser(ctx)
color: exit ? BARE_OS_DISCORD_COLOR_WARN : BARE_OS_DISCORD_COLOR_OK,
footer: 'exit ' + exit + ' · ' + ms + 'ms · ' + discordShPrettyCwd(ctx, rec.cwd),
components: [
discordButtons([
{ id: 'sh:again', label: 'Repeat', style: 1 },
{ id: 'sh:up', label: 'cd ..' },
{ id: 'sh:home', label: 'cd ~' },
{ id: 'sh:hud', label: 'Shell' }
])
]
}
}
}
async function discordShAction(ctx, interaction, id) {
const rec = discordShGet(interaction, ctx)
if (id === 'sh:hud' || id === 'run:compose') {
return discordSendResult(ctx, interaction, discordShHud(ctx, interaction, ''))
}
if (id === 'sh:pwd') {
return discordSendResult(ctx, interaction, await discordCmdHandleRun(ctx, interaction, 'pwd'))
}
if (id === 'sh:again') {
if (!rec.lastCmd) {
return discordSendResult(ctx, interaction, discordShHud(ctx, interaction, 'No command to repeat.'))
}
return discordSendResult(ctx, interaction, await discordCmdHandleRun(ctx, interaction, rec.lastCmd))
}
if (id === 'sh:up') {
return discordSendResult(ctx, interaction, await discordCmdHandleRun(ctx, interaction, 'cd ..'))
}
if (id === 'sh:home') {
return discordSendResult(ctx, interaction, await discordCmdHandleRun(ctx, interaction, 'cd'))
}
if (id === 'sh:hist') {
const i = Number((interaction.values && interaction.values[0]) || 0)
const hit = rec.hist[i]
if (!hit) return discordSendResult(ctx, interaction, discordShHud(ctx, interaction, ''))
return discordSendResult(ctx, interaction, await discordCmdHandleRun(ctx, interaction, hit.cmd))
}
return discordSendResult(ctx, interaction, discordShHud(ctx, interaction, ''))
}
async function discordCmdHandleJournal(ctx, unit) {
if (typeof ctx.bareOsRunSystemctlCli === 'function' && unit) {
const out = await discordCmdCapture(ctx, function () {
@@ -2409,7 +2870,7 @@ function discordBuildSlashCommands(dj) {
const net = new B().setName('net').setDescription('Swarm / network')
const man = new B().setName('man').setDescription('Look up a man page')
const say = new B().setName('say').setDescription('Speak as Bare OS (or open a compose form)')
const run = new B().setName('run').setDescription('Run an allowlisted utility as the session user')
const run = new B().setName('run').setDescription('Non-interactive Bare OS shell (cwd, pipes, cd, history)')
const journal = new B()
.setName('journal')
.setDescription('Tail a unit or system log')
@@ -2494,7 +2955,7 @@ function discordBuildSlashCommands(dj) {
}
discordCmdOpt(man, 'page', 'Command name (e.g. uname)', true, true)
discordCmdOpt(say, 'text', 'Text to box (omit to open a form)', false, false)
discordCmdOpt(run, 'cmd', 'Allowlisted utility', false, true)
discordCmdOpt(run, 'cmd', 'Shell line (cd, pipes, &&, redirects, $VAR)', false, true)
discordCmdOpt(journal, 'unit', 'Unit name', false, true)
discordCmdOpt(edit, 'path', 'File under ~ or /tmp (omit to pick)', false, true)
discordCmdOpt(create, 'path', 'New file under ~ or /tmp (omit to pick)', false, true)
@@ -2551,11 +3012,7 @@ async function discordRouteCommand(ctx, name, sub, opt) {
})
)
}
if (name === 'run') {
const cmd = opt('cmd')
if (!cmd) return { modal: 'run' }
return discordCmdHandleRun(ctx, cmd)
}
if (name === 'run') return { run: opt('cmd') || '' }
if (name === 'journal') return discordCmdHandleJournal(ctx, opt('unit'))
if (name === 'edit') {
const path = opt('path')
@@ -3951,6 +4408,7 @@ function discordIdleForgetSessions(userId) {
delete BARE_OS_DISCORD_FM_SESSIONS[userId]
delete BARE_OS_DISCORD_SET_SESSIONS[userId]
delete BARE_OS_DISCORD_MORE_SESSIONS[userId]
delete BARE_OS_DISCORD_SH_SESSIONS[userId]
}
function discordUnwrapMessage(sent) {
@@ -4069,6 +4527,9 @@ async function discordIdleWatch(interaction, payload, sent) {
}
async function discordSendResult(ctx, interaction, result) {
if (result && result.run !== undefined) {
return discordSendResult(ctx, interaction, await discordCmdHandleRun(ctx, interaction, result.run))
}
if (result && result.more) {
return discordSendResult(ctx, interaction, discordLongTextResult(interaction, result.more))
}
@@ -4138,13 +4599,7 @@ async function discordSendResult(ctx, interaction, result) {
return
}
if (result && result.modal === 'run') {
await discordShowModal(interaction, {
id: 'run:modal',
title: 'Run allowlisted command',
label: 'Command',
placeholder: 'uname -a'
})
return
return discordSendResult(ctx, interaction, discordShHud(ctx, interaction, ''))
}
const payload = discordCmdReplyPayload(result)
const isComp =
@@ -4201,7 +4656,7 @@ async function discordDispatchAutocomplete(ctx, interaction) {
try {
if (fname === 'unit') choices = await discordSuggestUnits(ctx, q)
else if (fname === 'page') choices = await discordSuggestMan(ctx, q)
else if (fname === 'cmd') choices = discordSuggestRun(q)
else if (fname === 'cmd') choices = await discordSuggestRun(ctx, interaction, q)
else if (fname === 'path') {
const cmd = String(interaction.commandName || '')
choices =
@@ -4385,6 +4840,10 @@ async function discordDispatchComponent(ctx, interaction) {
const opt = function () {
return ''
}
if (id.indexOf('sh:') === 0 || id === 'run:compose') {
await discordShAction(ctx, interaction, id)
return true
}
if (id === 'more:prev' || id === 'more:next') {
const rec = discordMoreGet(interaction)
if (!rec) {
@@ -4415,15 +4874,6 @@ async function discordDispatchComponent(ctx, interaction) {
})
return true
}
if (id === 'run:compose') {
await discordShowModal(interaction, {
id: 'run:modal',
title: 'Run allowlisted command',
label: 'Command',
placeholder: 'uname -a'
})
return true
}
if (id === 'edit:new') {
await discordShowModal(interaction, {
id: 'edit:path',
@@ -4752,7 +5202,7 @@ async function discordDispatchModal(ctx, interaction) {
)
}
if (id === 'run:modal') {
return discordSendResult(ctx, interaction, await discordCmdHandleRun(ctx, value))
return discordSendResult(ctx, interaction, await discordCmdHandleRun(ctx, interaction, value))
}
return discordSendResult(ctx, interaction, discordPanel(ctx))
}