Discript is a scripting language and CLI for safe, programmatic Discord automation. It is designed for developers, AI agents, and coding harnesses that need repeatable commands, composable scripts, structured results, and explicit control over side effects.
At its core, Discript parses and runs .ds programs or commands from files, stdin, or inline CLI input. The same engine can connect directly to Discord for one-shot work, reuse a long-lived daemon connection over a local socket, or expose and consume the engine through MCP stdio and HTTP/HTTPS transports.
- Node.js 26 or newer
- A Discord bot token in
DISCORD_TOKEN - The bot must be able to view the target guilds and channels
npm install
node -e "require('node:fs').copyFileSync('.env.example', '.env')"
# edit .env with a private Discord bot tokenFor local installation before the first npm release, use the Git SSH URL or build a tarball:
npm install git+ssh://git@github.com/eliware/discript.git
npm pack
npm install ./eliware-discript-*.tgzThe package is public and publishes to npm from v* Git tags through the standard Eliware release workflow. The npm package name is @eliware/discript because the unscoped discript name is unavailable for publishing.
The full documentation map is in docs/README.md, including getting started, the language, CLI and Discord references, agent workflows, developer guides, operations, and contribution guidance.
List guilds with a direct CLI command:
npm start -- guilds list --jsonRead-only commands and supported one-shot mutations can use REST without opening a Gateway session:
npm start -- --rest guilds list --json
npm start -- --rest channels list --guild <guild-id> --json
npm start -- --rest messages send --channel <channel-id> --content "hello"
npm start -- --rest channels delete --channel <channel-id> --yesFor repeated Gateway-backed commands or scripts, start the shared local broker once:
npm start -- daemon start
npm start -- --broker guilds list --json
npm start -- --broker script.ds
npm start -- daemon stopDiscover supported direct commands or generate shell completion scripts:
npm start -- commands list --json
npm start -- completion bash > discript-completion.bash
npm start -- completion zsh
npm start -- completion fishInspect the connected bot identity:
npm start -- bot get --jsonCommon singular and abbreviated resource names are accepted, such as msg send, chan list, and guild list.
Unknown commands include nearby command suggestions; JSON errors expose them under details.suggestions.
Machine-readable errors include error, code, and exitCode. Discord API failures use the stable DISCORD_API_ERROR code with exit status 5; sanitized request metadata may appear under details.
List channels in a guild:
npm start -- channels list --guild <guild-id> --jsonLook up a guild or channel:
npm start -- guilds get --guild <guild-id> --json
npm start -- channels get --channel <channel-id> --jsonDiscover members and roles:
npm start -- members list --guild <guild-id> --json
npm start -- roles list --guild <guild-id> --jsonRead guild invites, emojis, and stickers:
npm start -- invites list --guild <guild-id> --json
npm start -- emojis list --guild <guild-id> --json
npm start -- stickers list --guild <guild-id> --jsonEmoji and sticker lifecycle operations require --yes and support --dry-run previews:
npm start -- emojis create --guild <guild-id> --name wave --file ./wave.png --yes --json
npm start -- emojis delete --guild <guild-id> --emoji <emoji-id> --yes --json
npm start -- stickers create --guild <guild-id> --name wave --file ./wave.png --tags wave --yes --json
npm start -- stickers delete --guild <guild-id> --sticker <sticker-id> --yes --jsonChannel webhooks support guarded listing, creation, and deletion:
npm start -- webhooks list --channel <channel-id> --json
npm start -- webhooks create --channel <channel-id> --name automation --yes --json
npm start -- webhooks delete --channel <channel-id> --webhook <webhook-id> --yes --jsonChannel permission overwrites can be inspected or changed with comma-separated Discord permission names:
npm start -- permissions list --channel <channel-id> --json
npm start -- permissions set --channel <channel-id> --target <role-or-user-id> --allow ViewChannel,SendMessages --deny ManageMessages --yes --json
npm start -- permissions delete --channel <channel-id> --target <role-or-user-id> --yes --jsonMember voice status and guarded controls are available through voice-users:
npm start -- voice-users status --guild <guild-id> --user <user-id> --json
npm start -- voice-users mute --guild <guild-id> --user <user-id> --yes --json
npm start -- voice-users unmute --guild <guild-id> --user <user-id> --yes --json
npm start -- voice-users deafen --guild <guild-id> --user <user-id> --yes --json
npm start -- voice-users undeafen --guild <guild-id> --user <user-id> --yes --json
npm start -- voice-users move --guild <guild-id> --user <user-id> --channel <voice-channel-id> --yes --json
npm start -- voice-users disconnect --guild <guild-id> --user <user-id> --yes --jsonInvite creation and deletion are guarded mutations; use --yes (or -y) and preview with --dry-run:
npm start -- invites create --guild <guild-id> --channel <channel-id> --duration 3600 --yes --json
npm start -- invites delete --guild <guild-id> --invite <invite-code> --yes --jsonScheduled events support discovery and guarded lifecycle operations:
npm start -- events list --guild <guild-id> --json
npm start -- events create --guild <guild-id> --name "Town hall" --start "2030-01-01T00:00:00Z" --yes --json
npm start -- events update --guild <guild-id> --event <event-id> --name "Updated" --yes --json
npm start -- events delete --guild <guild-id> --event <event-id> --yes --jsonVoice connections support guarded join/leave and status inspection:
npm start -- voice status --guild <guild-id> --json
npm start -- voice join --channel <voice-channel-id> --yes --json
npm start -- voice leave --guild <guild-id> --yes --jsonScripts can declare event handlers. The incoming Discord payload is available as event inside the handler:
on("messageCreate") {
print(event.content)
}
When a script registers one or more handlers, it remains running until a termination signal is received; --timeout <milliseconds> can bound that lifetime for automation.
Scripts can read and modify the environment of the running Node process. Use property access or the explicit methods:
guildId = env.TEST_GUILD
tokenHint = env.get("DISCRIPT_MODE")
env.set("DISCRIPT_LAST_RUN", "agent")
env.clear("DISCRIPT_TEMP")
Environment values are strings; missing variables return null. Avoid printing secrets such as DISCORD_TOKEN.
Use bounded for-in loops to process result collections:
members = discord.guilds.get("<guild-id>").members.list()
for (member in members) {
print(member.username)
}
Scripts can schedule asynchronous work and compose concurrent operations:
every(60000) { print("heartbeat") }
after(1000) { print("started") }
values = parallel(sleep(10), sleep(10))
Arrow callbacks can transform and select collections:
names = map(members, member => member.username)
active = filter(members, member => member.roles != null)
Reusable script functions use fn and return:
fn announce(name) {
return "Hello " + name
}
print(announce("Discord"))
Reusable source files can be loaded into the current script scope:
import "./shared.discript"
print(announce("Discord"))
Threads support inspection and guarded lifecycle operations:
npm start -- threads list --channel <channel-id> --json
npm start -- threads create --channel <channel-id> --name "topic" --yes --json
npm start -- threads archive --channel <channel-id> --thread <thread-id> --yes --jsonRole changes require explicit approval:
npm start -- roles add --guild <guild-id> --user <user-id> --role <role-id> --yes --json
npm start -- roles remove --guild <guild-id> --user <user-id> --role <role-id> --yes --json
npm start -- roles create --guild <guild-id> --name "Helper" --yes --json
npm start -- roles update --guild <guild-id> --role <role-id> --name "Helper" --yes --json
npm start -- roles delete --guild <guild-id> --role <role-id> --yes --jsonModeration operations also require explicit approval:
Moderation also rejects bot, owner, self, and targets at or above the bot’s role hierarchy.
npm start -- moderation timeout --guild <guild-id> --user <user-id> --duration 3600000 --reason "reason" --yes --json
npm start -- moderation kick --guild <guild-id> --user <user-id> --reason "reason" --yes --json
npm start -- moderation ban --guild <guild-id> --user <user-id> --reason "reason" --yes --jsonPreview or send a message. Writes require explicit approval:
npm start -- messages send --channel <channel-id> --content "Hello" --dry-run --json
npm start -- messages send --channel <channel-id> --content "Hello" --yes --jsonCreate a test channel with the configured test guild:
npm start -- channels create --guild "$TEST_GUILD" --name discript-test --dry-run --json
npm start -- channels create --guild "$TEST_GUILD" --name discript-test --yes --jsonDirect dry-runs validate command-specific required fields and return a structured preview without connecting to Discord:
npm start -- channels create --guild <guild-id> --name preview --dry-run --jsonAdd --validate to connect, resolve the target, and check permissions while still preventing the mutation:
npm start -- channels create --guild <guild-id> --name preview --dry-run --validate --jsonMessages can be read or changed with explicit approval for writes:
npm start -- messages get --channel <channel-id> --message <message-id> --json
npm start -- messages edit --channel <channel-id> --message <message-id> --content "Updated" --yes --json
npm start -- messages delete --channel <channel-id> --message <message-id> --yes --json
npm start -- messages react --channel <channel-id> --message <message-id> --emoji "👍" --yes --json
npm start -- messages pin --channel <channel-id> --message <message-id> --yes --json
npm start -- messages unpin --channel <channel-id> --message <message-id> --yes --json
npm start -- messages bulk-delete --channel <channel-id> --messages <id1,id2> --yes --jsonEvaluate source inline:
npm start -- --eval 'guilds = discord.guilds.list(); guilds' --jsonFinite commands can be bounded for automation:
npm start -- --timeout 15000 --eval 'discord.guilds.list()' --jsonRead source from standard input:
printf '%s\n' 'discord.guilds.list()' | npm start -- --jsonFor agent pipelines and long-running scripts, use JSONL output:
npm start -- --eval 'print({phase: "start"}); discord.guilds.list()' --output jsonlRun a script file:
npm start -- examples/list-guilds.ds --jsonMore agent-oriented templates are included in examples/: safe-channel-workflow.ds demonstrates preview/force/try/exit handling, and event-monitor.ds demonstrates persistent event and timer handlers.
The language supports literals, variables, property access, function calls, sequential statements, functions, conditionals, loops, imports, environment access, async helpers, timers, event handlers, comments, dry-run/approval options, and script-level exit statuses. The CLI and language runtime use the same Discord capability layer. See docs/language/reference.md for the implemented contract.
npm test
npm run lintThe tests are deterministic where possible and do not require a Discord connection. Live smoke commands require a configured DISCORD_TOKEN.
Never commit .env, bot tokens, credential-bearing URLs, or raw Discord payloads containing sensitive data. Mutating operations will require explicit safeguards as the capability surface expands.
Product and behavior requirements are maintained in SPEC.md and specs/.