safetygates 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CycleCore Technologies LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,200 @@
1
+ # SafetyGates SDK for Node.js
2
+
3
+ Official Node.js client for [SafetyGates](https://cyclecore.ai/safetygates) content moderation API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install safetygates
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```javascript
14
+ const SafetyGates = require('safetygates');
15
+
16
+ const client = new SafetyGates('sg_live_your_api_key');
17
+
18
+ // Classify text
19
+ const result = await client.classify('you suck at this game', ['toxic', 'harassment']);
20
+ console.log(result.results.toxic.label); // true
21
+ console.log(result.results.toxic.confidence); // 0.92
22
+
23
+ // Convenience methods
24
+ if (await client.isToxic('some message')) {
25
+ console.log('Message blocked');
26
+ }
27
+ ```
28
+
29
+ ## Get Your API Key
30
+
31
+ Sign up for free at [sg-api.cyclecore.ai/signup](https://sg-api.cyclecore.ai/signup)
32
+
33
+ - **Free tier:** 1,000 requests/day
34
+ - **No credit card required**
35
+
36
+ ## API Reference
37
+
38
+ ### Constructor
39
+
40
+ ```javascript
41
+ const client = new SafetyGates(apiKey, options);
42
+ ```
43
+
44
+ | Parameter | Type | Description |
45
+ |-----------|------|-------------|
46
+ | `apiKey` | string | Your API key (required) |
47
+ | `options.baseUrl` | string | API URL (default: `https://sg-api.cyclecore.ai`) |
48
+ | `options.timeout` | number | Request timeout in ms (default: 10000) |
49
+
50
+ ### classify(text, gates)
51
+
52
+ Classify a single text.
53
+
54
+ ```javascript
55
+ const result = await client.classify('hello world', ['toxic', 'spam']);
56
+
57
+ // Result:
58
+ {
59
+ results: {
60
+ toxic: { label: false, confidence: 0.12 },
61
+ spam: { label: false, confidence: 0.08 }
62
+ },
63
+ latency_us: 1234.5
64
+ }
65
+ ```
66
+
67
+ ### classifyBatch(texts, gates)
68
+
69
+ Classify multiple texts (up to 10,000).
70
+
71
+ ```javascript
72
+ const result = await client.classifyBatch(
73
+ ['hello', 'you suck', 'nice game'],
74
+ ['toxic']
75
+ );
76
+
77
+ // Result:
78
+ {
79
+ results: [
80
+ { toxic: { label: false, confidence: 0.1 } },
81
+ { toxic: { label: true, confidence: 0.94 } },
82
+ { toxic: { label: false, confidence: 0.05 } }
83
+ ],
84
+ total_latency_us: 3456.7,
85
+ items_per_second: 867.5
86
+ }
87
+ ```
88
+
89
+ ### isToxic(text, threshold?)
90
+
91
+ Convenience method to check toxicity.
92
+
93
+ ```javascript
94
+ if (await client.isToxic(message)) {
95
+ // Block message
96
+ }
97
+
98
+ // With custom threshold
99
+ if (await client.isToxic(message, 0.8)) {
100
+ // Only block if >80% confident
101
+ }
102
+ ```
103
+
104
+ ### isSpam(text, threshold?)
105
+
106
+ Convenience method to check spam.
107
+
108
+ ```javascript
109
+ if (await client.isSpam(message)) {
110
+ // Block spam
111
+ }
112
+ ```
113
+
114
+ ### listGates()
115
+
116
+ Get available classification gates.
117
+
118
+ ```javascript
119
+ const { gates } = await client.listGates();
120
+ // [{ id: 'toxic', category: 'moderation', ... }, ...]
121
+ ```
122
+
123
+ ## Available Gates
124
+
125
+ | Gate | Language | Description |
126
+ |------|----------|-------------|
127
+ | `toxic` | English | Toxic/abusive content |
128
+ | `spam` | English | Spam/promotional |
129
+ | `hate` | English | Hate speech |
130
+ | `nsfw` | English | Adult content |
131
+ | `harassment` | English | Harassment/bullying |
132
+ | `toxic_es` | Spanish | Toxic content |
133
+ | `spam_es` | Spanish | Spam |
134
+ | `hate_es` | Spanish | Hate speech |
135
+ | `toxic_pt` | Portuguese | Toxic content |
136
+ | `toxic_fr` | French | Toxic content |
137
+
138
+ Use the `GATES` constant for autocomplete:
139
+
140
+ ```javascript
141
+ const { GATES } = require('safetygates');
142
+ client.classify(text, [GATES.TOXIC, GATES.SPAM]);
143
+ ```
144
+
145
+ ## Error Handling
146
+
147
+ ```javascript
148
+ const { SafetyGatesError } = require('safetygates');
149
+
150
+ try {
151
+ await client.classify('test', ['invalid_gate']);
152
+ } catch (err) {
153
+ if (err instanceof SafetyGatesError) {
154
+ console.log(err.statusCode); // 400
155
+ console.log(err.detail); // { detail: "Unknown gates: ['invalid_gate']" }
156
+ }
157
+ }
158
+ ```
159
+
160
+ ## Discord Bot Example
161
+
162
+ ```javascript
163
+ const SafetyGates = require('safetygates');
164
+ const { Client, GatewayIntentBits } = require('discord.js');
165
+
166
+ const sg = new SafetyGates(process.env.SAFETYGATES_KEY);
167
+ const discord = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages] });
168
+
169
+ discord.on('messageCreate', async (message) => {
170
+ if (message.author.bot) return;
171
+
172
+ if (await sg.isToxic(message.content, 0.7)) {
173
+ await message.delete();
174
+ await message.channel.send(`${message.author}, please keep it civil.`);
175
+ }
176
+ });
177
+
178
+ discord.login(process.env.DISCORD_TOKEN);
179
+ ```
180
+
181
+ ## TypeScript
182
+
183
+ Full TypeScript support included:
184
+
185
+ ```typescript
186
+ import SafetyGates, { ClassifyResult, GATES } from 'safetygates';
187
+
188
+ const client = new SafetyGates('sg_live_xxx');
189
+ const result: ClassifyResult = await client.classify('test', [GATES.TOXIC]);
190
+ ```
191
+
192
+ ## Links
193
+
194
+ - [Documentation](https://sg-api.cyclecore.ai/docs)
195
+ - [Get API Key](https://sg-api.cyclecore.ai/signup)
196
+ - [CycleCore](https://cyclecore.ai)
197
+
198
+ ## License
199
+
200
+ MIT
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "safetygates",
3
+ "version": "0.1.0",
4
+ "description": "Official SDK for SafetyGates content moderation API",
5
+ "main": "src/index.js",
6
+ "types": "src/index.d.ts",
7
+ "scripts": {
8
+ "test": "node test/test.js"
9
+ },
10
+ "keywords": [
11
+ "content-moderation",
12
+ "safety",
13
+ "toxic",
14
+ "spam",
15
+ "hate-speech",
16
+ "moderation",
17
+ "discord",
18
+ "gaming",
19
+ "chat"
20
+ ],
21
+ "author": "CycleCore Technologies <hi@cyclecore.ai>",
22
+ "license": "MIT",
23
+ "homepage": "https://cyclecore.ai/safetygates",
24
+ "engines": {
25
+ "node": ">=16.0.0"
26
+ },
27
+ "files": [
28
+ "src/"
29
+ ]
30
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * SafetyGates SDK TypeScript Definitions
3
+ *
4
+ * Copyright (c) 2026 CycleCore Technologies LLC
5
+ * Licensed under the MIT License
6
+ */
7
+
8
+ export interface GateResult {
9
+ label: boolean;
10
+ confidence: number;
11
+ }
12
+
13
+ export interface ClassifyResult {
14
+ results: Record<string, GateResult>;
15
+ latency_us: number;
16
+ }
17
+
18
+ export interface BatchClassifyResult {
19
+ results: Array<Record<string, GateResult>>;
20
+ total_latency_us: number;
21
+ items_per_second: number;
22
+ }
23
+
24
+ export interface GateInfo {
25
+ id: string;
26
+ category: string;
27
+ description: string;
28
+ threshold: number;
29
+ }
30
+
31
+ export interface GatesListResult {
32
+ gates: GateInfo[];
33
+ }
34
+
35
+ export interface HealthResult {
36
+ status: string;
37
+ gates_loaded: number;
38
+ available_gates: string[];
39
+ }
40
+
41
+ export interface SafetyGatesOptions {
42
+ baseUrl?: string;
43
+ timeout?: number;
44
+ }
45
+
46
+ export declare const GATES: {
47
+ TOXIC: 'toxic';
48
+ SPAM: 'spam';
49
+ HATE: 'hate';
50
+ NSFW: 'nsfw';
51
+ HARASSMENT: 'harassment';
52
+ TOXIC_ES: 'toxic_es';
53
+ SPAM_ES: 'spam_es';
54
+ HATE_ES: 'hate_es';
55
+ TOXIC_PT: 'toxic_pt';
56
+ TOXIC_FR: 'toxic_fr';
57
+ };
58
+
59
+ export declare class SafetyGatesError extends Error {
60
+ statusCode: number;
61
+ detail: any;
62
+ constructor(message: string, statusCode: number, detail: any);
63
+ }
64
+
65
+ export declare class SafetyGates {
66
+ constructor(apiKey: string, options?: SafetyGatesOptions);
67
+
68
+ /**
69
+ * Classify a single text
70
+ */
71
+ classify(text: string, gates?: string[]): Promise<ClassifyResult>;
72
+
73
+ /**
74
+ * Classify multiple texts in batch
75
+ */
76
+ classifyBatch(texts: string[], gates?: string[]): Promise<BatchClassifyResult>;
77
+
78
+ /**
79
+ * Check if text is toxic (convenience method)
80
+ */
81
+ isToxic(text: string, threshold?: number): Promise<boolean>;
82
+
83
+ /**
84
+ * Check if text is spam (convenience method)
85
+ */
86
+ isSpam(text: string, threshold?: number): Promise<boolean>;
87
+
88
+ /**
89
+ * Get available gates
90
+ */
91
+ listGates(): Promise<GatesListResult>;
92
+
93
+ /**
94
+ * Health check
95
+ */
96
+ health(): Promise<HealthResult>;
97
+ }
98
+
99
+ export default SafetyGates;
package/src/index.js ADDED
@@ -0,0 +1,228 @@
1
+ /**
2
+ * SafetyGates SDK for Node.js
3
+ * Official client library for the SafetyGates content moderation API
4
+ *
5
+ * Copyright (c) 2026 CycleCore Technologies LLC
6
+ * Licensed under the MIT License
7
+ * https://cyclecore.ai/safetygates
8
+ *
9
+ * @license MIT
10
+ *
11
+ * @example
12
+ * const SafetyGates = require('safetygates');
13
+ * const client = new SafetyGates('sg_live_xxx');
14
+ * const result = await client.classify('hello world', ['toxic']);
15
+ */
16
+
17
+ const https = require('https');
18
+ const http = require('http');
19
+
20
+ const DEFAULT_BASE_URL = 'https://sg-api.cyclecore.ai';
21
+ const DEFAULT_TIMEOUT = 10000;
22
+
23
+ /**
24
+ * Available classification gates
25
+ */
26
+ const GATES = {
27
+ // English
28
+ TOXIC: 'toxic',
29
+ SPAM: 'spam',
30
+ HATE: 'hate',
31
+ NSFW: 'nsfw',
32
+ HARASSMENT: 'harassment',
33
+ // Spanish
34
+ TOXIC_ES: 'toxic_es',
35
+ SPAM_ES: 'spam_es',
36
+ HATE_ES: 'hate_es',
37
+ // Portuguese
38
+ TOXIC_PT: 'toxic_pt',
39
+ // French
40
+ TOXIC_FR: 'toxic_fr',
41
+ };
42
+
43
+ class SafetyGatesError extends Error {
44
+ constructor(message, statusCode, detail) {
45
+ super(message);
46
+ this.name = 'SafetyGatesError';
47
+ this.statusCode = statusCode;
48
+ this.detail = detail;
49
+ }
50
+ }
51
+
52
+ class SafetyGates {
53
+ /**
54
+ * Create a SafetyGates client
55
+ * @param {string} apiKey - Your API key (starts with sg_live_ or sg_test_)
56
+ * @param {Object} options - Configuration options
57
+ * @param {string} options.baseUrl - API base URL (default: https://sg-api.cyclecore.ai)
58
+ * @param {number} options.timeout - Request timeout in ms (default: 10000)
59
+ */
60
+ constructor(apiKey, options = {}) {
61
+ if (!apiKey) {
62
+ throw new Error('API key is required. Get one at https://sg-api.cyclecore.ai/signup');
63
+ }
64
+ this.apiKey = apiKey;
65
+ this.baseUrl = options.baseUrl || DEFAULT_BASE_URL;
66
+ this.timeout = options.timeout || DEFAULT_TIMEOUT;
67
+ }
68
+
69
+ /**
70
+ * Make HTTP request to API
71
+ * @private
72
+ */
73
+ _request(method, path, body = null) {
74
+ return new Promise((resolve, reject) => {
75
+ const url = new URL(path, this.baseUrl);
76
+ const isHttps = url.protocol === 'https:';
77
+ const lib = isHttps ? https : http;
78
+
79
+ const options = {
80
+ hostname: url.hostname,
81
+ port: url.port || (isHttps ? 443 : 80),
82
+ path: url.pathname,
83
+ method,
84
+ headers: {
85
+ 'Content-Type': 'application/json',
86
+ 'X-API-Key': this.apiKey,
87
+ 'User-Agent': 'safetygates-node/1.0.0',
88
+ },
89
+ timeout: this.timeout,
90
+ };
91
+
92
+ const req = lib.request(options, (res) => {
93
+ let data = '';
94
+ res.on('data', (chunk) => { data += chunk; });
95
+ res.on('end', () => {
96
+ try {
97
+ const json = JSON.parse(data);
98
+ if (res.statusCode >= 400) {
99
+ reject(new SafetyGatesError(
100
+ json.detail || 'API error',
101
+ res.statusCode,
102
+ json
103
+ ));
104
+ } else {
105
+ resolve(json);
106
+ }
107
+ } catch (e) {
108
+ reject(new SafetyGatesError('Invalid JSON response', res.statusCode, data));
109
+ }
110
+ });
111
+ });
112
+
113
+ req.on('error', (e) => {
114
+ reject(new SafetyGatesError(`Request failed: ${e.message}`, 0, null));
115
+ });
116
+
117
+ req.on('timeout', () => {
118
+ req.destroy();
119
+ reject(new SafetyGatesError('Request timeout', 0, null));
120
+ });
121
+
122
+ if (body) {
123
+ req.write(JSON.stringify(body));
124
+ }
125
+ req.end();
126
+ });
127
+ }
128
+
129
+ /**
130
+ * Classify a single text
131
+ * @param {string} text - Text to classify
132
+ * @param {string[]} gates - Gates to check (default: ['toxic'])
133
+ * @returns {Promise<ClassifyResult>}
134
+ *
135
+ * @example
136
+ * const result = await client.classify('you suck', ['toxic', 'harassment']);
137
+ * console.log(result.results.toxic.label); // true
138
+ * console.log(result.results.toxic.confidence); // 0.92
139
+ */
140
+ async classify(text, gates = ['toxic']) {
141
+ if (!text || typeof text !== 'string') {
142
+ throw new Error('Text must be a non-empty string');
143
+ }
144
+ if (!Array.isArray(gates) || gates.length === 0) {
145
+ throw new Error('Gates must be a non-empty array');
146
+ }
147
+
148
+ return this._request('POST', '/v1/classify', { text, gates });
149
+ }
150
+
151
+ /**
152
+ * Classify multiple texts in batch
153
+ * @param {string[]} texts - Array of texts to classify (max 10,000)
154
+ * @param {string[]} gates - Gates to check (default: ['toxic'])
155
+ * @returns {Promise<BatchClassifyResult>}
156
+ *
157
+ * @example
158
+ * const result = await client.classifyBatch(
159
+ * ['hello', 'you suck', 'nice game'],
160
+ * ['toxic']
161
+ * );
162
+ * result.results.forEach((r, i) => {
163
+ * console.log(`Text ${i}: toxic=${r.toxic.label}`);
164
+ * });
165
+ */
166
+ async classifyBatch(texts, gates = ['toxic']) {
167
+ if (!Array.isArray(texts) || texts.length === 0) {
168
+ throw new Error('Texts must be a non-empty array');
169
+ }
170
+ if (texts.length > 10000) {
171
+ throw new Error('Batch size cannot exceed 10,000');
172
+ }
173
+ if (!Array.isArray(gates) || gates.length === 0) {
174
+ throw new Error('Gates must be a non-empty array');
175
+ }
176
+
177
+ return this._request('POST', '/v1/classify/batch', { texts, gates });
178
+ }
179
+
180
+ /**
181
+ * Check if text is toxic (convenience method)
182
+ * @param {string} text - Text to check
183
+ * @param {number} threshold - Confidence threshold (default: 0.5)
184
+ * @returns {Promise<boolean>}
185
+ *
186
+ * @example
187
+ * if (await client.isToxic('you suck')) {
188
+ * console.log('Blocked toxic message');
189
+ * }
190
+ */
191
+ async isToxic(text, threshold = 0.5) {
192
+ const result = await this.classify(text, ['toxic']);
193
+ return result.results.toxic.confidence >= threshold;
194
+ }
195
+
196
+ /**
197
+ * Check if text is spam (convenience method)
198
+ * @param {string} text - Text to check
199
+ * @param {number} threshold - Confidence threshold (default: 0.5)
200
+ * @returns {Promise<boolean>}
201
+ */
202
+ async isSpam(text, threshold = 0.5) {
203
+ const result = await this.classify(text, ['spam']);
204
+ return result.results.spam.confidence >= threshold;
205
+ }
206
+
207
+ /**
208
+ * Get available gates
209
+ * @returns {Promise<GatesListResult>}
210
+ */
211
+ async listGates() {
212
+ return this._request('GET', '/v1/gates');
213
+ }
214
+
215
+ /**
216
+ * Health check
217
+ * @returns {Promise<HealthResult>}
218
+ */
219
+ async health() {
220
+ return this._request('GET', '/health');
221
+ }
222
+ }
223
+
224
+ // Export class and constants
225
+ module.exports = SafetyGates;
226
+ module.exports.SafetyGates = SafetyGates;
227
+ module.exports.SafetyGatesError = SafetyGatesError;
228
+ module.exports.GATES = GATES;