@diegoaltoworks/chatter 0.25.0 → 0.26.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/README.md CHANGED
@@ -25,6 +25,7 @@
25
25
  - 📱 **Mobile Optimized**: Responsive design with iOS/Android-specific fixes
26
26
  - ⚡ **High Performance**: Built on Hono with streaming support
27
27
  - 🛡️ **Security First**: Rate limiting, CORS, referrer checking, and input guardrails
28
+ - 💸 **Usage Metering**: Per-caller and global daily caps for paid features, multi-instance safe, via `@diegoaltoworks/chatter/usage` ([guide](./docs/usage.md))
28
29
  - 📦 **Framework Agnostic**: Works with any JavaScript framework or vanilla JS
29
30
  - 🎯 **TypeScript**: Fully typed for excellent developer experience
30
31
 
@@ -71,8 +72,9 @@ Complete guides for setup, deployment, and integration:
71
72
  - **[Requirements](./docs/requirements.md)** - OpenAI, Turso, Clerk setup and pricing
72
73
  - **[Server Setup](./docs/server.md)** - Configuration, knowledge base, prompts, API keys
73
74
  - **[Client Integration](./docs/client.md)** - Widgets, React components, theming
75
+ - **[Usage Metering](./docs/usage.md)** - Daily spend caps for paid features
74
76
  - **[Deployment](./docs/deployment.md)** - Google Cloud Run, Fly.io, Railway, VPS
75
- - **[Testing](./docs/testing.md)** - Comprehensive testing guide (254 tests, 98% coverage)
77
+ - **[Testing](./docs/testing.md)** - Comprehensive testing guide
76
78
  - **[Code Quality](./docs/CODE_QUALITY.md)** - Security assessment and architecture review
77
79
  - **[FAQs](./docs/faqs.md)** - Troubleshooting and common questions
78
80
 
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Usage metering — daily spend guards for anything that costs money per call.
3
+ *
4
+ * Published as the `@diegoaltoworks/chatter/usage` subpath so the core install
5
+ * stays untouched by it. The limiter itself is pure; the Turso store is the
6
+ * shipped persistence binding and needs `@libsql/client`, which chatter
7
+ * already expects for retrieval.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ export type { DailyLimitCheck, DailyLimiter, DailyLimiterDeps, DailyLimitReason, DailyLimitsConfig, DailyLimitsStore, } from "./limits";
12
+ export { createDailyLimiter, GLOBAL_LIMIT_KEY, pickDailyLimit, utcDayKey } from "./limits";
13
+ export { createTursoUsageStore } from "./tursoStore";
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/usage/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,YAAY,EACV,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC3F,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/usage/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ GLOBAL_LIMIT_KEY: () => GLOBAL_LIMIT_KEY,
24
+ createDailyLimiter: () => createDailyLimiter,
25
+ createTursoUsageStore: () => createTursoUsageStore,
26
+ pickDailyLimit: () => pickDailyLimit,
27
+ utcDayKey: () => utcDayKey
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+
31
+ // src/usage/limits.ts
32
+ var GLOBAL_LIMIT_KEY = "global";
33
+ function utcDayKey(now) {
34
+ return new Date(now).toISOString().slice(0, 10);
35
+ }
36
+ function createDailyLimiter(config, deps) {
37
+ const now = deps.now ?? Date.now;
38
+ return {
39
+ async checkAndReserve(key) {
40
+ const day = utcDayKey(now());
41
+ const perKeyCount = await deps.store.incrementAndGet("key", key, day);
42
+ if (perKeyCount > config.perKeyDailyLimit) {
43
+ return { allowed: false, reason: "per-key" };
44
+ }
45
+ const globalCount = await deps.store.incrementAndGet("global", GLOBAL_LIMIT_KEY, day);
46
+ if (globalCount > config.globalDailyLimit) {
47
+ return { allowed: false, reason: "global" };
48
+ }
49
+ return { allowed: true, reason: null };
50
+ }
51
+ };
52
+ }
53
+ function pickDailyLimit(value, fallback) {
54
+ const trimmed = value?.trim();
55
+ if (trimmed === void 0 || trimmed === "") return fallback;
56
+ const parsed = Number(trimmed);
57
+ return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
58
+ }
59
+
60
+ // src/usage/tursoStore.ts
61
+ var schemaReady = /* @__PURE__ */ new WeakMap();
62
+ function ensureUsageSchema(client, tableName) {
63
+ let perTable = schemaReady.get(client);
64
+ if (!perTable) {
65
+ perTable = /* @__PURE__ */ new Map();
66
+ schemaReady.set(client, perTable);
67
+ }
68
+ const tables = perTable;
69
+ let ready = tables.get(tableName);
70
+ if (!ready) {
71
+ ready = client.execute(
72
+ `CREATE TABLE IF NOT EXISTS ${tableName} (
73
+ scope TEXT NOT NULL,
74
+ key TEXT NOT NULL,
75
+ day TEXT NOT NULL,
76
+ count INTEGER NOT NULL DEFAULT 0,
77
+ PRIMARY KEY (scope, key, day)
78
+ )`
79
+ ).catch((error) => {
80
+ tables.delete(tableName);
81
+ throw error;
82
+ });
83
+ tables.set(tableName, ready);
84
+ }
85
+ return ready;
86
+ }
87
+ var VALID_TABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
88
+ function createTursoUsageStore(client, tableName) {
89
+ if (!VALID_TABLE_NAME.test(tableName)) {
90
+ throw new Error(`Invalid usage store table name: ${tableName}`);
91
+ }
92
+ return {
93
+ async incrementAndGet(scope, key, day) {
94
+ await ensureUsageSchema(client, tableName);
95
+ const result = await client.execute({
96
+ sql: `INSERT INTO ${tableName} (scope, key, day, count) VALUES (?, ?, ?, 1)
97
+ ON CONFLICT(scope, key, day) DO UPDATE SET count = count + 1
98
+ RETURNING count`,
99
+ args: [scope, key, day]
100
+ });
101
+ const row = result.rows[0];
102
+ if (!row) {
103
+ throw new Error(`${tableName} returned no row from upsert`);
104
+ }
105
+ return Number(row.count);
106
+ }
107
+ };
108
+ }
109
+ // Annotate the CommonJS export names for ESM import in node:
110
+ 0 && (module.exports = {
111
+ GLOBAL_LIMIT_KEY,
112
+ createDailyLimiter,
113
+ createTursoUsageStore,
114
+ pickDailyLimit,
115
+ utcDayKey
116
+ });
@@ -0,0 +1,85 @@
1
+ // src/usage/limits.ts
2
+ var GLOBAL_LIMIT_KEY = "global";
3
+ function utcDayKey(now) {
4
+ return new Date(now).toISOString().slice(0, 10);
5
+ }
6
+ function createDailyLimiter(config, deps) {
7
+ const now = deps.now ?? Date.now;
8
+ return {
9
+ async checkAndReserve(key) {
10
+ const day = utcDayKey(now());
11
+ const perKeyCount = await deps.store.incrementAndGet("key", key, day);
12
+ if (perKeyCount > config.perKeyDailyLimit) {
13
+ return { allowed: false, reason: "per-key" };
14
+ }
15
+ const globalCount = await deps.store.incrementAndGet("global", GLOBAL_LIMIT_KEY, day);
16
+ if (globalCount > config.globalDailyLimit) {
17
+ return { allowed: false, reason: "global" };
18
+ }
19
+ return { allowed: true, reason: null };
20
+ }
21
+ };
22
+ }
23
+ function pickDailyLimit(value, fallback) {
24
+ const trimmed = value?.trim();
25
+ if (trimmed === void 0 || trimmed === "") return fallback;
26
+ const parsed = Number(trimmed);
27
+ return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
28
+ }
29
+
30
+ // src/usage/tursoStore.ts
31
+ var schemaReady = /* @__PURE__ */ new WeakMap();
32
+ function ensureUsageSchema(client, tableName) {
33
+ let perTable = schemaReady.get(client);
34
+ if (!perTable) {
35
+ perTable = /* @__PURE__ */ new Map();
36
+ schemaReady.set(client, perTable);
37
+ }
38
+ const tables = perTable;
39
+ let ready = tables.get(tableName);
40
+ if (!ready) {
41
+ ready = client.execute(
42
+ `CREATE TABLE IF NOT EXISTS ${tableName} (
43
+ scope TEXT NOT NULL,
44
+ key TEXT NOT NULL,
45
+ day TEXT NOT NULL,
46
+ count INTEGER NOT NULL DEFAULT 0,
47
+ PRIMARY KEY (scope, key, day)
48
+ )`
49
+ ).catch((error) => {
50
+ tables.delete(tableName);
51
+ throw error;
52
+ });
53
+ tables.set(tableName, ready);
54
+ }
55
+ return ready;
56
+ }
57
+ var VALID_TABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
58
+ function createTursoUsageStore(client, tableName) {
59
+ if (!VALID_TABLE_NAME.test(tableName)) {
60
+ throw new Error(`Invalid usage store table name: ${tableName}`);
61
+ }
62
+ return {
63
+ async incrementAndGet(scope, key, day) {
64
+ await ensureUsageSchema(client, tableName);
65
+ const result = await client.execute({
66
+ sql: `INSERT INTO ${tableName} (scope, key, day, count) VALUES (?, ?, ?, 1)
67
+ ON CONFLICT(scope, key, day) DO UPDATE SET count = count + 1
68
+ RETURNING count`,
69
+ args: [scope, key, day]
70
+ });
71
+ const row = result.rows[0];
72
+ if (!row) {
73
+ throw new Error(`${tableName} returned no row from upsert`);
74
+ }
75
+ return Number(row.count);
76
+ }
77
+ };
78
+ }
79
+ export {
80
+ GLOBAL_LIMIT_KEY,
81
+ createDailyLimiter,
82
+ createTursoUsageStore,
83
+ pickDailyLimit,
84
+ utcDayKey
85
+ };
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Daily spend guards — the counting half of "this feature costs money, cap it".
3
+ *
4
+ * Framework-free and storage-free: this module knows how to decide, never how
5
+ * to persist. A host supplies a {@link DailyLimitsStore} (see ./tursoStore for
6
+ * the shipped one) and instantiates one limiter per metered resource — image
7
+ * generation, voice minutes, anything with a per-unit price — by giving each
8
+ * its own store table. Nothing here is chat-specific.
9
+ */
10
+ /** The two caps every metered resource is subject to. */
11
+ export interface DailyLimitsConfig {
12
+ /** Units one key may reserve per UTC day. */
13
+ perKeyDailyLimit: number;
14
+ /** Units all keys together may reserve per UTC day. */
15
+ globalDailyLimit: number;
16
+ }
17
+ /** The key the global counter is stored under — that scope has no caller key. */
18
+ export declare const GLOBAL_LIMIT_KEY = "global";
19
+ /** Which cap blocked the request, or null when it was allowed. */
20
+ export type DailyLimitReason = "per-key" | "global" | null;
21
+ export interface DailyLimitCheck {
22
+ allowed: boolean;
23
+ reason: DailyLimitReason;
24
+ }
25
+ /**
26
+ * Structural dependency — a host implements this against its own storage.
27
+ *
28
+ * Must increment and read back atomically. Deployments run multiple instances,
29
+ * so a read-then-write from this process would race with another instance's
30
+ * and both would see room under a cap that only had space for one.
31
+ */
32
+ export interface DailyLimitsStore {
33
+ /** Increments the counter for (scope, key, day) and returns the count after incrementing. */
34
+ incrementAndGet(scope: "key" | "global", key: string, day: string): Promise<number>;
35
+ }
36
+ /** The UTC calendar day counters roll over on, e.g. "2026-08-01". */
37
+ export declare function utcDayKey(now: number): string;
38
+ export interface DailyLimiterDeps {
39
+ store: DailyLimitsStore;
40
+ /** Clock, injectable so day-rollover tests don't depend on real time. */
41
+ now?: () => number;
42
+ }
43
+ export interface DailyLimiter {
44
+ /**
45
+ * Reserves one unit for `key`, subject to both daily caps.
46
+ *
47
+ * Call this exactly once per logical request: the increment is permanent and
48
+ * unconditional (there is no release path), so a duplicate call — or a retry
49
+ * after a transient failure — permanently burns a second unit of quota for
50
+ * one request never delivered. Blocked calls still count: a call that trips
51
+ * the per-key cap still consumes one of that key's daily units, and (by
52
+ * design — see below) a call that trips the global cap after passing the
53
+ * per-key check also consumes one of that key's units, for nothing.
54
+ *
55
+ * The per-key counter increments before the global one, so a key blocked at
56
+ * its own cap never touches the shared global budget. The mirror case is
57
+ * accepted, not fixed: once the global cap is exhausted for the day, every
58
+ * further caller still burns one of their own per-key units on the way to
59
+ * being told no. This favours guarding against the more common cost risk
60
+ * (one key hammering the endpoint) over the rarer one (the global cap
61
+ * saturating and clipping everyone's personal quota).
62
+ *
63
+ * Reserve before spending, not after: the ordering is what makes the cap a
64
+ * cap. Callers with a cache should check it first, so a cache hit costs
65
+ * neither money nor quota.
66
+ */
67
+ checkAndReserve(key: string): Promise<DailyLimitCheck>;
68
+ }
69
+ export declare function createDailyLimiter(config: DailyLimitsConfig, deps: DailyLimiterDeps): DailyLimiter;
70
+ /**
71
+ * Reads a cap out of loosely-typed configuration (an env var, a JSON field).
72
+ *
73
+ * A configured non-negative integer wins over the default; anything else —
74
+ * unset, blank, negative, fractional, unparseable — falls back. A malformed
75
+ * cap must not silently become `NaN`, which compares false against every
76
+ * count and would disable the guard entirely.
77
+ *
78
+ * Blank is trimmed before parsing rather than compared raw: `Number(" ")` is
79
+ * `0`, so a config value that is whitespace would otherwise be read as a cap
80
+ * of zero and switch the feature off entirely instead of falling back.
81
+ *
82
+ * This is the supported way to build a {@link DailyLimitsConfig}. The limiter
83
+ * itself does not validate its caps, so a `NaN` reaching it from somewhere
84
+ * else fails open.
85
+ */
86
+ export declare function pickDailyLimit(value: string | undefined, fallback: number): number;
87
+ //# sourceMappingURL=limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/usage/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IAChC,6CAA6C;IAC7C,gBAAgB,EAAE,MAAM,CAAC;IACzB,uDAAuD;IACvD,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,iFAAiF;AACjF,eAAO,MAAM,gBAAgB,WAAW,CAAC;AAEzC,kEAAkE;AAClE,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC;AAE3D,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6FAA6F;IAC7F,eAAe,CAAC,KAAK,EAAE,KAAK,GAAG,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACrF;AAED,qEAAqE;AACrE,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,gBAAgB,CAAC;IACxB,yEAAyE;IACzE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CACxD;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,iBAAiB,EACzB,IAAI,EAAE,gBAAgB,GACrB,YAAY,CAoBd;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKlF"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Daily limiter tests.
3
+ *
4
+ * Two properties matter more than the arithmetic and are asserted directly:
5
+ * the *order* of the two increments (a key blocked at its own cap must not
6
+ * burn shared budget), and the reserve-once semantics (every call counts,
7
+ * including blocked ones — there is no release path to lean on).
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=limits.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.test.d.ts","sourceRoot":"","sources":["../../src/usage/limits.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Turso/libsql-backed {@link DailyLimitsStore}.
3
+ *
4
+ * Schema and upsert only — error typing and wrapping stay with the caller,
5
+ * because each metered feature wants its own error contract and this layer
6
+ * should not invent one for them.
7
+ *
8
+ * `@libsql/client` is imported for its type alone, so this module adds no
9
+ * runtime dependency to anyone importing `./usage` for the pure limiter.
10
+ */
11
+ import type { Client } from "@libsql/client";
12
+ import type { DailyLimitsStore } from "./limits";
13
+ /**
14
+ * Creates a store that counts into `tableName` on `client`.
15
+ *
16
+ * The upsert is a single atomic statement returning the post-increment count,
17
+ * so concurrent instances each get a distinct number and the cap holds across
18
+ * a multi-instance deployment. Table creation is idempotent and happens once
19
+ * per (client, table).
20
+ *
21
+ * Give each metered resource its own table — sharing one would make separate
22
+ * features compete for a single global counter.
23
+ */
24
+ export declare function createTursoUsageStore(client: Client, tableName: string): DailyLimitsStore;
25
+ //# sourceMappingURL=tursoStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tursoStore.d.ts","sourceRoot":"","sources":["../../src/usage/tursoStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAyCjD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAuBzF"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Turso usage store tests.
3
+ *
4
+ * Run against a real in-memory libsql client rather than a fake, because the
5
+ * properties worth proving here are SQL-level: the upsert is atomic, it hands
6
+ * every concurrent caller a distinct number, and table creation is idempotent.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=tursoStore.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tursoStore.test.d.ts","sourceRoot":"","sources":["../../src/usage/tursoStore.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegoaltoworks/chatter",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "Embeddable AI chatbot framework with RAG, authentication, and customizable widgets",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -28,6 +28,11 @@
28
28
  "types": "./dist/mcp-server.d.ts",
29
29
  "import": "./dist/mcp-server.mjs",
30
30
  "require": "./dist/mcp-server.js"
31
+ },
32
+ "./usage": {
33
+ "types": "./dist/usage/index.d.ts",
34
+ "import": "./dist/usage/index.mjs",
35
+ "require": "./dist/usage/index.js"
31
36
  }
32
37
  },
33
38
  "files": [
@@ -37,12 +42,13 @@
37
42
  ],
38
43
  "scripts": {
39
44
  "dev": "bun --watch src/index.ts",
40
- "build": "bun run build:clean && bun run build:types && bun run build:esm && bun run build:cjs && bun run build:mcp && bun run build:bin && bun run build:client && bun run build:copy-client",
45
+ "build": "bun run build:clean && bun run build:types && bun run build:esm && bun run build:cjs && bun run build:mcp && bun run build:usage && bun run build:bin && bun run build:client && bun run build:copy-client",
41
46
  "build:clean": "rm -rf dist",
42
47
  "build:types": "tsc --emitDeclarationOnly --outDir dist",
43
48
  "build:esm": "esbuild src/index.ts --bundle --format=esm --outfile=dist/index.mjs --external:hono --external:openai --external:@libsql/client --external:jose --platform=node --target=node24",
44
49
  "build:cjs": "esbuild src/index.ts --bundle --format=cjs --outfile=dist/index.js --external:hono --external:openai --external:@libsql/client --external:jose --platform=node --target=node24",
45
50
  "build:mcp": "esbuild src/mcp-server.ts --bundle --format=esm --outfile=dist/mcp-server.mjs --external:@modelcontextprotocol/sdk --external:openai --external:@libsql/client --external:zod --platform=node --target=node24 && esbuild src/mcp-server.ts --bundle --format=cjs --outfile=dist/mcp-server.js --external:@modelcontextprotocol/sdk --external:openai --external:@libsql/client --external:zod --platform=node --target=node24",
51
+ "build:usage": "esbuild src/usage/index.ts --bundle --format=esm --outfile=dist/usage/index.mjs --external:@libsql/client --platform=node --target=node24 && esbuild src/usage/index.ts --bundle --format=cjs --outfile=dist/usage/index.js --external:@libsql/client --platform=node --target=node24",
46
52
  "build:bin": "esbuild bin/create-apikey.ts --bundle --format=cjs --outfile=dist/bin/create-apikey.js --external:jose --platform=node --target=node18 --banner:js='#!/usr/bin/env node'",
47
53
  "build:client": "cd src/client && bun run build",
48
54
  "build:copy-client": "mkdir -p dist/static && cp src/client/dist/chatter.js src/client/dist/chatter.css dist/static/",