@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.
- package/dist/bin/create-apikey.js +193 -0
- package/package.json +5 -4
- package/bin/create-apikey.ts +0 -101
|
@@ -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.
|
|
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.
|
|
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", "
|
|
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",
|
package/bin/create-apikey.ts
DELETED
|
@@ -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();
|