Condense commands (bot)
Release rolling / release (push) Successful in 25m40s

This commit is contained in:
2026-08-23 14:11:27 -04:00
parent dd48c1a449
commit 3b0ada9a07
370 changed files with 45265 additions and 408 deletions
+336 -121
View File
@@ -311,31 +311,157 @@ function bareOsHexEncode(u8) {
return s
}
/**
* Shared GNU-style checksum FILE / stdin loop used by *sum commands.
* @param {Record<string, unknown>} ctx
* @param {string[]} argv
* @param {{
* cmd: string,
* help?: string,
* digest: (u8: Uint8Array) => string | Promise<string>
* }} opts
*/
async function bareChecksumRun(ctx, argv, opts) {
const cmd = String(opts.cmd || 'checksum')
const help =
opts.help ||
'usage: ' +
cmd +
' [FILE]...\nWith no FILE, or when FILE is -, read standard input.'
const digest = opts.digest
const paths = []
for (let i = 1; i < argv.length; i++) {
const a = argv[i]
if (a === '-h' || a === '--help') {
ctx.console.log(help)
ctx.exitCode = 0
return
}
if (a.startsWith('-') && a !== '-') {
ctx.console.error(cmd + ': unsupported option ' + a)
ctx.exitCode = 1
return
}
paths.push(a)
}
const b4 = ctx.b4a
async function one(name, buf) {
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
try {
const hex = await digest(u8)
ctx.console.log(hex + ' ' + name)
} catch (e) {
ctx.console.error(cmd + ': ' + (e.message || e))
ctx.exitCode = 1
}
}
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
await one('-', b4.from(bareStdin(ctx)))
return
}
for (const p of paths) {
if (p === '-') {
await one('-', b4.from(bareStdin(ctx)))
continue
}
const b = await ctx.vfs.readFile(p)
if (!b) {
ctx.console.error(cmd + ': ' + p + ': No such file')
ctx.exitCode = 1
continue
}
await one(p, b)
}
}
/**
* WebCrypto hex digest; throws the same missing-subtle message as the old *sum files.
* @param {string} algo
* @param {string} missingMsg
* @returns {(u8: Uint8Array) => Promise<string>}
*/
function bareSubtleHexDigest(algo, missingMsg) {
return async function (u8) {
const subtle = globalThis.crypto?.subtle
if (!subtle || typeof subtle.digest !== 'function') {
throw new Error(missingMsg)
}
const hash = await subtle.digest(algo, u8)
return bareOsHexEncode(new Uint8Array(hash))
}
}
/**
* Owner/group name → uid/gid used by chown / chgrp.
* @param {string} spec
* @param {Record<string, string>} env
* @returns {number | null}
*/
function parseGroupSpec(spec, env) {
const s = String(spec).trim()
if (!s) return null
if (/^\d+$/.test(s)) {
const n = Number.parseInt(s, 10)
return Number.isFinite(n) ? n : null
}
const g = (env.GROUP || env.USER || 'guest').trim()
if (s === 'root') return 0
if (s === 'guest' || s === 'nobody') return 65534
if (g && s === g) {
const gid = Number.parseInt(String(env.GID ?? '65534'), 10)
return Number.isFinite(gid) ? gid : 65534
}
return null
}
/**
* User name / numeric uid used by chown.
* @param {string} spec
* @param {Record<string, string>} env
* @returns {{ uid: number | null, gid: number | null }}
*/
function parseIdSpec(spec, env) {
const s = String(spec).trim()
if (!s) return { uid: null, gid: null }
if (/^\d+$/.test(s)) {
const n = Number.parseInt(s, 10)
return { uid: Number.isFinite(n) ? n : null, gid: null }
}
const u = (env.USER || env.LOGNAME || '').trim()
if (s === 'root') return { uid: 0, gid: null }
if (s === 'guest' || s === 'nobody') return { uid: 65534, gid: null }
if (u && s === u) {
const uid = Number.parseInt(String(env.UID ?? '65534'), 10)
return { uid: Number.isFinite(uid) ? uid : 65534, gid: null }
}
return { uid: null, gid: null }
}
/**
* Shared QVAC chat catalog + REST /models helpers (preamble for agent and discord-bot).
*/
/** @type {{ id: string, family: string, label: string, tools: boolean, ramGb: number, profile?: string }[]} */
/** @type {{ id: string, family: string, label: string, tools: boolean, ramGb: number, ctxSize: number, profile?: string }[]} */
var BARE_AGENT_QVAC_CHAT_MODELS = [
{ id: 'QWEN3_600M_INST_Q4', family: 'qwen3', label: 'Qwen3 0.6B Instruct Q4', tools: true, ramGb: 4, profile: 'lite' },
{ id: 'QWEN3_1_7B_INST_Q4', family: 'qwen3', label: 'Qwen3 1.7B Instruct Q4', tools: true, ramGb: 8, profile: 'recommended' },
{ id: 'QWEN3_4B_INST_Q4_K_M', family: 'qwen3', label: 'Qwen3 4B Instruct Q4_K_M', tools: true, ramGb: 16, profile: 'strong' },
{ id: 'QWEN3_4B_Q4_K_M', family: 'qwen3', label: 'Qwen3 4B Q4_K_M', tools: true, ramGb: 16 },
{ id: 'QWEN3_8B_INST_Q4_K_M', family: 'qwen3', label: 'Qwen3 8B Instruct Q4_K_M', tools: true, ramGb: 24 },
{ id: 'LLAMA_TOOL_CALLING_1B_INST_Q4_K', family: 'llama', label: 'Llama 3.2 1B tool-calling', tools: true, ramGb: 6, profile: 'tool-tiny' },
{ id: 'LLAMA_3_2_1B_INST_Q4_0', family: 'llama', label: 'Llama 3.2 1B Instruct Q4_0', tools: true, ramGb: 6 },
{ id: 'SMOLLM2_360M_INST_Q8', family: 'smol', label: 'SmolLM2 360M Instruct Q8', tools: false, ramGb: 3 },
{ id: 'GPT_OSS_20B_INST_Q4_K_M', family: 'gpt-oss', label: 'GPT-OSS 20B Instruct Q4_K_M', tools: true, ramGb: 24 },
{ id: 'GEMMA4_2B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 2B multimodal Q4', tools: true, ramGb: 8 },
{ id: 'GEMMA4_2B_MULTIMODAL_Q6_K', family: 'gemma', label: 'Gemma 4 2B multimodal Q6', tools: true, ramGb: 10 },
{ id: 'GEMMA4_4B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 4B multimodal Q4', tools: true, ramGb: 16 },
{ id: 'GEMMA4_31B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 31B multimodal Q4', tools: true, ramGb: 48 },
{ id: 'QWEN3VL_2B_MULTIMODAL_Q4_K', family: 'qwen3', label: 'Qwen3-VL 2B multimodal Q4', tools: true, ramGb: 10 },
{ id: 'QWEN3_5_2B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 2B multimodal Q4', tools: true, ramGb: 10 },
{ id: 'QWEN3_5_4B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 4B multimodal Q4', tools: true, ramGb: 16 },
{ id: 'QWEN3_5_0_8B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 8B multimodal Q4', tools: true, ramGb: 24 },
{ id: 'QWEN3_5_9B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 9B multimodal Q4', tools: true, ramGb: 28 },
{ id: 'QWEN3_6_27B_MULTIMODAL_Q4_K_XL', family: 'large', label: 'Qwen3.6 27B multimodal Q4', tools: true, ramGb: 48 }
{ id: 'QWEN3_600M_INST_Q4', family: 'qwen3', label: 'Qwen3 0.6B Instruct Q4', tools: true, ramGb: 4, profile: 'lite', ctxSize: 32768 },
{ id: 'QWEN3_1_7B_INST_Q4', family: 'qwen3', label: 'Qwen3 1.7B Instruct Q4', tools: true, ramGb: 8, profile: 'recommended', ctxSize: 32768 },
{ id: 'QWEN3_4B_INST_Q4_K_M', family: 'qwen3', label: 'Qwen3 4B Instruct Q4_K_M', tools: true, ramGb: 16, profile: 'strong', ctxSize: 32768 },
{ id: 'QWEN3_4B_Q4_K_M', family: 'qwen3', label: 'Qwen3 4B Q4_K_M', tools: true, ramGb: 16, ctxSize: 32768 },
{ id: 'QWEN3_8B_INST_Q4_K_M', family: 'qwen3', label: 'Qwen3 8B Instruct Q4_K_M', tools: true, ramGb: 24, ctxSize: 32768 },
{ id: 'LLAMA_TOOL_CALLING_1B_INST_Q4_K', family: 'llama', label: 'Llama 3.2 1B tool-calling', tools: true, ramGb: 6, profile: 'tool-tiny', ctxSize: 131072 },
{ id: 'LLAMA_3_2_1B_INST_Q4_0', family: 'llama', label: 'Llama 3.2 1B Instruct Q4_0', tools: true, ramGb: 6, ctxSize: 131072 },
{ id: 'SMOLLM2_360M_INST_Q8', family: 'smol', label: 'SmolLM2 360M Instruct Q8', tools: false, ramGb: 3, ctxSize: 8192 },
{ id: 'GPT_OSS_20B_INST_Q4_K_M', family: 'gpt-oss', label: 'GPT-OSS 20B Instruct Q4_K_M', tools: true, ramGb: 24, ctxSize: 131072 },
{ id: 'GEMMA4_2B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 2B multimodal Q4', tools: true, ramGb: 8, ctxSize: 131072 },
{ id: 'GEMMA4_2B_MULTIMODAL_Q6_K', family: 'gemma', label: 'Gemma 4 2B multimodal Q6', tools: true, ramGb: 10, ctxSize: 131072 },
{ id: 'GEMMA4_4B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 4B multimodal Q4', tools: true, ramGb: 16, ctxSize: 131072 },
{ id: 'GEMMA4_31B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 31B multimodal Q4', tools: true, ramGb: 48, ctxSize: 262144 },
{ id: 'QWEN3VL_2B_MULTIMODAL_Q4_K', family: 'qwen3', label: 'Qwen3-VL 2B multimodal Q4', tools: true, ramGb: 10, ctxSize: 131072 },
{ id: 'QWEN3_5_2B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 2B multimodal Q4', tools: true, ramGb: 10, ctxSize: 262144 },
{ id: 'QWEN3_5_4B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 4B multimodal Q4', tools: true, ramGb: 16, ctxSize: 262144 },
{ id: 'QWEN3_5_0_8B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 0.8B multimodal Q4', tools: true, ramGb: 8, ctxSize: 262144 },
{ id: 'QWEN3_5_9B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 9B multimodal Q4', tools: true, ramGb: 28, ctxSize: 262144 },
{ id: 'QWEN3_6_27B_MULTIMODAL_Q4_K_XL', family: 'large', label: 'Qwen3.6 27B multimodal Q4', tools: true, ramGb: 48, ctxSize: 262144 }
]
/** @type {Record<string, string[]>} */
@@ -437,6 +563,72 @@ function bareAgentParseOpenAiModels(json) {
return out
}
/**
* Keep an explicit QVAC model id (Discord / `--model` / picker).
* Profile defaults only apply when no model is set.
* @param {Record<string, unknown>} config
* @param {{ chatModel?: string }} [profile]
* @returns {Record<string, unknown>}
*/
function bareAgentHonorExplicitQvacModel(config, profile) {
const out = config && typeof config === 'object' ? config : {}
const explicit = String(out.qvac_model || out.model || '').trim()
if (explicit) {
out.qvac_model = explicit
out.model = explicit
if (typeof bareAgentQvacSyncSettingsForModel === 'function') {
bareAgentQvacSyncSettingsForModel(out, explicit)
} else {
const card =
typeof bareAgentQvacFindChatModel === 'function'
? bareAgentQvacFindChatModel(explicit)
: null
if (card && card.profile) out.qvac_profile = card.profile
}
return out
}
const fallback = String((profile && profile.chatModel) || '').trim()
if (fallback) {
out.qvac_model = fallback
out.model = fallback
}
return out
}
/**
* Persist a live model switch for the current backend.
* @param {Record<string, unknown>} config
* @param {string} modelId
* @returns {Record<string, unknown>}
*/
function bareAgentApplyLiveModel(config, modelId) {
const out = { ...(config && typeof config === 'object' ? config : {}) }
const id = String(modelId || '').trim()
if (!id) return out
let backend = 'qvac'
if (typeof bareAgentResolveBackend === 'function') {
backend = bareAgentResolveBackend(out)
} else {
const b = String(out.backend || '').trim().toLowerCase()
const p = String(out.provider || '').trim().toLowerCase()
if (b === 'rest' || b === 'openai' || b === 'http') backend = 'rest'
else if (b === 'qvac') backend = 'qvac'
else if (p === 'groq' || p === 'xai' || p === 'openai' || p === 'custom') backend = 'rest'
else if (out.rest_api_key && String(out.rest_api_key).trim()) backend = 'rest'
}
if (backend === 'qvac') {
out.backend = 'qvac'
out.provider = 'qvac'
out.qvac_model = id
out.model = id
out.qvac_ctx_size = 0
return bareAgentHonorExplicitQvacModel(out, null)
}
out.backend = 'rest'
out.model = id
return out
}
/**
* @param {string} [provider]
*/
@@ -3344,17 +3536,16 @@ async function discordCmdHandleBare(ctx, sub) {
title: 'Commands',
desc: 'Running as **' + user + '**. Open **Menu** for destinations, or use a slash command.',
fields: [
discordField('/bare', 'Session — status, whoami, hostname, date, uptime, motd, uname'),
discordField('/sys', 'Host — disk, memory, processes, env, doctor, features, limits'),
discordField('/svc', 'Serviceslist, start, stop, restart, logs'),
discordField('/fs', 'Read-only VFS — ls, cat, stat, head'),
discordField('/net', 'Swarm — peers, summary'),
discordField('/panel', 'Control panel (also Menu on every reply)'),
discordField('/bare', 'Session — ping, about, help, status, whoami, hostname, date, uptime, motd, uname, presence'),
discordField('/sys', 'Hostdisk, memory, processes, env, doctor, features, limits, peers, swarm'),
discordField('/svc', 'Services — list, start, stop, restart, logs, journal'),
discordField(
'/files /edit /create /upload /open-file',
'Browse, edit, wget in, or attach a VFS file out (CDN, kept)'
'/files',
'VFS — browse, ls, cat, stat, head, edit, create, upload, open-file (CDN, kept)'
),
discordField('/settings', 'Live session knobs (theme, shell, Discord, agent, aliases)'),
discordField('/r /journal /say /man', 'Full shell (cwd + autocomplete), logs, speak, man pages'),
discordField('/r /say /man', 'Full shell (cwd + autocomplete), speak, man pages'),
discordField('/plugins', 'List, reload, enable/disable ~/.discord/plugins'),
discordField('/hdms', 'Hyperdrives — list, create, add, invite, pair, /mnt'),
discordField('/holesail', 'Tunnels — list, add, edit, start/stop, enable, service'),
@@ -3362,8 +3553,7 @@ async function discordCmdHandleBare(ctx, sub) {
discordField(
'DM this bot',
'Whitelisted users: send a normal message in a Direct Message to talk to the agent (same ~/.agent history). help / reset / stop work as text.'
),
discordField('/status', 'Bot presence — playing / watching / listening / competing')
)
]
})
)
@@ -4493,7 +4683,7 @@ async function discordCmdHandleOpenFile(ctx, interaction, rawPath) {
const given = String(rawPath || '').trim()
if (!given) {
return {
text: 'Give `/open-file` a VFS path (e.g. `~/notes.txt`). The bot attaches it to this channel (Discord CDN; not auto-deleted).',
text: 'Give `/files open-file` a VFS path (e.g. `~/notes.txt`). The bot attaches it to this channel (Discord CDN; not auto-deleted).',
ephemeral: true
}
}
@@ -7675,27 +7865,16 @@ function discordBuildSlashCommands(dj, ctx) {
const finish = function () {
const B2 = dj.SlashCommandBuilder
const bare = new B2().setName('bare').setDescription('Bare OS session (logged-in user)')
const sys = new B2().setName('sys').setDescription('Bare OS system snapshots')
const svc = new B2().setName('svc').setDescription('systemctl units')
const fsCmd = new B2().setName('fs').setDescription('Read-only VFS')
const net = new B2().setName('net').setDescription('Swarm / network')
const sys = new B2().setName('sys').setDescription('Bare OS system snapshots and swarm')
const svc = new B2().setName('svc').setDescription('systemctl units and journal')
const man = new B2().setName('man').setDescription('Look up a man page')
const say = new B2().setName('say').setDescription('Speak as Bare OS (or open a compose form)')
const run = new B2()
.setName('r')
.setDescription('Run a Bare OS shell command (cwd, pipes, cd, history)')
const journal = new B2()
.setName('journal')
.setDescription('Tail a unit or system log')
const edit = new B2()
.setName('edit')
.setDescription('Edit a text file in a Discord modal (home or /tmp)')
const create = new B2()
.setName('create')
.setDescription('Create a new text file (pick a path, then enter contents)')
const files = new B2()
.setName('files')
.setDescription('Browse and manage files (list, open, mkdir, rename, delete)')
.setDescription('VFS: browse, read, edit, create, upload, attach')
const settings = new B2()
.setName('settings')
.setDescription('Live session settings (theme, shell, discord, agent, aliases)')
@@ -7705,16 +7884,6 @@ function discordBuildSlashCommands(dj, ctx) {
const panel = new B2()
.setName('panel')
.setDescription('Interactive Bare OS control panel')
const ping = new B2().setName('ping').setDescription('Reply pong')
const status = new B2()
.setName('status')
.setDescription('Bot presence in the member list (playing / watching / …)')
const upload = new B2()
.setName('upload')
.setDescription('Upload an attachment; wget saves it in the /r cwd or path')
const openFile = new B2()
.setName('open-file')
.setDescription('Send a VFS file as a Discord attachment (kept, not auto-deleted)')
const hdms = new B2()
.setName('hdms')
.setDescription('Hyperdrive management (list, create, add, invite, pair)')
@@ -7735,7 +7904,31 @@ function discordBuildSlashCommands(dj, ctx) {
['date', 'Clock'],
['uptime', '/proc/uptime'],
['motd', '/etc/motd'],
['uname', 'uname -a style']
['uname', 'uname -a style'],
['presence', 'Bot presence in the member list', function (s) {
discordCmdOpt(s, 'activity', 'playing, watching, listening, competing, custom, streaming', false, false, {
choices: [
['Playing', 'playing'],
['Watching', 'watching'],
['Listening', 'listening'],
['Competing', 'competing'],
['Custom', 'custom'],
['Streaming', 'streaming']
]
})
discordCmdOpt(s, 'name', 'Activity text shown in the member list', false, false, {
min: 1,
max: 128
})
discordCmdOpt(s, 'state', 'online, idle, dnd, or invisible', false, false, {
choices: [
['Online', 'online'],
['Idle', 'idle'],
['Do not disturb', 'dnd'],
['Invisible', 'invisible']
]
})
}]
]) &&
discordCmdAddSubs(sys, [
['df', 'Disk / host resources'],
@@ -7744,7 +7937,10 @@ function discordBuildSlashCommands(dj, ctx) {
['env', 'Redacted environment'],
['doctor', 'Security / debug posture'],
['features', '/proc/bare_os_features'],
['rlimits', 'Resource limits']
['rlimits', 'Resource limits'],
['peers', 'Swarm peers'],
['swarm', 'Swarm snapshot'],
['summary', 'net_summary']
]) &&
discordCmdAddSubs(svc, [
['list', 'systemctl list'],
@@ -7762,9 +7958,15 @@ function discordBuildSlashCommands(dj, ctx) {
}],
['logs', 'Unit logs', function (s) {
discordCmdOpt(s, 'unit', 'Unit name', true, true)
}],
['journal', 'Tail a unit or system log', function (s) {
discordCmdOpt(s, 'unit', 'Unit name', false, true)
}]
]) &&
discordCmdAddSubs(fsCmd, [
discordCmdAddSubs(files, [
['browse', 'Interactive file manager', function (s) {
discordCmdOpt(s, 'path', 'Directory to open', false, true)
}],
['ls', 'List a directory', function (s) {
discordCmdOpt(s, 'path', 'VFS path', false, true)
}],
@@ -7777,13 +7979,21 @@ function discordBuildSlashCommands(dj, ctx) {
['head', 'First lines of a file', function (s) {
discordCmdOpt(s, 'path', 'VFS path', true, true)
discordCmdOpt(s, 'lines', 'Line count', false, false)
}],
['edit', 'Edit a text file in a Discord modal', function (s) {
discordCmdOpt(s, 'path', 'File under ~ or /tmp (omit to pick)', false, true)
}],
['create', 'Create a new text file', function (s) {
discordCmdOpt(s, 'path', 'New file under ~ or /tmp (omit to pick)', false, true)
}],
['upload', 'Upload an attachment; wget saves it in the /r cwd or path', function (s) {
discordCmdAtt(s, 'file', 'File to upload (Discord CDN, then wget into the VFS)', true)
discordCmdOpt(s, 'path', 'VFS dest (file or dir; default: /r cwd + name)', false, true)
}],
['open-file', 'Send a VFS file as a Discord attachment (kept)', function (s) {
discordCmdOpt(s, 'path', 'VFS file to attach (uploaded to the Discord CDN)', true, true)
}]
]) &&
discordCmdAddSubs(net, [
['peers', 'Swarm peers'],
['swarm', 'Swarm snapshot'],
['summary', 'net_summary']
]) &&
discordCmdAddSubs(plugins, [
['list', 'Loaded plugins'],
['reload', 'Rescan ~/.discord/plugins'],
@@ -7920,41 +8130,13 @@ function discordBuildSlashCommands(dj, ctx) {
['stop', 'Abort the in-flight turn']
])
if (!ok) {
return [ping.toJSON()]
const ping = new B2().setName('ping').setDescription('Reply pong')
return [discordStampUserInstallCommand(ping.toJSON(), ctx)]
}
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', 'Shell line (cd, pipes, &&, redirects, $VAR)', true, 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)
discordCmdOpt(files, 'path', 'Directory to open', false, true)
discordCmdAtt(upload, 'file', 'File to upload (Discord CDN, then wget into the VFS)', true)
discordCmdOpt(upload, 'path', 'VFS dest (file or dir; default: /r cwd + name)', false, true)
discordCmdOpt(openFile, 'path', 'VFS file to attach (uploaded to the Discord CDN)', true, true)
discordCmdOpt(status, 'activity', 'playing, watching, listening, competing, custom, streaming', false, false, {
choices: [
['Playing', 'playing'],
['Watching', 'watching'],
['Listening', 'listening'],
['Competing', 'competing'],
['Custom', 'custom'],
['Streaming', 'streaming']
]
})
discordCmdOpt(status, 'name', 'Activity text shown in the member list', false, false, {
min: 1,
max: 128
})
discordCmdOpt(status, 'state', 'online, idle, dnd, or invisible', false, false, {
choices: [
['Online', 'online'],
['Idle', 'idle'],
['Do not disturb', 'dnd'],
['Invisible', 'invisible']
]
})
const stock = [bare, sys, svc, fsCmd, net, man, say, run, journal, edit, create, files, settings, plugins, panel, ping, status, upload, openFile, hdms, holesail, agent].map(
const stock = [bare, sys, svc, files, man, say, run, settings, plugins, panel, hdms, holesail, agent].map(
function (c) {
return discordStampUserInstallCommand(c.toJSON(), ctx)
}
@@ -8012,15 +8194,40 @@ function discordShowModal(interaction, spec) {
return discordShowForm(interaction, spec)
}
function discordCmdHandleEdit(opt) {
const path = opt('path')
if (!path) return { modal: 'edit:path' }
return { edit: path }
}
function discordCmdHandleCreate(opt) {
const path = opt('path')
if (!path) return { createPick: true }
return { create: path }
}
async function discordRouteCommand(ctx, name, sub, opt) {
if (name === 'panel') return discordPanel(ctx)
if (name === 'ping' || (name === 'bare' && (sub === 'ping' || !sub))) {
return discordCmdHandleBare(ctx, 'ping')
}
if (name === 'bare') return discordCmdHandleBare(ctx, sub)
if (name === 'sys') return discordCmdHandleSys(ctx, sub)
if (name === 'svc') return discordCmdHandleSvc(ctx, sub, opt('unit'))
if (name === 'fs') return discordCmdHandleFs(ctx, sub, opt('path'), opt('lines'))
if (name === 'bare') {
if (sub === 'presence') return discordCmdHandlePresence(ctx, opt)
return discordCmdHandleBare(ctx, sub)
}
if (name === 'sys') {
if (sub === 'peers' || sub === 'swarm' || sub === 'summary') {
return discordCmdHandleNet(ctx, sub)
}
return discordCmdHandleSys(ctx, sub)
}
if (name === 'svc') {
if (sub === 'journal') return discordCmdHandleJournal(ctx, opt('unit'))
return discordCmdHandleSvc(ctx, sub, opt('unit'))
}
if (name === 'fs' || (name === 'files' && (sub === 'ls' || sub === 'cat' || sub === 'stat' || sub === 'head'))) {
return discordCmdHandleFs(ctx, sub, opt('path'), opt('lines'))
}
if (name === 'net') return discordCmdHandleNet(ctx, sub)
if (name === 'man') return discordCmdHandleMan(ctx, opt('page'))
if (name === 'say') {
@@ -8035,16 +8242,8 @@ async function discordRouteCommand(ctx, name, sub, opt) {
}
if (name === 'r' || name === 'run') return { run: opt('cmd') || '' }
if (name === 'journal') return discordCmdHandleJournal(ctx, opt('unit'))
if (name === 'edit') {
const path = opt('path')
if (!path) return { modal: 'edit:path' }
return { edit: path }
}
if (name === 'create') {
const path = opt('path')
if (!path) return { createPick: true }
return { create: path }
}
if (name === 'edit' || (name === 'files' && sub === 'edit')) return discordCmdHandleEdit(opt)
if (name === 'create' || (name === 'files' && sub === 'create')) return discordCmdHandleCreate(opt)
if (name === 'files' || name === 'browse') {
return { files: opt('path') || '' }
}
@@ -8110,7 +8309,7 @@ async function discordEditSaveFromModal(ctx, interaction) {
const rec = discordEditGet(interaction)
if (!rec || !rec.path) {
return {
text: 'Edit session expired (15 minutes). Run `/edit` again.',
text: 'Edit session expired (15 minutes). Run `/files edit` again.',
ephemeral: true
}
}
@@ -8134,7 +8333,7 @@ async function discordEditSaveFromModal(ctx, interaction) {
const body = parts.join('')
if (rec.created && (await discordFileExists(ctx, p))) {
return {
text: '`' + p + '` already exists. Use `/edit` to change it.',
text: '`' + p + '` already exists. Use `/files edit` to change it.',
ephemeral: true
}
}
@@ -10080,12 +10279,24 @@ async function discordDispatchAutocomplete(ctx, interaction) {
else if (fname === 'cmd') choices = await discordSuggestRun(ctx, interaction, q)
else if (fname === 'path') {
const cmd = String(interaction.commandName || '')
choices =
cmd === 'edit' || cmd === 'create' || cmd === 'upload'
? discordSuggestEditPaths(ctx, q)
: cmd === 'hdms'
? await discordSuggestHdmsLabels(ctx, q)
: discordSuggestPaths(ctx, q)
let sub = ''
try {
if (interaction.options && typeof interaction.options.getSubcommand === 'function') {
sub = String(interaction.options.getSubcommand(false) || '')
}
} catch {
sub = ''
}
const writePath =
cmd === 'edit' ||
cmd === 'create' ||
cmd === 'upload' ||
(cmd === 'files' && (sub === 'edit' || sub === 'create' || sub === 'upload'))
choices = writePath
? discordSuggestEditPaths(ctx, q)
: cmd === 'hdms'
? await discordSuggestHdmsLabels(ctx, q)
: discordSuggestPaths(ctx, q)
} else if (fname === 'label' && String(interaction.commandName || '') === 'hdms') {
choices = await discordSuggestHdmsLabels(ctx, q)
} else if (fname === 'id' && String(interaction.commandName || '') === 'holesail') {
@@ -10924,7 +11135,7 @@ function discordDmHelpResult() {
discordField('reset / !reset / !new', 'Clear chat history (keeps config)'),
discordField('stop / !stop', 'Abort the in-flight turn'),
discordField('ping', 'Pong'),
discordField('/agent /status', 'Slash commands still work in this DM'),
discordField('/agent /bare presence', 'Slash commands still work in this DM'),
discordField(
'Agent reach-out',
'The agent can DM you first on this channel (`discord_send_message`). Same whitelist.'
@@ -11091,9 +11302,12 @@ async function discordDispatchInteraction(ctx, interaction) {
await discordPluginsEnsure(ctx)
if (name === 'plugins') {
result = await discordPluginsAdmin(ctx, interaction, sub, opt)
} else if (name === 'upload') {
} else if (name === 'upload' || (name === 'files' && sub === 'upload')) {
result = await discordCmdHandleUpload(ctx, interaction, opt('path'))
} else if (name === 'open-file') {
} else if (
name === 'open-file' ||
(name === 'files' && (sub === 'open-file' || sub === 'attach'))
) {
result = await discordCmdHandleOpenFile(ctx, interaction, opt('path'))
} else if (name === 'hdms') {
result = await discordCmdHandleHdms(ctx, interaction, sub, opt)
@@ -11214,6 +11428,7 @@ var bareOsDiscordCommands = {
if (typeof module !== 'undefined' && module.exports) {
module.exports = bareOsDiscordCommands
}
/**
* Stock Discord ping-pong bot via ctx.bare.discordJS (vendored bare-discord-js).
* Token: --token, DISCORD_TOKEN, --env PATH, DISCORD_ENV_FILE, ~/.discord.env.
@@ -11382,10 +11597,10 @@ function discordUsage(argv0) {
'host DISCORD_ENV_FILE, ~/.discord/.env, ~/.discord.env).\n' +
'Ctrl+C stops the foreground bot. Initd unit bare-os-discord appears in\n' +
'systemctl only when ~/.discord/.env exists with DISCORD_TOKEN=.\n' +
'Slash commands: /panel /files /edit /create /upload /open-file /hdms /holesail /agent /status /bare /sys /svc /fs /net /man /say /r /journal /ping.\n' +
'Slash commands: /panel /bare /sys /svc /files /man /say /r /settings /plugins /hdms /holesail /agent.\n' +
'Commands run as the unlocked session user. Options use Discord autocomplete.\n' +
'Whitelisted users can DM the bot to talk to /bin/agent (same ~/.agent session).\n' +
'/status sets playing / watching / listening / competing in the member list.\n' +
'/bare presence sets playing / watching / listening / competing in the member list.\n' +
'Channel "ping" still replies pong when Message Content Intent is enabled.\n' +
'User-installable (profile app) is on by default so operators can use slash\n' +
'commands in DMs and any server. DISCORD_ID_WHITELIST is always enforced on\n' +