@wolfstar/create-http-framework 2.7.0 → 2.8.0-next-20261004142621

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/README.md CHANGED
@@ -48,26 +48,27 @@ The CLI will guide you through the following prompts:
48
48
 
49
49
  ## Options
50
50
 
51
- | Flag | Alias | Description |
52
- | ------------------------------------ | ----- | -------------------------------------------------------------------------- |
53
- | `--overwrite` | | Overwrite the target directory if it already exists |
54
- | `--no-interactive` | | Skip all prompts and use defaults / flags |
55
- | `--interactive` | `-i` | Force interactive prompts even when an AI agent is detected |
56
- | `--package-manager <pm>` | | Choose npm, yarn, pnpm, or bun |
57
- | `--language <lang>` | | Choose TypeScript (`ts`) or JavaScript (`js`) |
58
- | `--build <tool>` | | Choose `tsc6`, `tsc7`, `tsdown`, `vite`, or `vite-nitro` for TypeScript |
59
- | `--lint <linter>` | | Choose `none`, `eslint`, or `oxlint` |
60
- | `--format <formatter>` | | Choose `none`, `prettier`, or `oxfmt` |
61
- | `--port <number>` | | Set the HTTP port (default: `3000`) |
62
- | `--i18n` / `--no-i18n` | | Enable or disable `@wolfstar/plugin-i18next` scaffolding |
63
- | `--subcommands` / `--no-subcommands` | | Enable or disable the example subcommand command |
64
- | `--testing` / `--no-testing` | | Enable or disable the Vitest + `@wolfstar/http-framework-test-utils` setup |
65
- | `--gateway` / `--no-gateway` | | Enable or disable `@wolfstar/plugin-gateway` scaffolding |
66
- | `--cache` / `--no-cache` | | Enable or disable `@wolfstar/plugin-cache` (turns `--gateway` on) |
67
- | `--redis` / `--no-redis` | | Cache in Redis instead of memory (turns `--cache` on) |
68
- | `--sharder` / `--no-sharder` | | Enable or disable `@wolfstar/plugin-sharder` (turns `--gateway` on) |
69
- | `--install` / `--no-install` | | Enable or disable dependency installation |
70
- | `--help` | `-h` | Print usage and exit |
51
+ | Flag | Alias | Description |
52
+ | ------------------------------------ | ----- | --------------------------------------------------------------------------- |
53
+ | `--overwrite` | | Overwrite the target directory if it already exists |
54
+ | `--no-interactive` | | Skip all prompts and use defaults / flags |
55
+ | `--interactive` | `-i` | Force interactive prompts even when an AI agent is detected |
56
+ | `--package-manager <pm>` | | Choose npm, yarn, pnpm, or bun |
57
+ | `--language <lang>` | | Choose TypeScript (`ts`) or JavaScript (`js`) |
58
+ | `--build <tool>` | | Choose `tsc6`, `tsc7`, `tsdown`, `vite`, or `vite-nitro` for TypeScript |
59
+ | `--lint <linter>` | | Choose `none`, `eslint`, or `oxlint` |
60
+ | `--format <formatter>` | | Choose `none`, `prettier`, or `oxfmt` |
61
+ | `--port <number>` | | Set the HTTP port (default: `3000`) |
62
+ | `--i18n` / `--no-i18n` | | Enable or disable `@wolfstar/plugin-i18next` scaffolding |
63
+ | `--subcommands` / `--no-subcommands` | | Enable or disable the example subcommand command |
64
+ | `--testing` / `--no-testing` | | Enable or disable the Vitest + `@wolfstar/http-framework-test-utils` setup |
65
+ | `--gateway` / `--no-gateway` | | Enable or disable `@wolfstar/plugin-gateway` scaffolding |
66
+ | `--cache` / `--no-cache` | | Enable or disable `@wolfstar/plugin-cache` (turns `--gateway` on) |
67
+ | `--redis` / `--no-redis` | | Cache in Redis instead of memory (turns `--cache` on) |
68
+ | `--sharder` / `--no-sharder` | | Enable or disable `@wolfstar/plugin-sharder` (turns `--gateway` on) |
69
+ | `--tunnel` / `--no-tunnel` | | Open a cloudflared quick tunnel in `stars dev` (`dev.tunnel` in the config) |
70
+ | `--install` / `--no-install` | | Enable or disable dependency installation |
71
+ | `--help` | `-h` | Print usage and exit |
71
72
 
72
73
  ## Non-interactive / AI agent mode
73
74
 
@@ -106,6 +107,9 @@ my-discord-bot/
106
107
  ├── vitest.setup.ts # Vitest setup file (--testing)
107
108
  ├── compose.yaml # Local Redis server (--redis)
108
109
  ├── README.md # Generated project README
110
+ ├── AGENTS.md # The project's commands, layout and rules, for AI coding agents
111
+ ├── llms.txt # The upstream documentation by topic, for AI coding agents
112
+ ├── stars.config.ts # The `stars` CLI configuration (build, dev, tunnel)
109
113
  ├── .env # Environment variables (DISCORD_TOKEN, DISCORD_PUBLIC_KEY)
110
114
  ├── .gitignore
111
115
  ├── package.json
@@ -117,6 +121,9 @@ my-discord-bot/
117
121
  - `src/commands/math.ts` is only generated when **Subcommands** is enabled.
118
122
  - `src/lib/cache.ts` is only generated with **Cache**, `src/shard.ts` with **Sharder**, and `compose.yaml` with **Redis**.
119
123
  - `tests/ping.test.ts`, `vitest.config.ts`, and `vitest.setup.ts` are only generated when **Testing** is enabled.
124
+ - `AGENTS.md` and `llms.txt` are always generated and only describe the features the project was created with. When one already exists and is not this generator's own unedited output (you wrote it, or edited it), a rerun leaves it as it is and says so.
125
+ - With a linter, the generated configuration (`.oxlintrc.json` or `eslint.config.mjs`) enables the rules of [`@wolfstar/eslint-plugin-http-framework`](../eslint-plugin-http-framework): decorator order, raw Discord fetches, dynamic translation keys and the other mistakes TypeScript cannot catch.
126
+ - `--tunnel` (or the **Dev tunnel** feature in the prompt) writes `dev: { tunnel: true }` to `stars.config`, so `stars dev` opens a cloudflared quick tunnel and Discord reaches the bot on your machine. Without it, press `t` in `stars dev` to open one on demand.
120
127
 
121
128
  ### Vite and Nitro
122
129
 
package/dist/index.js CHANGED
@@ -145,6 +145,7 @@ async function fetchDependencyVersions(selections) {
145
145
  names.add(selections.language === "ts" ? "typescript-eslint" : "@eslint/js");
146
146
  }
147
147
  if (selections.linter === "oxlint") names.add("oxlint");
148
+ if (selections.linter !== "none") names.add("@wolfstar/eslint-plugin-http-framework");
148
149
  if (selections.formatter === "prettier") names.add("prettier");
149
150
  if (selections.formatter === "oxfmt") names.add("oxfmt");
150
151
  const list = [...names];
@@ -219,6 +220,17 @@ function runCommand(command, args, dir) {
219
220
  //#endregion
220
221
  //#region src/tools/projectFiles.ts
221
222
  const caret = (version) => `^${version}`;
223
+ const FRAMEWORK_LINT_PLUGIN = "@wolfstar/eslint-plugin-http-framework";
224
+ /** The rules of {@link FRAMEWORK_LINT_PLUGIN}, listed for oxlint, whose JSON configuration cannot import them. */
225
+ const FRAMEWORK_LINT_RULES = [
226
+ "wolfstar/apply-options-decorator-order",
227
+ "wolfstar/require-subcommand-parent",
228
+ "wolfstar/no-raw-discord-fetch",
229
+ "wolfstar/no-dynamic-translation-key",
230
+ "wolfstar/prefer-apply-localized-builder",
231
+ "wolfstar/no-hoisted-plugin-register-import",
232
+ "wolfstar/no-deprecated-i18n-package"
233
+ ];
222
234
  function sortKeys(record) {
223
235
  return Object.fromEntries(Object.entries(record).sort(([a], [b]) => a.localeCompare(b)));
224
236
  }
@@ -300,6 +312,7 @@ function buildDevDependencies(ctx) {
300
312
  if (ctx.language === "ts") dev["typescript-eslint"] = caret(v["typescript-eslint"]);
301
313
  else dev["@eslint/js"] = caret(v["@eslint/js"]);
302
314
  } else if (ctx.linter === "oxlint") dev["oxlint"] = caret(v["oxlint"]);
315
+ if (ctx.linter !== "none") dev[FRAMEWORK_LINT_PLUGIN] = caret(v[FRAMEWORK_LINT_PLUGIN]);
303
316
  if (ctx.formatter === "prettier") dev["prettier"] = caret(v["prettier"]);
304
317
  else if (ctx.formatter === "oxfmt") dev["oxfmt"] = caret(v["oxfmt"]);
305
318
  if (ctx.i18n) dev["@wolfstar/i18next-type-generator"] = caret(v["@wolfstar/i18next-type-generator"]);
@@ -389,12 +402,16 @@ function writeTsconfig(targetDir, ctx) {
389
402
  */
390
403
  function writeStarsConfig(targetDir, ctx) {
391
404
  const isJs = ctx.language === "js";
392
- let options = !isJs && (ctx.buildTool === "tsc6" || ctx.buildTool === "tsc7") ? "{ build: { tool: 'tsc' }, env: false }" : isJs ? "{ env: false }" : "{}";
393
- if (!isJs && isViteBuild(ctx.buildTool)) options = `{ build: { tool: 'vite' }, experimental: { enableVite: true${isNitroBuild(ctx.buildTool) ? ", enableNitro: true, nitro: { preset: 'node-server' }" : ""} } }`;
405
+ const parts = !isJs && (ctx.buildTool === "tsc6" || ctx.buildTool === "tsc7") ? ["build: { tool: 'tsc' }", "env: false"] : isJs ? ["env: false"] : [];
406
+ if (!isJs && isViteBuild(ctx.buildTool)) {
407
+ const nitro = isNitroBuild(ctx.buildTool) ? ", enableNitro: true, nitro: { preset: 'node-server' }" : "";
408
+ parts.push("build: { tool: 'vite' }", `experimental: { enableVite: true${nitro} }`);
409
+ }
410
+ if (ctx.tunnel) parts.push("dev: { tunnel: true }");
394
411
  const content = [
395
412
  "import { defineConfig } from '@wolfstar/http-framework/config';",
396
413
  "",
397
- `export default defineConfig(${options});`,
414
+ `export default defineConfig(${parts.length > 0 ? `{ ${parts.join(", ")} }` : "{}"});`,
398
415
  ""
399
416
  ].join("\n");
400
417
  writeFile(join(targetDir, isJs ? "stars.config.js" : "stars.config.ts"), content);
@@ -403,27 +420,33 @@ function writeLinterConfig(targetDir, ctx) {
403
420
  if (ctx.linter === "oxlint") writeFile(join(targetDir, ".oxlintrc.json"), json({
404
421
  $schema: "./node_modules/oxlint/configuration_schema.json",
405
422
  ...ctx.language === "ts" ? { plugins: ["typescript"] } : {},
423
+ jsPlugins: [FRAMEWORK_LINT_PLUGIN],
406
424
  categories: {
407
425
  correctness: "error",
408
426
  suspicious: "warn"
409
427
  },
428
+ rules: Object.fromEntries(FRAMEWORK_LINT_RULES.map((rule) => [rule, "error"])),
410
429
  ignorePatterns: ["dist/**", "node_modules/**"]
411
430
  }));
412
431
  else if (ctx.linter === "eslint") {
413
432
  const content = ctx.language === "ts" ? [
433
+ `import wolfstar, { recommendedRules } from '${FRAMEWORK_LINT_PLUGIN}';`,
414
434
  "import tseslint from 'typescript-eslint';",
415
435
  "",
416
436
  "export default tseslint.config(",
417
437
  " { ignores: ['dist/**'] },",
418
- " ...tseslint.configs.recommended",
438
+ " ...tseslint.configs.recommended,",
439
+ " { plugins: { wolfstar }, rules: recommendedRules }",
419
440
  ");",
420
441
  ""
421
442
  ].join("\n") : [
422
443
  "import js from '@eslint/js';",
444
+ `import wolfstar, { recommendedRules } from '${FRAMEWORK_LINT_PLUGIN}';`,
423
445
  "",
424
446
  "export default [",
425
447
  " { ignores: ['dist/**'] },",
426
- " js.configs.recommended",
448
+ " js.configs.recommended,",
449
+ " { plugins: { wolfstar }, rules: recommendedRules }",
427
450
  "];",
428
451
  ""
429
452
  ].join("\n");
@@ -497,6 +520,12 @@ function removeI18nDeclaration(outputDir) {
497
520
  if (existsSync(typesDir) && readdirSync(typesDir).length === 0) rmSync(typesDir, { recursive: true });
498
521
  }
499
522
  /**
523
+ * The files that describe a project to coding agents. A project often has its own already, and unlike the sources
524
+ * they are prose a rerun cannot merge: an existing one is only replaced when this generator wrote it and nobody
525
+ * edited it since.
526
+ */
527
+ const AGENT_DOCS = /* @__PURE__ */ new Set(["AGENTS.md", "llms.txt"]);
528
+ /**
500
529
  * What the Handlebars sources see: the persisted {@link TemplateContext} plus values derived from it. Deriving at render
501
530
  * time keeps the manifest small and lets manifests written before these fields existed still render.
502
531
  */
@@ -516,8 +545,10 @@ function toRenderContext(context) {
516
545
  });
517
546
  return {
518
547
  ...context,
548
+ typescript,
519
549
  vite: typescript && isViteBuild(context.buildTool),
520
550
  nitro: typescript && isNitroBuild(context.buildTool),
551
+ autoImports: typescript && context.buildTool === "tsdown",
521
552
  registersEnv: Boolean(context.autoEnv) && typescript && (context.buildTool === "tsdown" || context.buildTool === "vite"),
522
553
  commands
523
554
  };
@@ -694,7 +725,7 @@ function removeStaleGeneratedFiles(outputDir, context) {
694
725
  * - Non-`.hbs` files (e.g. static locale JSON under a feature's `src/locales/**`) are copied verbatim —
695
726
  * no extension stripped, no Handlebars compilation.
696
727
  */
697
- function processDir(root, outputDir, context) {
728
+ function processDir(root, outputDir, context, keep) {
698
729
  for (const absoluteSource of walkDir(root)) {
699
730
  const outputRelative = toOutputRelative(root, absoluteSource, context.language);
700
731
  if (outputRelative === null) continue;
@@ -702,6 +733,7 @@ function processDir(root, outputDir, context) {
702
733
  const isHandlebars = absoluteSource.endsWith(".hbs");
703
734
  const outputPath = join(outputDir, outputRelative);
704
735
  const content = isHandlebars ? Handlebars.compile(rawContent)(toRenderContext(context)) : rawContent;
736
+ if (keep?.(outputRelative, content)) continue;
705
737
  writeFile(outputPath, content);
706
738
  }
707
739
  }
@@ -710,13 +742,25 @@ function processDir(root, outputDir, context) {
710
742
  * {@link resolveFeatureDirs} on top, in order — feature files overwrite base files at the same
711
743
  * output-relative path (e.g. `features/i18n/src/main.ts.hbs` overwrites `base/src/main.ts.hbs`).
712
744
  *
745
+ * @param onKept Called with each of `AGENTS.md`/`llms.txt` that was left as it is, because it exists and is not this
746
+ * generator's own unedited output.
713
747
  * @returns Output-relative paths that looked stale (belong to a disabled feature or the other
714
748
  * language) but were left in place because they'd been hand-edited since the last run.
715
749
  */
716
- async function processTemplate(outputDir, context) {
750
+ async function processTemplate(outputDir, context, onKept) {
751
+ const manifestContext = readManifest(outputDir);
717
752
  const preserved = removeStaleGeneratedFiles(outputDir, context);
718
753
  if (!context.i18n) removeI18nDeclaration(outputDir);
719
- processDir(baseDir, outputDir, context);
754
+ processDir(baseDir, outputDir, context, (outputRelative, content) => {
755
+ if (!AGENT_DOCS.has(outputRelative)) return false;
756
+ const target = join(outputDir, outputRelative);
757
+ if (!existsSync(target)) return false;
758
+ const actual = readFileSync(target, "utf-8");
759
+ const source = join(baseDir, `${outputRelative}.hbs`);
760
+ const pristine = actual === content || manifestContext !== void 0 && renderSource(source, manifestContext) === actual;
761
+ if (!pristine) onKept?.(outputRelative);
762
+ return !pristine;
763
+ });
720
764
  for (const feature of resolveFeatureDirs(context)) processDir(join(featuresDir, feature), outputDir, context);
721
765
  writeManifest(outputDir, context);
722
766
  return preserved;
@@ -752,6 +796,7 @@ Options:
752
796
  --cache / --no-cache Toggle @wolfstar/plugin-cache, the gateway entity cache (default: off; enables --gateway)
753
797
  --redis / --no-redis Store the cache in Redis instead of memory (default: off; enables --cache)
754
798
  --sharder / --no-sharder Toggle @wolfstar/plugin-sharder, gateway shards across cluster workers (default: off; enables --gateway)
799
+ --tunnel / --no-tunnel Open a cloudflared quick tunnel in \`stars dev\`, so Discord reaches the bot (default: off)
755
800
  --install / --no-install Toggle dependency installation (default: on)
756
801
  --ignore Write into an existing, non-empty directory without clearing it
757
802
  --help, -h Print this message and exit
@@ -810,6 +855,7 @@ async function main() {
810
855
  const cliCache = argv["cache"];
811
856
  const cliRedis = argv["redis"];
812
857
  const cliSharder = argv["sharder"];
858
+ const cliTunnel = argv["tunnel"];
813
859
  const cliInstall = argv["install"];
814
860
  const { isAgent, agent } = await determineAgent();
815
861
  const agentMode = isAgent && flagInteractive !== true;
@@ -1037,6 +1083,7 @@ async function main() {
1037
1083
  let wantsCache;
1038
1084
  let wantsRedis;
1039
1085
  let wantsSharder;
1086
+ let wantsTunnel;
1040
1087
  const nitro = language === "ts" && isNitroBuild(buildTool);
1041
1088
  if (nonInteractive) {
1042
1089
  wantsI18n = cliI18n ?? false;
@@ -1047,6 +1094,7 @@ async function main() {
1047
1094
  wantsCache = cliCache ?? false;
1048
1095
  wantsRedis = cliRedis ?? false;
1049
1096
  wantsSharder = cliSharder ?? false;
1097
+ wantsTunnel = cliTunnel ?? false;
1050
1098
  } else {
1051
1099
  const featuresResult = await multiselect({
1052
1100
  message: "Which optional features would you like to add?",
@@ -1078,9 +1126,13 @@ async function main() {
1078
1126
  ...nitro ? [] : [{
1079
1127
  value: "sharder",
1080
1128
  label: "Gateway sharding across cluster workers (@wolfstar/plugin-sharder)"
1081
- }]
1129
+ }],
1130
+ {
1131
+ value: "tunnel",
1132
+ label: "Dev tunnel (a cloudflared quick tunnel, so Discord reaches the bot while developing)"
1133
+ }
1082
1134
  ],
1083
- initialValues: [],
1135
+ initialValues: cliTunnel ? ["tunnel"] : [],
1084
1136
  required: false
1085
1137
  });
1086
1138
  if (isCancel(featuresResult)) {
@@ -1095,6 +1147,7 @@ async function main() {
1095
1147
  wantsGateway = features.has("gateway");
1096
1148
  wantsCache = features.has("cache");
1097
1149
  wantsSharder = features.has("sharder");
1150
+ wantsTunnel = features.has("tunnel");
1098
1151
  wantsRedis = cliRedis ?? false;
1099
1152
  if (wantsCache && cliRedis === void 0) {
1100
1153
  const redisResult = await confirm({
@@ -1155,6 +1208,7 @@ async function main() {
1155
1208
  });
1156
1209
  s.stop("Versions fetched.");
1157
1210
  s.start("Generating project files...");
1211
+ const keptDocs = [];
1158
1212
  const preservedFiles = await processTemplate(targetDir, {
1159
1213
  name: projectName,
1160
1214
  port,
@@ -1168,8 +1222,9 @@ async function main() {
1168
1222
  redis: wantsRedis,
1169
1223
  sharder: wantsSharder,
1170
1224
  buildTool,
1225
+ tunnel: wantsTunnel,
1171
1226
  autoEnv: true
1172
- });
1227
+ }, (path) => keptDocs.push(path));
1173
1228
  writeProjectFiles(targetDir, {
1174
1229
  name: projectName,
1175
1230
  port,
@@ -1181,6 +1236,7 @@ async function main() {
1181
1236
  cache: wantsCache,
1182
1237
  redis: wantsRedis,
1183
1238
  sharder: wantsSharder,
1239
+ tunnel: wantsTunnel,
1184
1240
  packageManager,
1185
1241
  language,
1186
1242
  buildTool,
@@ -1189,6 +1245,7 @@ async function main() {
1189
1245
  versions
1190
1246
  });
1191
1247
  s.stop("Project files generated.");
1248
+ for (const file of keptDocs) log.warn(`Kept your existing "${file}" as it is — delete it and run again to get the generated one.`);
1192
1249
  for (const file of preservedFiles) log.warn(`Kept hand-edited "${file}" even though its feature is now disabled — remove it manually if it's no longer needed.`);
1193
1250
  if (wantsI18n) try {
1194
1251
  s.start("Generating i18n types...");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wolfstar/create-http-framework",
3
- "version": "2.7.0",
3
+ "version": "2.8.0-next-20261004142621",
4
4
  "description": "Create a new WolfStar HTTP Framework bot project",
5
5
  "keywords": [
6
6
  "cli",
@@ -0,0 +1,61 @@
1
+ # AGENTS.md
2
+
3
+ Guidance for AI coding agents working in `{{name}}`, a Discord bot built with
4
+ [`@wolfstar/http-framework`](https://www.npmjs.com/package/@wolfstar/http-framework). It receives interactions over
5
+ HTTP{{#if gateway}} and gateway events over a WebSocket (`@wolfstar/plugin-gateway`){{/if}}.
6
+
7
+ ## Commands
8
+
9
+ - `npm run dev` — build, run and restart the bot on changes (`stars dev`). Add `-- --no-tui` for plain line output,
10
+ which is what a non-interactive shell gets anyway.
11
+ {{#if typescript}}
12
+ - `npm run build` — build once (`stars build`).
13
+ {{/if}}
14
+ - `npm start` — run the built bot.
15
+ - `npx stars doctor` — check the runtime, the credentials and the generated files. Run it first when something is off.
16
+ - `npx stars info --json` — the resolved configuration.
17
+ - `npx stars commands diff` — what a deploy would change on Discord. `npx stars commands deploy --yes` deploys.
18
+ {{#if i18n}}
19
+ - `npm run generate:i18n` — regenerate the i18next types after editing `src/locales/`.
20
+ {{/if}}
21
+ {{#if testing}}
22
+ - `npm test` — run the tests (vitest).
23
+ {{/if}}
24
+
25
+ ## Layout
26
+
27
+ - `src/main.*` — the entry: creates the client, loads the pieces, starts listening.
28
+ - `src/commands/` — one file per application command. A command registers its own builder
29
+ (`@RegisterCommand(...)` or `registerApplicationCommands`) and answers in `chatInputRun`.
30
+ - `src/interaction-handlers/` — buttons, select menus and modals, matched by custom id.
31
+ - `src/listeners/` — client event listeners.
32
+ - `src/lib/` — shared code{{#if autoImports}}, auto-imported by `stars` (see `.stars/imports.d.ts`){{else}}; import what you use, nothing is auto-imported{{/if}}.
33
+ {{#if i18n}}
34
+ - `src/locales/<locale>/` — translations. Keys are typed: never build a key from a variable.
35
+ {{/if}}
36
+ - `stars.config.*` — the only build and dev configuration. There is no separate bundler config.
37
+ - `.stars/` — generated by `stars prepare`. Never edit it, and do not commit it.
38
+
39
+ {{#if vite}}
40
+ The project is bundled into one file, so there is no `commands` directory at runtime: import every new piece in
41
+ `src/main.*` and load it with `container.stores.loadPiece`.
42
+
43
+ {{/if}}
44
+ ## Rules
45
+
46
+ - Secrets live in `.env` (`DISCORD_TOKEN`, `DISCORD_PUBLIC_KEY`, `DISCORD_CLIENT_ID`). Never print or commit them.
47
+ - Call Discord through `container.rest`, not `fetch`: it handles rate limits and authentication.
48
+ - An interaction must be answered within 3 seconds: `reply()`, or `defer()` first and `edit()` later. `edit()` before
49
+ either one fails.
50
+ {{#if tunnel}}
51
+ - `stars dev` opens a public cloudflared tunnel to the bot (`dev.tunnel` in `stars.config`). Its URL changes on every
52
+ run; it is the application's interactions endpoint only while the session lasts.
53
+ {{/if}}
54
+ - Changing a command's builder (name, description, options) changes what Discord has to be told. `stars dev` asks
55
+ before it redeploys; outside it, use `stars commands deploy`.
56
+ - Do not start a long-running `npm run dev` to check a change: build{{#if testing}} and run the tests{{/if}} instead.
57
+
58
+ ## Reference
59
+
60
+ - Framework and CLI documentation: <https://github.com/wolfstar-project/stars-components>
61
+ - `llms.txt` in this directory lists the documentation by topic.
@@ -2,6 +2,8 @@
2
2
 
3
3
  A Discord bot built with [`@wolfstar/http-framework`](https://www.npmjs.com/package/@wolfstar/http-framework), an HTTP-interactions framework for Discord bots.
4
4
 
5
+ `AGENTS.md` and `llms.txt` describe the project to AI coding agents: its commands, its layout and the rules that are easy to get wrong.
6
+
5
7
  ## Setup
6
8
 
7
9
  1. Install dependencies (skip this if the generator already installed them for you).
@@ -40,14 +42,22 @@ The project is bundled with [Vite](https://vite.dev) into a single file, so the
40
42
 
41
43
  The `dev` and `build` scripts are powered by the [`stars`](https://www.npmjs.com/package/@wolfstar/cli) CLI and read the `stars.config` file at the project root.
42
44
 
43
- - `npm run dev` — build, run and restart the bot on changes, with an interactive terminal UI (`npm run dev -- --no-tui` for plain logs).
45
+ - `npm run dev` — build, run and restart the bot on changes, with an interactive terminal dashboard: status, log channels and levels you can filter, and a prompt before changed commands are redeployed (`npm run dev -- --no-tui` for plain logs).
44
46
  - `npm run build` — {{#if nitro}}build the deployable `.output/` directory{{else}}compile the project{{/if}} (TypeScript projects only).
45
47
  - `npm start` — run the bot.
46
48
  - `npx stars info` — print the resolved configuration.
49
+ - `npx stars doctor` — check the runtime, the credentials, the interactions endpoint and the generated files.
50
+ - `npx stars commands diff` / `npx stars commands deploy` — compare the commands the bot defines with the ones Discord has, and deploy them.
47
51
  {{#if testing}}
48
52
  - `npm test` — run the test suite with vitest.
49
53
  {{/if}}
50
54
 
55
+ {{#if tunnel}}
56
+ ## Tunnel
57
+
58
+ `stars dev` opens a [cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) quick tunnel (`dev.tunnel` in `stars.config`), so Discord can reach the bot on your machine. Its URL changes on every run: paste it as the **Interactions Endpoint URL** of the application, or set `dev: { tunnel: { updateEndpoint: true } }` to let `stars dev` do it. Press `t` in the dev UI to close or reopen it.
59
+
60
+ {{/if}}
51
61
  ## Commands
52
62
 
53
63
  - `/ping` — replies with `Pong!`.
@@ -0,0 +1,39 @@
1
+ # {{name}}
2
+
3
+ > A Discord bot built with @wolfstar/http-framework: HTTP interactions{{#if gateway}} and gateway events{{/if}}, built and run with the `stars` CLI.
4
+
5
+ Start with `AGENTS.md` for this project's commands, layout and rules. The links below are the upstream documentation.
6
+
7
+ ## Framework
8
+
9
+ - [@wolfstar/http-framework](https://github.com/wolfstar-project/stars-components/tree/main/packages/http-framework#readme): the client, commands, interaction handlers, listeners, plugins
10
+ - [@wolfstar/decorators](https://github.com/wolfstar-project/stars-components/tree/main/packages/decorators#readme): `ApplyOptions`, permission and context preconditions
11
+ - [@wolfstar/env-utilities](https://github.com/wolfstar-project/stars-components/tree/main/packages/env-utilities#readme): typed environment variables
12
+
13
+ ## CLI
14
+
15
+ - [@wolfstar/cli](https://github.com/wolfstar-project/stars-components/tree/main/packages/cli#readme): `stars dev`, `build`, `prepare`, `info`, `doctor`, `commands`, `codegen`, `completions`, and every `stars.config` option
16
+ {{#if i18n}}
17
+
18
+ ## Internationalisation
19
+
20
+ - [@wolfstar/plugin-i18next](https://www.npmjs.com/package/@wolfstar/plugin-i18next): the i18next plugin
21
+ - [@wolfstar/i18next-type-generator](https://github.com/wolfstar-project/stars-components/tree/main/packages/i18next-type-generator#readme): typed translation keys
22
+ {{/if}}
23
+ {{#if gateway}}
24
+
25
+ ## Gateway
26
+
27
+ - [@wolfstar/plugin-gateway](https://www.npmjs.com/package/@wolfstar/plugin-gateway): gateway events next to HTTP interactions
28
+ {{#if cache}}
29
+ - [@wolfstar/plugin-cache](https://www.npmjs.com/package/@wolfstar/plugin-cache): the gateway entity cache
30
+ {{/if}}
31
+ {{#if sharder}}
32
+ - [@wolfstar/plugin-sharder](https://www.npmjs.com/package/@wolfstar/plugin-sharder): shards across cluster workers
33
+ {{/if}}
34
+ {{/if}}
35
+
36
+ ## Discord
37
+
38
+ - [Receiving and responding to interactions](https://discord.com/developers/docs/interactions/receiving-and-responding)
39
+ - [Application commands](https://discord.com/developers/docs/interactions/application-commands)