chapterjs 0.0.0 → 0.1.2

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.
Files changed (53) hide show
  1. package/dist/cli/cli.d.ts +2 -0
  2. package/dist/cli/cli.js +33 -0
  3. package/dist/cli/dev.d.ts +1 -0
  4. package/dist/cli/dev.js +206 -0
  5. package/dist/components.d.ts +97 -0
  6. package/dist/components.js +260 -0
  7. package/dist/context.d.ts +72 -0
  8. package/dist/context.js +273 -0
  9. package/dist/discord/client.d.ts +35 -0
  10. package/dist/discord/client.js +66 -0
  11. package/dist/discord/constants.d.ts +49 -0
  12. package/dist/discord/constants.js +61 -0
  13. package/dist/discord/events.d.ts +129 -0
  14. package/dist/discord/events.js +102 -0
  15. package/dist/discord/gateway.d.ts +61 -0
  16. package/dist/discord/gateway.js +320 -0
  17. package/dist/discord/payloads.d.ts +275 -0
  18. package/dist/discord/payloads.js +3 -0
  19. package/dist/discord/presence.d.ts +42 -0
  20. package/dist/discord/presence.js +51 -0
  21. package/dist/discord/rest.d.ts +49 -0
  22. package/dist/discord/rest.js +271 -0
  23. package/dist/discord/types.d.ts +334 -0
  24. package/dist/discord/types.js +4 -0
  25. package/dist/events.d.ts +94 -0
  26. package/dist/events.js +255 -0
  27. package/dist/index.d.ts +18 -0
  28. package/dist/index.js +12 -0
  29. package/dist/presence.d.ts +92 -0
  30. package/dist/presence.js +250 -0
  31. package/dist/structures/Channel.d.ts +110 -0
  32. package/dist/structures/Channel.js +171 -0
  33. package/dist/structures/Guild.d.ts +70 -0
  34. package/dist/structures/Guild.js +105 -0
  35. package/dist/structures/Interaction.d.ts +113 -0
  36. package/dist/structures/Interaction.js +274 -0
  37. package/dist/structures/Member.d.ts +70 -0
  38. package/dist/structures/Member.js +124 -0
  39. package/dist/structures/Message.d.ts +87 -0
  40. package/dist/structures/Message.js +182 -0
  41. package/dist/structures/Reaction.d.ts +38 -0
  42. package/dist/structures/Reaction.js +64 -0
  43. package/dist/structures/Role.d.ts +43 -0
  44. package/dist/structures/Role.js +77 -0
  45. package/dist/structures/User.d.ts +41 -0
  46. package/dist/structures/User.js +79 -0
  47. package/dist/structures/utils.d.ts +72 -0
  48. package/dist/structures/utils.js +59 -0
  49. package/dist/utils/errors.d.ts +5 -0
  50. package/dist/utils/errors.js +28 -0
  51. package/dist/utils/modules.d.ts +39 -0
  52. package/dist/utils/modules.js +156 -0
  53. package/package.json +30 -5
@@ -0,0 +1,79 @@
1
+ import { Channel } from './Channel.js';
2
+ import { cdnUrl, snowflakeDate, } from './utils.js';
3
+ /**
4
+ * A Discord user. `raw` holds every field sent by Discord.
5
+ * https://docs.discord.com/developers/resources/user#user-object
6
+ */
7
+ export class User {
8
+ client;
9
+ raw;
10
+ id;
11
+ username;
12
+ /** Display name set by the user, if any. */
13
+ globalName;
14
+ discriminator;
15
+ avatar;
16
+ banner;
17
+ bot;
18
+ system;
19
+ constructor(client, raw) {
20
+ this.client = client;
21
+ this.raw = raw;
22
+ this.id = raw.id;
23
+ this.username = raw.username;
24
+ this.globalName = raw.global_name ?? null;
25
+ this.discriminator = raw.discriminator;
26
+ this.avatar = raw.avatar ?? null;
27
+ this.banner = raw.banner ?? null;
28
+ this.bot = raw.bot ?? false;
29
+ this.system = raw.system ?? false;
30
+ }
31
+ /** Name shown in Discord: the display name, or the username. */
32
+ get displayName() {
33
+ return this.globalName ?? this.username;
34
+ }
35
+ get createdAt() {
36
+ return snowflakeDate(this.id);
37
+ }
38
+ /** Mention, e.g. `${user}` in a message. */
39
+ toString() {
40
+ return `<@${this.id}>`;
41
+ }
42
+ /** Uploaded avatar, or null when the user has none. */
43
+ avatarURL(options) {
44
+ return this.avatar
45
+ ? cdnUrl(`avatars/${this.id}`, this.avatar, options)
46
+ : null;
47
+ }
48
+ // https://docs.discord.com/developers/reference#image-formatting-cdn-endpoints
49
+ /** Default avatar Discord shows when the user has none. */
50
+ get defaultAvatarURL() {
51
+ const index = this.discriminator === '0' || !this.discriminator
52
+ ? Number((BigInt(this.id) >> 22n) % 6n)
53
+ : Number(this.discriminator) % 5;
54
+ return `https://cdn.discordapp.com/embed/avatars/${index}.png`;
55
+ }
56
+ /** Avatar shown in Discord: the uploaded one, or the default one. */
57
+ displayAvatarURL(options) {
58
+ return this.avatarURL(options) ?? this.defaultAvatarURL;
59
+ }
60
+ bannerURL(options) {
61
+ return this.banner
62
+ ? cdnUrl(`banners/${this.id}`, this.banner, options)
63
+ : null;
64
+ }
65
+ /**
66
+ * Sends a direct message, opening the DM channel (or reusing it).
67
+ * https://docs.discord.com/developers/resources/user#create-dm
68
+ */
69
+ async send(options) {
70
+ const channel = await this.client.rest.post('/users/@me/channels', {
71
+ body: { recipient_id: this.id },
72
+ });
73
+ return new Channel(this.client, channel).send(options);
74
+ }
75
+ /** Latest data from Discord. */
76
+ async fetch() {
77
+ return new User(this.client, await this.client.rest.get(`/users/${this.id}`));
78
+ }
79
+ }
@@ -0,0 +1,72 @@
1
+ import { type ComponentInput } from '../components.js';
2
+ import type { FileUpload } from '../discord/rest.js';
3
+ import type { Snowflake } from '../discord/types.js';
4
+ /** Creation date encoded in a snowflake. */
5
+ export declare function snowflakeDate(id: Snowflake): Date;
6
+ export declare const toDate: (timestamp: string | null | undefined) => Date | null;
7
+ export interface ImageOptions {
8
+ /** Power of 2 between 16 and 4096. */
9
+ size?: 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096;
10
+ extension?: 'png' | 'jpg' | 'webp' | 'gif';
11
+ }
12
+ /**
13
+ * CDN URL of an image. Animated hashes (`a_`) are served as animated WebP,
14
+ * which works whatever the uploaded format.
15
+ * https://docs.discord.com/developers/reference#image-formatting
16
+ */
17
+ export declare function cdnUrl(path: string, hash: string, options?: ImageOptions): string;
18
+ export interface AllowedMentions {
19
+ parse?: ('roles' | 'users' | 'everyone')[];
20
+ roles?: Snowflake[];
21
+ users?: Snowflake[];
22
+ /** For replies, whether to mention the author of the replied message. */
23
+ repliedUser?: boolean;
24
+ }
25
+ /** Message content, or every field Discord accepts when sending a message. */
26
+ export type MessageOptions = string | {
27
+ /** Up to 2000 characters. */
28
+ content?: string;
29
+ /** Up to 10 embeds, https://docs.discord.com/developers/resources/message#embed-object */
30
+ embeds?: object[];
31
+ /**
32
+ * Components made with `defineButton`, or raw objects
33
+ * (https://docs.discord.com/developers/components/reference). Buttons
34
+ * are put in rows of 5 automatically; an array is one row.
35
+ */
36
+ components?: ComponentInput[];
37
+ allowedMentions?: AllowedMentions;
38
+ files?: FileUpload[];
39
+ tts?: boolean;
40
+ /** https://docs.discord.com/developers/resources/message#message-object-message-flags */
41
+ flags?: number;
42
+ stickerIds?: Snowflake[];
43
+ /** https://docs.discord.com/developers/resources/poll#poll-create-request-object */
44
+ poll?: object;
45
+ };
46
+ /** Options of `message.reply`. */
47
+ export type ReplyOptions = MessageOptions & {
48
+ /** Whether to ping the author of the replied message (`allowed_mentions.replied_user`). */
49
+ mention?: boolean;
50
+ /** Fail when the replied message no longer exists, instead of sending a normal message. Defaults to true. */
51
+ failIfNotExists?: boolean;
52
+ };
53
+ /** Converts message options to the JSON body Discord expects, and the files to upload. */
54
+ export declare function toMessageBody(options: MessageOptions | ReplyOptions): {
55
+ body: Record<string, unknown>;
56
+ files?: FileUpload[];
57
+ };
58
+ export declare function toAllowedMentions({ repliedUser, ...rest }: AllowedMentions): {
59
+ parse?: ('roles' | 'users' | 'everyone')[];
60
+ roles?: Snowflake[];
61
+ users?: Snowflake[];
62
+ replied_user?: boolean | undefined;
63
+ };
64
+ /**
65
+ * Emoji as expected in reaction routes: the unicode character, or `name:id`
66
+ * for a custom emoji (`<:name:id>` is accepted too), URL encoded.
67
+ * https://docs.discord.com/developers/resources/message#create-reaction
68
+ */
69
+ export declare function encodeEmoji(emoji: string | {
70
+ id: Snowflake | null;
71
+ name: string | null;
72
+ }): string;
@@ -0,0 +1,59 @@
1
+ import { serializeComponents } from '../components.js';
2
+ /** https://docs.discord.com/developers/reference#snowflakes */
3
+ const DISCORD_EPOCH = 1420070400000n;
4
+ /** Creation date encoded in a snowflake. */
5
+ export function snowflakeDate(id) {
6
+ return new Date(Number((BigInt(id) >> 22n) + DISCORD_EPOCH));
7
+ }
8
+ export const toDate = (timestamp) => timestamp ? new Date(timestamp) : null;
9
+ /**
10
+ * CDN URL of an image. Animated hashes (`a_`) are served as animated WebP,
11
+ * which works whatever the uploaded format.
12
+ * https://docs.discord.com/developers/reference#image-formatting
13
+ */
14
+ export function cdnUrl(path, hash, options = {}) {
15
+ const animated = hash.startsWith('a_');
16
+ const url = new URL(`https://cdn.discordapp.com/${path}/${hash}.${options.extension ?? (animated ? 'webp' : 'png')}`);
17
+ if (options.size)
18
+ url.searchParams.set('size', String(options.size));
19
+ if (animated && !options.extension)
20
+ url.searchParams.set('animated', 'true');
21
+ return url.href;
22
+ }
23
+ /** Converts message options to the JSON body Discord expects, and the files to upload. */
24
+ export function toMessageBody(options) {
25
+ if (typeof options === 'string')
26
+ return { body: { content: options } };
27
+ const { allowedMentions, files, stickerIds, components, ...rest } = options;
28
+ delete rest.mention;
29
+ delete rest.failIfNotExists;
30
+ return {
31
+ body: {
32
+ ...rest,
33
+ ...(stickerIds && { sticker_ids: stickerIds }),
34
+ ...(components && { components: serializeComponents(components) }),
35
+ ...(allowedMentions && {
36
+ allowed_mentions: toAllowedMentions(allowedMentions),
37
+ }),
38
+ },
39
+ files,
40
+ };
41
+ }
42
+ export function toAllowedMentions({ repliedUser, ...rest }) {
43
+ return {
44
+ ...rest,
45
+ ...(repliedUser !== undefined && { replied_user: repliedUser }),
46
+ };
47
+ }
48
+ /**
49
+ * Emoji as expected in reaction routes: the unicode character, or `name:id`
50
+ * for a custom emoji (`<:name:id>` is accepted too), URL encoded.
51
+ * https://docs.discord.com/developers/resources/message#create-reaction
52
+ */
53
+ export function encodeEmoji(emoji) {
54
+ if (typeof emoji !== 'string') {
55
+ emoji = emoji.id ? `${emoji.name}:${emoji.id}` : (emoji.name ?? '');
56
+ }
57
+ const custom = /^<a?:(\w+):(\d+)>$/.exec(emoji);
58
+ return encodeURIComponent(custom ? `${custom[1]}:${custom[2]}` : emoji);
59
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Formats an error thrown by user code: keeps the stack frames of the user's
3
+ * files only, relative to the project, without the hot reload query string.
4
+ */
5
+ export declare function formatUserError(error: unknown, cwd?: string): string;
@@ -0,0 +1,28 @@
1
+ import { fileURLToPath, pathToFileURL } from 'node:url';
2
+ /** Root of the framework (`dist/`), whose frames are noise in user errors. */
3
+ const FRAMEWORK_DIR = fileURLToPath(new URL('..', import.meta.url));
4
+ const FRAMEWORK_URL = pathToFileURL(FRAMEWORK_DIR).href;
5
+ /**
6
+ * Formats an error thrown by user code: keeps the stack frames of the user's
7
+ * files only, relative to the project, without the hot reload query string.
8
+ */
9
+ export function formatUserError(error, cwd = process.cwd()) {
10
+ if (!(error instanceof Error))
11
+ return String(error);
12
+ if (!error.stack)
13
+ return error.message;
14
+ const projectUrl = `${pathToFileURL(cwd).href}/`;
15
+ const projectPath = `${cwd}/`;
16
+ const lines = error.stack.split('\n');
17
+ const firstFrame = lines.findIndex((line) => /^\s+at /.test(line));
18
+ if (firstFrame === -1)
19
+ return error.stack;
20
+ const frames = lines
21
+ .slice(firstFrame)
22
+ .filter((line) => !line.includes(FRAMEWORK_URL) &&
23
+ !line.includes(FRAMEWORK_DIR) &&
24
+ !line.includes('node:internal') &&
25
+ !line.includes('node_modules/chapterjs/'))
26
+ .map((line) => line.replaceAll(projectUrl, '').replaceAll(projectPath, '').replace(/\?t=\d+/g, ''));
27
+ return [...lines.slice(0, firstFrame), ...frames].join('\n');
28
+ }
@@ -0,0 +1,39 @@
1
+ import { type Dirent, type FSWatcher } from 'node:fs';
2
+ /** Returns the path of the first existing file among `names` in `dir`. */
3
+ export declare function findFile(dir: string, names: string[]): string | null;
4
+ /** Imports a user module, bypassing the ESM cache so edits are picked up. */
5
+ export declare function importFresh(path: string): Promise<any>;
6
+ /**
7
+ * Describes an error thrown while loading the user file `path`, as
8
+ * `src/events/ping.ts:4: message`, with the code snippet Node gives for syntax
9
+ * errors and a hint for common mistakes.
10
+ */
11
+ export declare function describeLoadError(error: unknown, path: string, cwd?: string): string;
12
+ /**
13
+ * Describes an error thrown by user code while the bot runs: its message, then
14
+ * only the stack lines from the project's `src/` (not Node's or the
15
+ * framework's), as relative paths.
16
+ */
17
+ export declare function describeRuntimeError(error: unknown, cwd?: string): string;
18
+ /**
19
+ * Calls `onChange` whenever one of `names` is created, edited or deleted in `dir`.
20
+ * Watches the directory since editors often save by replacing the file.
21
+ */
22
+ export declare function watchFiles(dir: string, names: string[], onChange: () => void): FSWatcher | null;
23
+ /**
24
+ * Calls `onChange` whenever a file or folder under `dir` whose path (relative
25
+ * to `dir`) matches `filter` is created, edited, renamed or deleted.
26
+ */
27
+ export declare function watchTree(dir: string, filter: (relativePath: string) => boolean, onChange: () => void): FSWatcher | null;
28
+ /**
29
+ * File of the function that called the caller of `callerFile`, from the V8
30
+ * call sites. Lets `defineButton` know which file it is defined in.
31
+ */
32
+ export declare function callerFile(depth?: number): string | null;
33
+ /**
34
+ * Script files under `dir`, recursively and sorted. Entries starting with `_`
35
+ * or `.`, declarations and tests are ignored.
36
+ */
37
+ export declare function scriptFiles(dir: string): string[];
38
+ export declare function readEntries(dir: string): Dirent[];
39
+ export declare function isScript(entry: Dirent): boolean;
@@ -0,0 +1,156 @@
1
+ import { existsSync, readdirSync, watch, } from 'node:fs';
2
+ import { dirname, extname, relative, resolve, sep } from 'node:path';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
4
+ /** Returns the path of the first existing file among `names` in `dir`. */
5
+ export function findFile(dir, names) {
6
+ const name = names.find(name => existsSync(resolve(dir, name)));
7
+ return name ? resolve(dir, name) : null;
8
+ }
9
+ /** Imports a user module, bypassing the ESM cache so edits are picked up. */
10
+ export async function importFresh(path) {
11
+ const url = pathToFileURL(path);
12
+ url.searchParams.set('t', String(Date.now()));
13
+ return import(url.href);
14
+ }
15
+ /**
16
+ * Describes an error thrown while loading the user file `path`, as
17
+ * `src/events/ping.ts:4: message`, with the code snippet Node gives for syntax
18
+ * errors and a hint for common mistakes.
19
+ */
20
+ export function describeLoadError(error, path, cwd = process.cwd()) {
21
+ const file = relative(cwd, path);
22
+ if (!(error instanceof Error))
23
+ return `${file}: ${String(error)}`;
24
+ const code = error.code;
25
+ const stack = error.stack ?? '';
26
+ // Syntax errors start with `<path>:<line>`, then the code with a ^ marker
27
+ const syntax = stack.match(/^(.+):(\d+)\n([\s\S]*?)\n\n/);
28
+ if (error instanceof SyntaxError && syntax) {
29
+ const snippet = syntax[3].replace(/^/gm, ' ');
30
+ const hint = code === 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX'
31
+ ? '\n Node runs your .ts files by removing the types, so TypeScript syntax that generates code' +
32
+ ' (enum, namespace, constructor parameter properties) is not supported: use a plain object or class instead.'
33
+ : '';
34
+ // A file URL with importFresh's query string, or a path for files it imports
35
+ const location = syntax[1].startsWith('file:')
36
+ ? fileURLToPath(syntax[1].split('?')[0])
37
+ : syntax[1];
38
+ return `${relative(cwd, location)}:${syntax[2]}: ${error.message}\n${snippet}${hint}`;
39
+ }
40
+ if (code === 'ERR_MODULE_NOT_FOUND') {
41
+ const missing = error.message.match(/^Cannot find (module|package) '(.+)' imported from (.+)$/);
42
+ if (missing) {
43
+ const [, kind, target, importer] = missing;
44
+ const from = relative(cwd, importer);
45
+ if (kind === 'package') {
46
+ return `${from}: the package "${target}" is not installed. Install it with your package manager, e.g. npm install ${target}`;
47
+ }
48
+ const extension = ['.ts', '.js'].find(ext => existsSync(target + ext));
49
+ if (extension) {
50
+ const specifier = relative(dirname(importer), target)
51
+ .split(sep)
52
+ .join('/');
53
+ return `${from}: imports of your own files need the file extension: "${specifier.startsWith('.') ? specifier : `./${specifier}`}${extension}".`;
54
+ }
55
+ return `${from}: the file ${relative(cwd, target)} imported here doesn't exist.`;
56
+ }
57
+ }
58
+ // Other errors: the first call site in the file, ignoring importFresh's query string
59
+ const url = pathToFileURL(path).href.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
60
+ const line = stack.match(new RegExp(`${url}(?:\\?[^:]*)?:(\\d+):\\d+`))?.[1];
61
+ return `${file}${line ? `:${line}` : ''}: ${error.message}`;
62
+ }
63
+ /**
64
+ * Describes an error thrown by user code while the bot runs: its message, then
65
+ * only the stack lines from the project's `src/` (not Node's or the
66
+ * framework's), as relative paths.
67
+ */
68
+ export function describeRuntimeError(error, cwd = process.cwd()) {
69
+ if (!(error instanceof Error))
70
+ return String(error);
71
+ const header = error.name === 'Error' ? error.message : `${error.name}: ${error.message}`;
72
+ const src = pathToFileURL(resolve(cwd, 'src')).href + '/';
73
+ const frames = (error.stack ?? '')
74
+ .split('\n')
75
+ .filter(line => /^\s+at /.test(line) && line.includes(src))
76
+ .map(line => line
77
+ .trim()
78
+ // importFresh's query string sits between the path and the position
79
+ .replace(/(file:\/\/[^\s?)]+)(\?[^:\s)]*)?/, (_, url) => relative(cwd, fileURLToPath(url))));
80
+ return [header, ...frames.map(frame => ` ${frame}`)].join('\n');
81
+ }
82
+ /**
83
+ * Calls `onChange` whenever one of `names` is created, edited or deleted in `dir`.
84
+ * Watches the directory since editors often save by replacing the file.
85
+ */
86
+ export function watchFiles(dir, names, onChange) {
87
+ if (!existsSync(dir))
88
+ return null;
89
+ let timeout;
90
+ return watch(dir, (_event, filename) => {
91
+ if (!filename || !names.includes(filename))
92
+ return;
93
+ // Editors emit several events per save
94
+ clearTimeout(timeout);
95
+ timeout = setTimeout(onChange, 100);
96
+ });
97
+ }
98
+ /**
99
+ * Calls `onChange` whenever a file or folder under `dir` whose path (relative
100
+ * to `dir`) matches `filter` is created, edited, renamed or deleted.
101
+ */
102
+ export function watchTree(dir, filter, onChange) {
103
+ if (!existsSync(dir))
104
+ return null;
105
+ let timeout;
106
+ return watch(dir, { recursive: true }, (_event, filename) => {
107
+ if (!filename || !filter(filename))
108
+ return;
109
+ // Editors emit several events per save, and moving a folder one per file
110
+ clearTimeout(timeout);
111
+ timeout = setTimeout(onChange, 100);
112
+ });
113
+ }
114
+ /**
115
+ * File of the function that called the caller of `callerFile`, from the V8
116
+ * call sites. Lets `defineButton` know which file it is defined in.
117
+ */
118
+ export function callerFile(depth = 2) {
119
+ const original = Error.prepareStackTrace;
120
+ try {
121
+ Error.prepareStackTrace = (_, callSites) => callSites;
122
+ const callSites = new Error().stack;
123
+ const file = callSites[depth]?.getFileName();
124
+ if (!file)
125
+ return null;
126
+ // ES modules are reported as URLs, with importFresh's query string
127
+ return file.startsWith('file:') ? fileURLToPath(file.split('?')[0]) : file;
128
+ }
129
+ finally {
130
+ Error.prepareStackTrace = original;
131
+ }
132
+ }
133
+ const EXTENSIONS = new Set(['.ts', '.mts', '.js', '.mjs']);
134
+ /**
135
+ * Script files under `dir`, recursively and sorted. Entries starting with `_`
136
+ * or `.`, declarations and tests are ignored.
137
+ */
138
+ export function scriptFiles(dir) {
139
+ return readEntries(dir).flatMap(entry => {
140
+ const path = resolve(dir, entry.name);
141
+ if (entry.isDirectory())
142
+ return scriptFiles(path);
143
+ return isScript(entry) ? [path] : [];
144
+ });
145
+ }
146
+ export function readEntries(dir) {
147
+ return readdirSync(dir, { withFileTypes: true })
148
+ .filter(entry => !entry.name.startsWith('_') && !entry.name.startsWith('.'))
149
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
150
+ }
151
+ export function isScript(entry) {
152
+ return (entry.isFile() &&
153
+ EXTENSIONS.has(extname(entry.name)) &&
154
+ !/\.d\.[mc]?ts$/.test(entry.name) &&
155
+ !/\.(test|spec)\.[mc]?[jt]s$/.test(entry.name));
156
+ }
package/package.json CHANGED
@@ -1,6 +1,31 @@
1
1
  {
2
- "name": "chapterjs",
3
- "version": "0.0.0",
4
- "license": "MIT",
5
- "repository": "github:chapterjs/chapterjs"
6
- }
2
+ "name": "chapterjs",
3
+ "version": "0.1.2",
4
+ "description": "The Discord framework",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "chapterjs": "./dist/cli/cli.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "devDependencies": {
21
+ "@types/node": "^24.0.0",
22
+ "typescript": "^7.0.2"
23
+ },
24
+ "engines": {
25
+ "node": ">=22.18"
26
+ },
27
+ "scripts": {
28
+ "build": "tsc && chmod +x dist/cli/cli.js",
29
+ "dev": "tsc --watch"
30
+ }
31
+ }