spearkit 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/spearkit/SKILL.md +247 -0
- package/.claude/skills/spearkit/reference/cheatsheet.md +329 -0
- package/AGENTS.md +261 -0
- package/README.md +23 -3
- package/dist/index.cjs +599 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +524 -2
- package/dist/index.d.ts +524 -2
- package/dist/index.js +576 -19
- package/dist/index.js.map +1 -1
- package/docs/README.md +72 -0
- package/docs/api-reference.md +777 -0
- package/docs/auto-defer.md +74 -0
- package/docs/client.md +245 -0
- package/docs/collectors.md +65 -0
- package/docs/commands.md +203 -0
- package/docs/components.md +281 -0
- package/docs/context-menus.md +121 -0
- package/docs/context.md +293 -0
- package/docs/cooldown.md +125 -0
- package/docs/env.md +130 -0
- package/docs/errors.md +73 -0
- package/docs/events.md +152 -0
- package/docs/getting-started.md +147 -0
- package/docs/guards.md +146 -0
- package/docs/loading.md +144 -0
- package/docs/logging.md +195 -0
- package/docs/messages.md +35 -0
- package/docs/migration.md +160 -0
- package/docs/options.md +163 -0
- package/docs/permissions.md +68 -0
- package/docs/plugins.md +116 -0
- package/docs/prefix.md +234 -0
- package/docs/scheduler.md +111 -0
- package/docs/shutdown.md +42 -0
- package/docs/store.md +90 -0
- package/docs/usage.md +188 -0
- package/llms-full.txt +4619 -0
- package/llms.txt +127 -0
- package/package.json +9 -3
package/AGENTS.md
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# AGENTS.md — writing code with spearkit
|
|
2
|
+
|
|
3
|
+
Authoritative rules for AI agents (and humans) writing Discord bots with
|
|
4
|
+
**spearkit**. Read this first; it is short on purpose. For depth, load
|
|
5
|
+
[`llms-full.txt`](./llms-full.txt) (every guide + the full API reference in one
|
|
6
|
+
file) or browse [`docs/`](./docs) and [`examples/`](./examples).
|
|
7
|
+
|
|
8
|
+
spearkit is **discord.js++**: it re-exports the entire discord.js surface (so it
|
|
9
|
+
is a drop-in replacement) and adds a fully type-safe layer for slash commands,
|
|
10
|
+
options, components, events, cooldowns, scheduled tasks, prefix commands,
|
|
11
|
+
logging, usage tracking and dotenv. Install: `npm install spearkit discord.js`.
|
|
12
|
+
|
|
13
|
+
## Golden rules
|
|
14
|
+
|
|
15
|
+
1. **Import everything from `"spearkit"`.** Both spearkit's helpers and every
|
|
16
|
+
discord.js symbol (`Client`, `EmbedBuilder`, `GatewayIntentBits`, `REST`,
|
|
17
|
+
`Routes`, …) come from `"spearkit"`. Do **not** add a separate
|
|
18
|
+
`import … from "discord.js"`.
|
|
19
|
+
2. **Use `SpearClient`, not `Client`.** It extends discord.js `Client` and wires
|
|
20
|
+
up command/event/component routing. `intents` may be omitted (defaults to
|
|
21
|
+
`Intents.default`).
|
|
22
|
+
3. **Co-locate definition + handler.** A command's options and `run`, a button's
|
|
23
|
+
look and click logic, a modal's fields and submit — each in one object.
|
|
24
|
+
4. **Never write an `interactionCreate` switch.** Define commands/components and
|
|
25
|
+
`client.register(...)` them; spearkit routes by command name and custom-id
|
|
26
|
+
namespace and decodes typed params for you.
|
|
27
|
+
5. **Lifecycle order:** `client.register(...)` → `await client.start(token)` →
|
|
28
|
+
`await client.deployCommands({ guildId })`. Deploy **after** `start()` (it
|
|
29
|
+
uses the client's authenticated REST and the ready application id).
|
|
30
|
+
6. **The ready event is `clientReady`**, not `ready` (discord.js v14.16 rename).
|
|
31
|
+
7. **Trust inference.** Required options are non-nullable, optional ones are
|
|
32
|
+
`T | undefined`, `choices` narrow to a literal union, custom-id `{param}`s and
|
|
33
|
+
modal field keys are typed. Don't cast; don't annotate handler args.
|
|
34
|
+
|
|
35
|
+
## Use cases — reach for
|
|
36
|
+
|
|
37
|
+
Map the task to the API. Patterns and signatures are below and in `docs/`.
|
|
38
|
+
|
|
39
|
+
**Bot setup & lifecycle**
|
|
40
|
+
|
|
41
|
+
| Want to… | Reach for |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| Start a bot and connect | `new SpearClient({ intents })` + `await client.start(token)` |
|
|
44
|
+
| Choose gateway intents | `Intents.none / default / guilds / messages / all` |
|
|
45
|
+
| Wire up handlers | `client.register(...)`; one file per handler → `client.load(dir)` |
|
|
46
|
+
| Push commands to Discord | `client.deployCommands({ guildId })`; slash + context menus, safe CI → `client.deployAllCommands({ strategy: "diff", dryRun })` |
|
|
47
|
+
| Package a reusable feature | `definePlugin(...)` + `client.use(...)` |
|
|
48
|
+
| Migrate an existing discord.js bot | import from `"spearkit"`, swap `Client` → `SpearClient` |
|
|
49
|
+
|
|
50
|
+
**Commands & input**
|
|
51
|
+
|
|
52
|
+
| Want to… | Reach for |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| A slash command | `command({ name, description, run })` |
|
|
55
|
+
| Typed inputs to a command | `options: { x: option.string/integer/number/boolean/user/channel/role/mentionable/attachment(...) }` |
|
|
56
|
+
| Group many commands under one name | `commandGroup` + `subcommand` / `subcommandGroup` |
|
|
57
|
+
| Suggest values while the user types | `option.string({ autocomplete })` |
|
|
58
|
+
| A right-click "Apps" action on a user/message | `userCommand` / `messageCommand` |
|
|
59
|
+
| A classic `!text` command | `prefixCommand(...)` + `new SpearClient({ prefix })` |
|
|
60
|
+
| Parse `!cmd` arguments into typed values | `args: (a) => a.snowflake().duration().rest()` → `ctx.options` |
|
|
61
|
+
| Avoid `Unknown interaction` (10062) on slow work | `command({ autoDefer: true })` / `new SpearClient({ autoDefer: true })` |
|
|
62
|
+
|
|
63
|
+
**Interactivity (components)**
|
|
64
|
+
|
|
65
|
+
| Want to… | Reach for |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| A clickable button | `button({ id, run })` → `row(btn.build(...))` |
|
|
68
|
+
| A URL button (no handler) | `linkButton` |
|
|
69
|
+
| A dropdown of fixed options | `stringSelect` |
|
|
70
|
+
| Pick users / roles / channels / mentionables | `userSelect` / `roleSelect` / `channelSelect` / `mentionableSelect` |
|
|
71
|
+
| A form with text fields | `modal` + `textInput` |
|
|
72
|
+
| Carry data through a component | custom-id params `id: "x:{id}"` → `ctx.params.id` |
|
|
73
|
+
| A paged list with next/prev | `paginate(...)` |
|
|
74
|
+
| An "Are you sure?" yes/no gate | `confirm(...)` |
|
|
75
|
+
|
|
76
|
+
**Replies & UX**
|
|
77
|
+
|
|
78
|
+
| Want to… | Reach for |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| Reply, public or hidden | `ctx.reply(...)` / `ctx.replyEphemeral(...)` |
|
|
81
|
+
| Work that takes >3s | `ctx.defer()` then `ctx.editReply(...)` |
|
|
82
|
+
| A styled success/error/info/warn embed | `ctx.success/error/info/warn(...)` |
|
|
83
|
+
| "Reply, edit, or follow-up — whichever fits" | `ctx.send(...)` |
|
|
84
|
+
|
|
85
|
+
**Cross-cutting concerns**
|
|
86
|
+
|
|
87
|
+
| Want to… | Reach for |
|
|
88
|
+
| --- | --- |
|
|
89
|
+
| React to gateway events | `event(name, run)`; once on startup → `event("clientReady", ...)` |
|
|
90
|
+
| Rate-limit a command/handler | `cooldown` (per-command or client-wide) |
|
|
91
|
+
| Restrict by role / permission / owner / guild | guards: `requireAnyRole` / `requireUserPermissions` / `requireOwner` / `guildOnly` |
|
|
92
|
+
| Run jobs on cron or interval | `task({ cron \| interval })` / `client.schedule(...)` |
|
|
93
|
+
| Delay once / staged follow-ups / recover on restart | `client.scheduler.delay` / `followUp` / `reconcile` |
|
|
94
|
+
| Structured logs to file/webhook | `client.logger` + `consoleSink` / `jsonlSink` / `webhookSink` |
|
|
95
|
+
| Track who used what | `new SpearClient({ usage })` + `MemoryUsageStore` / `JsonFileUsageStore` |
|
|
96
|
+
| Read typed env / load `.env` | `env.string/number/boolean/require` (auto-loaded on `start()`) |
|
|
97
|
+
| Shut down cleanly on SIGINT/SIGTERM | `client.enableGracefulShutdown({ onShutdown })` |
|
|
98
|
+
| Permission / role-hierarchy preflight | `moderationCheck(...)`, `missingPermissions(...)`, `canActOn(...)`, `ctx.botMissing(...)` |
|
|
99
|
+
| Wait for a reply / click / modal submission | `ctx.awaitMessageFrom(...)` / `ctx.awaitModal(...)` / `awaitComponent(...)` |
|
|
100
|
+
| Branch on a Discord API error | `isDiscordError(err, DiscordErrorCode.X)` / `explainDiscordError(err)` |
|
|
101
|
+
| Per-guild prefix from a store | `prefix: { dynamic: (message) => ... }` |
|
|
102
|
+
|
|
103
|
+
**Utilities (primitives)**
|
|
104
|
+
|
|
105
|
+
| Want to… | Reach for |
|
|
106
|
+
| --- | --- |
|
|
107
|
+
| Stop concurrent runs per key (e.g. per user) | `KeyedLock` |
|
|
108
|
+
| Fetch that returns `null` instead of throwing | `safeFetch.{member,channel,message,user,guild,role}` |
|
|
109
|
+
| Format/parse `"1h30m"` durations | `formatDuration` / `parseDuration` |
|
|
110
|
+
| Render `<t:…>` Discord timestamps | `discordTimestamp` / `relativeTimestamp` |
|
|
111
|
+
| In-memory cache / counters / rate-limit window | `MemoryCache` |
|
|
112
|
+
| Load JSON/JSON5/YAML config | `loadConfig` |
|
|
113
|
+
| Persist key-value data / per-guild settings | `MemoryStore` / `JsonStore` + `createSettings({ store, defaults })` |
|
|
114
|
+
| Split text to Discord's 2000-char limit | `chunkMessage(text)` / `truncate(text, max)` |
|
|
115
|
+
|
|
116
|
+
## Canonical patterns
|
|
117
|
+
|
|
118
|
+
### Client + lifecycle
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { SpearClient, Intents, command, option, event } from "spearkit";
|
|
122
|
+
|
|
123
|
+
const client = new SpearClient({ intents: Intents.default }); // Intents: none|default|guilds|messages|all
|
|
124
|
+
|
|
125
|
+
const ready = event("clientReady", (c) => console.log(`Online as ${c.user.tag}`));
|
|
126
|
+
|
|
127
|
+
client.register(/* commands, events, components */ ready);
|
|
128
|
+
await client.start(process.env.DISCORD_TOKEN); // falls back to DISCORD_TOKEN
|
|
129
|
+
await client.deployCommands({ guildId: process.env.GUILD_ID }); // omit guildId for global (slow)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Slash commands + typed options
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
const echo = command({
|
|
136
|
+
name: "echo",
|
|
137
|
+
description: "Repeat a message",
|
|
138
|
+
options: {
|
|
139
|
+
text: option.string({ description: "What to say", required: true }), // string
|
|
140
|
+
times: option.integer({ description: "Count", minValue: 1, maxValue: 5 }), // number | undefined
|
|
141
|
+
who: option.user({ description: "Mention", required: true }), // User
|
|
142
|
+
},
|
|
143
|
+
run: (ctx) =>
|
|
144
|
+
ctx.reply(ctx.options.text.repeat(ctx.options.times ?? 1)),
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Builders: `option.string|integer|number|boolean|user|channel|role|mentionable|attachment`.
|
|
149
|
+
Autocomplete is co-located: `option.string({ autocomplete: (ctx) => [{ name, value }] })`.
|
|
150
|
+
Subcommands: `commandGroup({ name, description, subcommands, groups })` with
|
|
151
|
+
`subcommand(...)` and `subcommandGroup(...)`.
|
|
152
|
+
|
|
153
|
+
### Interactive components
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import { button, stringSelect, modal, textInput, row } from "spearkit";
|
|
157
|
+
|
|
158
|
+
const vote = button({
|
|
159
|
+
id: "vote:{choice}", // {choice} → typed param
|
|
160
|
+
label: "Yes", style: "Success", // "Primary"|"Secondary"|"Success"|"Danger"
|
|
161
|
+
run: (ctx) => ctx.update(`You chose ${ctx.params.choice}`), // ctx.params.choice: string
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const colour = stringSelect({
|
|
165
|
+
id: "colour",
|
|
166
|
+
options: [{ label: "Red", value: "red" }, { label: "Blue", value: "blue" }],
|
|
167
|
+
run: (ctx) => ctx.replyEphemeral(ctx.values.join(", ")), // ctx.values: string[]
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const feedback = modal({
|
|
171
|
+
id: "feedback:{ticket}",
|
|
172
|
+
title: "Feedback",
|
|
173
|
+
fields: { summary: textInput({ label: "Summary", required: true }) },
|
|
174
|
+
run: (ctx) => ctx.reply(`#${ctx.params.ticket}: ${ctx.fields.summary}`), // typed params + fields
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
client.register(vote, colour, feedback);
|
|
178
|
+
|
|
179
|
+
// Put them in a message — build() requires exactly the params the id declares:
|
|
180
|
+
await channel.send({ content: "Choose:", components: [row(vote.build({ choice: "yes" })), row(colour.build())] });
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Builders: `button`, `linkButton`, `stringSelect`, `userSelect`, `roleSelect`,
|
|
184
|
+
`channelSelect`, `mentionableSelect`, `modal` (+ `textInput`), `row`.
|
|
185
|
+
Component context: `ctx.params`, `ctx.update(...)`, `ctx.deferUpdate()`,
|
|
186
|
+
`ctx.showModal(modal)`; selects add `ctx.values` (+ `ctx.users/roles/channels/members`);
|
|
187
|
+
modals add `ctx.fields`.
|
|
188
|
+
|
|
189
|
+
### Context (every handler)
|
|
190
|
+
|
|
191
|
+
`ctx.reply` · `ctx.replyEphemeral` · `ctx.defer({ ephemeral? })` · `ctx.editReply`
|
|
192
|
+
· `ctx.followUp` · `ctx.send` (state-aware) · `ctx.error(msg)` · preset embeds
|
|
193
|
+
`ctx.success/info/warn/error` (+ `ctx.replySuccess/Info/Warn/Error`) · accessors
|
|
194
|
+
`ctx.client/user/member/guild/guildId/channel/channelId/locale` · state
|
|
195
|
+
`ctx.deferred/replied`. For hidden replies prefer `ctx.replyEphemeral(...)` or
|
|
196
|
+
`ctx.reply({ content, ephemeral: true })` — spearkit normalizes it.
|
|
197
|
+
|
|
198
|
+
## Subsystems (most have a dedicated guide in docs/; all are in the API reference)
|
|
199
|
+
|
|
200
|
+
- **Guards** — `guards: [...]` on `command`/`prefixCommand`/`button`/`userCommand`/
|
|
201
|
+
`messageCommand`, or client-wide `new SpearClient({ guards })`. Helpers:
|
|
202
|
+
`guildOnly`, `dmOnly`, `requireAnyRole`, `requireAllRoles`, `requireOwner`,
|
|
203
|
+
`requireUserPermissions`, `requireBotPermissions`, `guard`, `denied`.
|
|
204
|
+
- **Cooldowns** — `command({ cooldown: number | CooldownConfig })` or
|
|
205
|
+
`new SpearClient({ cooldown })`; scopes `user|guild|channel|global`, `exempt`,
|
|
206
|
+
`overrides`.
|
|
207
|
+
- **Scheduled tasks** — `task({ name, cron?, interval?, runOnStart?, run })`,
|
|
208
|
+
`client.schedule(...)`, `client.scheduler.delay/followUp/reconcile`, `cron(expr)`.
|
|
209
|
+
- **Prefix commands** — `prefixCommand({ name, aliases?, cooldown?, guards?, args?, run })`
|
|
210
|
+
+ `new SpearClient({ prefix: "!" })`. Typed args:
|
|
211
|
+
`args: (a) => a.snowflake("target").duration("d").rest("reason")` → `ctx.options`.
|
|
212
|
+
Reading **other users'** message content needs the privileged `MessageContent`
|
|
213
|
+
intent — use `Intents.messages` and enable it in the Developer Portal.
|
|
214
|
+
- **Context menus** — `userCommand({ name, run: (ctx) => ctx.targetUser })`,
|
|
215
|
+
`messageCommand({ name, run: (ctx) => ctx.targetMessage })`. Deploy slash +
|
|
216
|
+
menus together with `client.deployAllCommands({ guildId, strategy: "diff", dryRun? })`.
|
|
217
|
+
- **Pagination / confirm** — `paginate(interaction, items, { pageSize, render, user?, timeoutMs?, controls?, ephemeral? })`;
|
|
218
|
+
`const { confirmed } = await confirm(interaction, { body, confirm?, cancel?, user?, timeoutMs?, ephemeral? })`.
|
|
219
|
+
- **Logging** — `client.logger`; configure via `new SpearClient({ logger: { level, transports: [consoleSink, jsonlSink(path), webhookSink({ url })] } })`.
|
|
220
|
+
- **Usage tracking** — `new SpearClient({ usage: { store?, channel?, format? } })`;
|
|
221
|
+
`MemoryUsageStore`, `JsonFileUsageStore`.
|
|
222
|
+
- **Env** — `.env` auto-loads on `start()`; read with `env.string/number/boolean/require`.
|
|
223
|
+
- **Plugins** — `definePlugin({ name, setup(client) })`, then `await client.use(plugin)`.
|
|
224
|
+
- **File-based loading** — `await client.load(dir)`. Imports **compiled JS**
|
|
225
|
+
(default extensions `.js/.mjs/.cjs`), so build before running compiled output.
|
|
226
|
+
- **Primitives** — `KeyedLock`, `safeFetch.{member,channel,message,user,guild,role,try}`,
|
|
227
|
+
`formatDuration`/`parseDuration`/`discordTimestamp`/`relativeTimestamp`,
|
|
228
|
+
`MemoryCache`, `loadConfig`.
|
|
229
|
+
- **Auto-defer** — `command({ autoDefer: true | { ephemeral?, delayMs? } })` or
|
|
230
|
+
`new SpearClient({ autoDefer: true })`; auto-`deferReply()` before Discord's 3s
|
|
231
|
+
window so slow handlers don't 10062. Respond via `ctx.send`/`ctx.editReply`.
|
|
232
|
+
- **Graceful shutdown** — `client.enableGracefulShutdown({ onShutdown, timeoutMs? })`
|
|
233
|
+
(or `gracefulShutdown(client, ...)`); clean `SIGINT`/`SIGTERM` teardown.
|
|
234
|
+
- **Permissions & moderation** — `missingPermissions`, `botMissingPermissions`,
|
|
235
|
+
`hasPermissions`, `compareRoles`, `canActOn`, `moderationCheck`, `formatPermissions`;
|
|
236
|
+
on context: `ctx.botPermissions`, `ctx.botMissing(...)`, `ctx.userMissing(...)`.
|
|
237
|
+
- **Persistent storage** — `MemoryStore`/`JsonStore` (`KeyValueStore`), `namespaced(...)`,
|
|
238
|
+
and typed per-guild `createSettings({ store, defaults, namespace? })`.
|
|
239
|
+
- **Collectors** — `ctx.awaitMessageFrom(...)`, `ctx.awaitModal(...)`, plus standalone
|
|
240
|
+
`awaitMessage`, `awaitComponent`, `showAndAwaitModal` (all resolve `null` on timeout).
|
|
241
|
+
- **Discord errors** — `isDiscordError(err, DiscordErrorCode.X)`, `isHTTPError`,
|
|
242
|
+
`isRateLimitError`, `explainDiscordError`; named `DiscordErrorCode` map.
|
|
243
|
+
- **Message formatting** — `chunkMessage(text)` (2000-char-safe split), `truncate(text, max)`,
|
|
244
|
+
`MESSAGE_CHARACTER_LIMIT`.
|
|
245
|
+
|
|
246
|
+
## Common mistakes to avoid
|
|
247
|
+
|
|
248
|
+
- Importing from `"discord.js"` in a spearkit project (use `"spearkit"`).
|
|
249
|
+
- Listening on `"ready"` instead of `"clientReady"`.
|
|
250
|
+
- Calling `deployCommands()` before `start()`.
|
|
251
|
+
- Hand-rolling an `interactionCreate` switch or parsing custom ids by hand.
|
|
252
|
+
- Passing the wrong/missing params to `component.build(...)` (it is typed — pass
|
|
253
|
+
exactly the `{param}`s in the id; custom ids cap at 100 chars).
|
|
254
|
+
- Deploying on every restart — only deploy when command *definitions* change.
|
|
255
|
+
|
|
256
|
+
## For maintainers
|
|
257
|
+
|
|
258
|
+
`docs/*.md` is the single source of truth. `llms.txt`, `llms-full.txt` and the
|
|
259
|
+
`website/public/` copies are generated — run `npm run docs:llms` after editing
|
|
260
|
+
docs. A ready-to-use agent skill lives at
|
|
261
|
+
[`.claude/skills/spearkit/`](./.claude/skills/spearkit).
|
package/README.md
CHANGED
|
@@ -15,15 +15,15 @@ npm install spearkit discord.js
|
|
|
15
15
|
## Batteries included
|
|
16
16
|
|
|
17
17
|
- **Type-safe slash commands**, options, subcommands, autocomplete, buttons, selects and modals — no `interactionCreate` switch.
|
|
18
|
-
- **Cooldowns** — per user/
|
|
18
|
+
- **Cooldowns** — per user/guild/channel/global, with per-role/per-user exemptions and overrides ([guide](./docs/cooldown.md)).
|
|
19
19
|
- **Scheduled tasks** — cron and interval jobs, started on ready ([guide](./docs/scheduler.md)).
|
|
20
20
|
- **Prefix commands** — classic `!text` commands that share cooldowns ([guide](./docs/prefix.md)).
|
|
21
21
|
- **Structured logging** — leveled, scoped, pluggable; every error flows through it ([guide](./docs/logging.md)).
|
|
22
22
|
- **Usage tracking** — record who used what to a database and/or a Discord channel ([guide](./docs/usage.md)).
|
|
23
23
|
- **dotenv built in** — auto-load `.env` and read typed env vars ([guide](./docs/env.md)).
|
|
24
24
|
- **Plugins & file-based loading** for organising larger bots.
|
|
25
|
-
- **Guards** — declarative `requireAnyRole`/`requireUserPermissions`/`guildOnly`/`requireOwner` preconditions on commands, components and prefix commands ([
|
|
26
|
-
- **Context-menu commands** — `userCommand` / `messageCommand` with typed `targetUser` / `targetMessage` ([
|
|
25
|
+
- **Guards** — declarative `requireAnyRole`/`requireUserPermissions`/`guildOnly`/`requireOwner` preconditions on commands, components and prefix commands ([guide](./docs/guards.md)).
|
|
26
|
+
- **Context-menu commands** — `userCommand` / `messageCommand` with typed `targetUser` / `targetMessage` ([guide](./docs/context-menus.md)).
|
|
27
27
|
- **Preset embeds** — `ctx.success/info/warn/error` and `client.embeds` factory with configurable colors/icons ([API ref](./docs/api-reference.md#embeds--preset-replies)).
|
|
28
28
|
- **Pagination & confirmation** — `paginate(...)` and `confirm(...)` button flows with user-only filter and timeout.
|
|
29
29
|
- **Typed prefix args** — `prefixCommand({ args: a => a.snowflake("target").duration("d").rest("reason"), run: ctx => ctx.options })`.
|
|
@@ -31,6 +31,13 @@ npm install spearkit discord.js
|
|
|
31
31
|
- **Logger transports** — multi-sink (`consoleSink`, `jsonlSink`, `webhookSink`); per-level routing.
|
|
32
32
|
- **Scheduler extras** — `scheduler.delay/followUp/reconcile` for one-shot jobs and on-ready recovery.
|
|
33
33
|
- **Deploy strategy** — `deployAllCommands({ dryRun, strategy: "diff" })` for safe CI deploys.
|
|
34
|
+
- **Auto-defer** — `command({ autoDefer: true })` / `new SpearClient({ autoDefer: true })` to dodge `Unknown interaction` (10062) on slow handlers ([API ref](./docs/api-reference.md#auto-defer)).
|
|
35
|
+
- **Graceful shutdown** — `client.enableGracefulShutdown({ onShutdown })` for clean `SIGINT`/`SIGTERM` teardown ([API ref](./docs/api-reference.md#graceful-shutdown)).
|
|
36
|
+
- **Permissions & moderation** — `moderationCheck`, `missingPermissions`, `canActOn`, `ctx.botMissing(...)` role-hierarchy/permission preflights ([API ref](./docs/api-reference.md#permissions--moderation)).
|
|
37
|
+
- **Persistent storage** — `MemoryStore`/`JsonStore` key-value stores + typed per-guild `createSettings(...)` ([API ref](./docs/api-reference.md#persistent-storage)).
|
|
38
|
+
- **Collectors** — `ctx.awaitMessageFrom(...)`, `ctx.awaitModal(...)`, `awaitComponent(...)` without hand-rolled collectors ([API ref](./docs/api-reference.md#collectors)).
|
|
39
|
+
- **Discord error helpers** — `isDiscordError(err, DiscordErrorCode.UnknownMessage)`, `explainDiscordError(...)` ([API ref](./docs/api-reference.md#discord-errors)).
|
|
40
|
+
- **Dynamic prefixes** — per-guild prefix resolution via `prefix: { dynamic }` ([guide](./docs/prefix.md#dynamic-per-guild-prefixes)).
|
|
34
41
|
|
|
35
42
|
## Documentation
|
|
36
43
|
|
|
@@ -38,6 +45,19 @@ npm install spearkit discord.js
|
|
|
38
45
|
- **Guides & API reference** ([`docs/`](./docs)) — the Markdown the site is built from.
|
|
39
46
|
- **Examples** ([`examples/`](./examples)) — one folder per topic (commands, options, components, events, loading, …).
|
|
40
47
|
|
|
48
|
+
## For AI agents
|
|
49
|
+
|
|
50
|
+
spearkit ships machine-readable guidance so coding agents write correct code with it:
|
|
51
|
+
|
|
52
|
+
- **[`AGENTS.md`](./AGENTS.md)** — the golden rules and canonical patterns, auto-read by most coding agents.
|
|
53
|
+
- **[`llms.txt`](./llms.txt)** — an [llmstxt.org](https://llmstxt.org) index of the docs; **[`llms-full.txt`](./llms-full.txt)** is every guide and the full API reference in one file.
|
|
54
|
+
- **Agent skill** ([`.claude/skills/spearkit/`](./.claude/skills/spearkit)) — a drop-in [Agent Skill](https://docs.anthropic.com/en/docs/agents-and-tools/agent-skills) with recipes and a symbol cheatsheet.
|
|
55
|
+
|
|
56
|
+
`AGENTS.md`, `llms.txt`, `llms-full.txt`, `docs/` and the agent skill ship in the
|
|
57
|
+
npm package as plain files (no install hook), so an installed copy lives under
|
|
58
|
+
`node_modules/spearkit/` — e.g. `node_modules/spearkit/.claude/skills/spearkit/SKILL.md`.
|
|
59
|
+
The `llms` files are generated from `docs/`; run `npm run docs:llms` after editing docs.
|
|
60
|
+
|
|
41
61
|
## Quick start
|
|
42
62
|
|
|
43
63
|
```ts
|