@diegoaltoworks/chatter 0.12.0 → 0.13.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.
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // src/auth/apikeys.ts
5
+ var import_node_crypto = require("node:crypto");
6
+ var import_jose = require("jose");
7
+ var ApiKeyManager = class {
8
+ constructor(secret) {
9
+ this.secret = secret;
10
+ if (!secret) {
11
+ throw new Error("API key secret is required");
12
+ }
13
+ this.secretBytes = new TextEncoder().encode(secret);
14
+ }
15
+ secretBytes;
16
+ /**
17
+ * Create a new API key
18
+ *
19
+ * @param options - API key options
20
+ * @returns JWT token string
21
+ */
22
+ async create(options = {}) {
23
+ const keyId = (0, import_node_crypto.randomUUID)();
24
+ const keyName = options.name || "api-key";
25
+ const expiresIn = options.expiresIn || "365d";
26
+ const expirationMs = this.parseExpiration(expiresIn);
27
+ const expiresAt = new Date(Date.now() + expirationMs);
28
+ const jwt = await new import_jose.SignJWT({
29
+ name: keyName,
30
+ type: "api_key",
31
+ ...options.claims
32
+ }).setProtectedHeader({ alg: "HS256" }).setSubject(keyId).setIssuedAt().setExpirationTime(expiresAt).sign(this.secretBytes);
33
+ return jwt;
34
+ }
35
+ /**
36
+ * Verify an API key
37
+ *
38
+ * @param token - JWT token to verify
39
+ * @returns Verification result with payload if valid
40
+ */
41
+ async verify(token) {
42
+ try {
43
+ const { payload } = await (0, import_jose.jwtVerify)(token, this.secretBytes);
44
+ if (payload.type !== "api_key") {
45
+ return {
46
+ valid: false,
47
+ error: "Token is not an API key"
48
+ };
49
+ }
50
+ return {
51
+ valid: true,
52
+ payload
53
+ };
54
+ } catch (error) {
55
+ return {
56
+ valid: false,
57
+ error: error instanceof Error ? error.message : "Verification failed"
58
+ };
59
+ }
60
+ }
61
+ /**
62
+ * Parse expiration string to milliseconds
63
+ *
64
+ * @param expiresIn - Time string (e.g., "1y", "30d", "12h")
65
+ * @returns Milliseconds
66
+ */
67
+ parseExpiration(expiresIn) {
68
+ const match = expiresIn.match(/^(\d+)([smhdMy])$/);
69
+ if (!match) {
70
+ throw new Error(
71
+ `Invalid expiration format: ${expiresIn}. Supported: s (seconds), m (minutes), h (hours), d (days), M (months), y (years)`
72
+ );
73
+ }
74
+ const value = Number.parseInt(match[1], 10);
75
+ const unit = match[2];
76
+ switch (unit) {
77
+ case "s":
78
+ return value * 1e3;
79
+ case "m":
80
+ return value * 60 * 1e3;
81
+ case "h":
82
+ return value * 60 * 60 * 1e3;
83
+ case "d":
84
+ return value * 24 * 60 * 60 * 1e3;
85
+ case "M":
86
+ return value * 30 * 24 * 60 * 60 * 1e3;
87
+ // Approximate 30 days
88
+ case "y":
89
+ return value * 365 * 24 * 60 * 60 * 1e3;
90
+ // Approximate 365 days
91
+ default:
92
+ throw new Error(`Unknown time unit: ${unit}`);
93
+ }
94
+ }
95
+ /**
96
+ * Get information about a token without verifying signature
97
+ * Useful for debugging
98
+ *
99
+ * @param token - JWT token
100
+ * @returns Decoded payload (unverified)
101
+ */
102
+ decode(token) {
103
+ try {
104
+ const parts = token.split(".");
105
+ if (parts.length !== 3) {
106
+ return null;
107
+ }
108
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8"));
109
+ return payload;
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+ };
115
+
116
+ // bin/create-apikey.ts
117
+ async function main() {
118
+ const args = process.argv.slice(2);
119
+ const options = {};
120
+ for (let i = 0; i < args.length; i++) {
121
+ if (args[i] === "--name" && args[i + 1]) {
122
+ options.name = args[i + 1];
123
+ i++;
124
+ } else if (args[i] === "--expires-in" && args[i + 1]) {
125
+ options.expiresIn = args[i + 1];
126
+ i++;
127
+ } else if (args[i] === "--help" || args[i] === "-h") {
128
+ console.log(`
129
+ Chatter API Key Generator
130
+
131
+ Usage:
132
+ npx chatter create-apikey [options]
133
+
134
+ Options:
135
+ --name <name> Name/label for the API key (default: "api-key")
136
+ --expires-in <time> Expiration time (default: "365d")
137
+ Formats: s=seconds, m=minutes, h=hours, d=days, M=months, y=years
138
+ Examples: 30d, 12h, 1y, 6M
139
+
140
+ Environment:
141
+ CHATTER_SECRET Required. Secret key for signing JWT tokens
142
+
143
+ Examples:
144
+ npx chatter create-apikey --name "mobile-app" --expires-in 365d
145
+ npx chatter create-apikey --name "test-key" --expires-in 1h
146
+ `);
147
+ process.exit(0);
148
+ }
149
+ }
150
+ const secret = process.env.CHATTER_SECRET;
151
+ if (!secret) {
152
+ console.error("\u274C Error: CHATTER_SECRET environment variable is not set");
153
+ console.error("\n Please set it before running this command:");
154
+ console.error(" export CHATTER_SECRET=your-secret-key-here");
155
+ console.error("\n Or prefix the command:");
156
+ console.error(" CHATTER_SECRET=your-secret npx chatter create-apikey\n");
157
+ process.exit(1);
158
+ }
159
+ const manager = new ApiKeyManager(secret);
160
+ const keyName = options.name || "api-key";
161
+ const expiresIn = options.expiresIn || "365d";
162
+ try {
163
+ const apiKey = await manager.create({
164
+ name: keyName,
165
+ expiresIn
166
+ });
167
+ const payload = manager.decode(apiKey);
168
+ if (!payload) {
169
+ throw new Error("Failed to decode generated API key");
170
+ }
171
+ const expiresAt = new Date((payload.exp || 0) * 1e3);
172
+ const issuedAt = new Date((payload.iat || 0) * 1e3);
173
+ console.log("\n\u2705 API Key generated successfully!\n");
174
+ console.log(` Name: ${keyName}`);
175
+ console.log(` ID: ${payload.sub}`);
176
+ console.log(` Issued: ${issuedAt.toISOString()}`);
177
+ console.log(` Expires: ${expiresAt.toISOString()} (${expiresIn})`);
178
+ console.log(`
179
+ API Key:
180
+ ${apiKey}
181
+ `);
182
+ console.log(" Usage:");
183
+ console.log(" curl -X POST https://your-bot.example.com/api/public/chat \\");
184
+ console.log(` -H "x-api-key: ${apiKey.slice(0, 50)}..." \\`);
185
+ console.log(' -H "Content-Type: application/json" \\');
186
+ console.log(` -d '{"message": "Hello"}'`);
187
+ console.log("");
188
+ } catch (error) {
189
+ console.error("\u274C Error creating API key:", error);
190
+ process.exit(1);
191
+ }
192
+ }
193
+ main();
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@diegoaltoworks/chatter",
3
- "version": "0.12.0",
3
+ "version": "0.13.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",
7
7
  "types": "./dist/index.d.ts",
8
8
  "bin": {
9
- "chatter": "./bin/create-apikey.ts"
9
+ "chatter": "./dist/bin/create-apikey.js"
10
10
  },
11
11
  "exports": {
12
12
  ".": {
@@ -30,15 +30,16 @@
30
30
  "require": "./dist/mcp-server.js"
31
31
  }
32
32
  },
33
- "files": ["dist", "bin", "README.md", "LICENSE"],
33
+ "files": ["dist", "README.md", "LICENSE"],
34
34
  "scripts": {
35
35
  "dev": "bun --watch src/index.ts",
36
- "build": "bun run build:clean && bun run build:types && bun run build:esm && bun run build:cjs && bun run build:mcp && bun run build:client && bun run build:copy-client",
36
+ "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",
37
37
  "build:clean": "rm -rf dist",
38
38
  "build:types": "tsc --emitDeclarationOnly --outDir dist",
39
39
  "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",
40
40
  "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",
41
41
  "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",
42
+ "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'",
42
43
  "build:client": "cd src/client && bun run build",
43
44
  "build:copy-client": "mkdir -p dist/static && cp src/client/dist/chatter.js src/client/dist/chatter.css dist/static/",
44
45
  "test": "bun test",
@@ -1,101 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * CLI tool for creating Chatter API keys
4
- *
5
- * Usage:
6
- * npx chatter create-apikey --name "my-key" --expires-in 365d
7
- * bun run bin/create-apikey.ts --name "test" --expires-in 1y
8
- */
9
-
10
- import { ApiKeyManager } from "../src/auth/apikeys";
11
-
12
- async function main() {
13
- // Parse command line arguments
14
- const args = process.argv.slice(2);
15
- const options: { name?: string; expiresIn?: string } = {};
16
-
17
- for (let i = 0; i < args.length; i++) {
18
- if (args[i] === "--name" && args[i + 1]) {
19
- options.name = args[i + 1];
20
- i++;
21
- } else if (args[i] === "--expires-in" && args[i + 1]) {
22
- options.expiresIn = args[i + 1];
23
- i++;
24
- } else if (args[i] === "--help" || args[i] === "-h") {
25
- console.log(`
26
- Chatter API Key Generator
27
-
28
- Usage:
29
- npx chatter create-apikey [options]
30
-
31
- Options:
32
- --name <name> Name/label for the API key (default: "api-key")
33
- --expires-in <time> Expiration time (default: "365d")
34
- Formats: s=seconds, m=minutes, h=hours, d=days, M=months, y=years
35
- Examples: 30d, 12h, 1y, 6M
36
-
37
- Environment:
38
- CHATTER_SECRET Required. Secret key for signing JWT tokens
39
-
40
- Examples:
41
- npx chatter create-apikey --name "mobile-app" --expires-in 365d
42
- npx chatter create-apikey --name "test-key" --expires-in 1h
43
- `);
44
- process.exit(0);
45
- }
46
- }
47
-
48
- // Check for CHATTER_SECRET
49
- const secret = process.env.CHATTER_SECRET;
50
- if (!secret) {
51
- console.error("❌ Error: CHATTER_SECRET environment variable is not set");
52
- console.error("\n Please set it before running this command:");
53
- console.error(" export CHATTER_SECRET=your-secret-key-here");
54
- console.error("\n Or prefix the command:");
55
- console.error(" CHATTER_SECRET=your-secret npx chatter create-apikey\n");
56
- process.exit(1);
57
- }
58
-
59
- // Create API key manager
60
- const manager = new ApiKeyManager(secret);
61
-
62
- // Default values
63
- const keyName = options.name || "api-key";
64
- const expiresIn = options.expiresIn || "365d";
65
-
66
- try {
67
- // Create the API key
68
- const apiKey = await manager.create({
69
- name: keyName,
70
- expiresIn,
71
- });
72
-
73
- // Decode to show details (without verification for display)
74
- const payload = manager.decode(apiKey);
75
- if (!payload) {
76
- throw new Error("Failed to decode generated API key");
77
- }
78
-
79
- const expiresAt = new Date((payload.exp || 0) * 1000);
80
- const issuedAt = new Date((payload.iat || 0) * 1000);
81
-
82
- // Output the result
83
- console.log("\n✅ API Key generated successfully!\n");
84
- console.log(` Name: ${keyName}`);
85
- console.log(` ID: ${payload.sub}`);
86
- console.log(` Issued: ${issuedAt.toISOString()}`);
87
- console.log(` Expires: ${expiresAt.toISOString()} (${expiresIn})`);
88
- console.log(`\n API Key:\n ${apiKey}\n`);
89
- console.log(" Usage:");
90
- console.log(" curl -X POST https://your-bot.example.com/api/public/chat \\");
91
- console.log(` -H "x-api-key: ${apiKey.slice(0, 50)}..." \\`);
92
- console.log(' -H "Content-Type: application/json" \\');
93
- console.log(' -d \'{"message": "Hello"}\'');
94
- console.log("");
95
- } catch (error) {
96
- console.error("❌ Error creating API key:", error);
97
- process.exit(1);
98
- }
99
- }
100
-
101
- main();