continum 0.0.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.
- package/README.md +258 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +603 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# create-continum
|
|
2
|
+
|
|
3
|
+
Interactive CLI to set up Continum SDK in your project with zero configuration.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
Run this command in your project directory:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx create-continum
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
No installation required! The CLI will guide you through:
|
|
14
|
+
|
|
15
|
+
1. **Product Type** - What kind of AI product you're building
|
|
16
|
+
2. **LLM Providers** - Which providers you're using (OpenAI, Anthropic, etc.)
|
|
17
|
+
3. **Compliance Frameworks** - Which regulations you need to comply with
|
|
18
|
+
4. **Alert Channels** - Real-time notifications for violations (Slack, PagerDuty, Discord, Webhooks)
|
|
19
|
+
5. **API Key** - Your Continum API key
|
|
20
|
+
|
|
21
|
+
## What It Creates
|
|
22
|
+
|
|
23
|
+
After running `npx create-continum`, you'll have:
|
|
24
|
+
|
|
25
|
+
- ✅ `continum.config.ts` (or `.js`) - Your Continum configuration
|
|
26
|
+
- ✅ `.env` - Updated with API key and alert webhooks
|
|
27
|
+
- ✅ `continum-example.ts` (or `.js`) - Sample code to get started
|
|
28
|
+
|
|
29
|
+
## Example Session
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
$ npx create-continum
|
|
33
|
+
|
|
34
|
+
┌ Continum Setup
|
|
35
|
+
│
|
|
36
|
+
◇ Set up Continum AI compliance in your project?
|
|
37
|
+
│ Yes
|
|
38
|
+
│
|
|
39
|
+
◇ What kind of AI product are you building?
|
|
40
|
+
│ 💬 Customer support bot
|
|
41
|
+
│
|
|
42
|
+
◇ Which LLM providers are you using?
|
|
43
|
+
│ ◼ OpenAI
|
|
44
|
+
│ ◼ Anthropic
|
|
45
|
+
│
|
|
46
|
+
◇ Do you need to comply with any specific frameworks?
|
|
47
|
+
│ ◼ GDPR
|
|
48
|
+
│ ◼ SOC2
|
|
49
|
+
│
|
|
50
|
+
◇ Set up real-time alerts for compliance violations?
|
|
51
|
+
│ Yes
|
|
52
|
+
│
|
|
53
|
+
◇ Which alert channels do you want to configure?
|
|
54
|
+
│ ◼ Slack
|
|
55
|
+
│ ◼ PagerDuty
|
|
56
|
+
│
|
|
57
|
+
◇ Slack webhook URL (or press Enter to use env var):
|
|
58
|
+
│ https://hooks.slack.com/services/...
|
|
59
|
+
│
|
|
60
|
+
◇ PagerDuty integration key (or press Enter to use env var):
|
|
61
|
+
│ R0123456789ABCDEF
|
|
62
|
+
│
|
|
63
|
+
◇ Paste your Continum API key:
|
|
64
|
+
│ co_live_...
|
|
65
|
+
│
|
|
66
|
+
◆ Setting up Continum...
|
|
67
|
+
│
|
|
68
|
+
├ Files Created
|
|
69
|
+
│
|
|
70
|
+
│ ✓ Created continum.config.ts
|
|
71
|
+
│ ✓ Updated .env with API key and alert webhooks
|
|
72
|
+
│ ✓ Created continum-example.ts with sample code
|
|
73
|
+
│
|
|
74
|
+
└ Next steps:
|
|
75
|
+
|
|
76
|
+
1. Install the SDK: npm install @continum/sdk
|
|
77
|
+
2. Wrap your first LLM call with protect()
|
|
78
|
+
3. Check continum-example.ts for examples
|
|
79
|
+
|
|
80
|
+
Docs: https://docs.continum.co/quickstart
|
|
81
|
+
Dashboard: https://app.continum.co
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Features
|
|
85
|
+
|
|
86
|
+
### 🎯 Smart Defaults
|
|
87
|
+
|
|
88
|
+
The CLI automatically:
|
|
89
|
+
- Detects TypeScript vs JavaScript
|
|
90
|
+
- Suggests appropriate presets based on your product type
|
|
91
|
+
- Configures compliance frameworks
|
|
92
|
+
- Sets up alert channels
|
|
93
|
+
|
|
94
|
+
### 🔒 Secure by Default
|
|
95
|
+
|
|
96
|
+
- API keys and webhooks stored in `.env` (not committed)
|
|
97
|
+
- HTTPS validation for all webhook URLs
|
|
98
|
+
- Environment variable references in config files
|
|
99
|
+
|
|
100
|
+
### 📝 Complete Examples
|
|
101
|
+
|
|
102
|
+
Generates three example patterns:
|
|
103
|
+
1. Basic usage with inline config
|
|
104
|
+
2. Global configuration (recommended)
|
|
105
|
+
3. Blocking mode for high-risk scenarios
|
|
106
|
+
|
|
107
|
+
### 🚀 Zero Installation
|
|
108
|
+
|
|
109
|
+
Run with `npx` - no global installation needed.
|
|
110
|
+
|
|
111
|
+
## Configuration File
|
|
112
|
+
|
|
113
|
+
The generated `continum.config.ts` looks like:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import type { ContinumConfig } from '@continum/sdk';
|
|
117
|
+
|
|
118
|
+
const config: ContinumConfig = {
|
|
119
|
+
apiKey: process.env.CONTINUM_API_KEY,
|
|
120
|
+
preset: 'customer-support',
|
|
121
|
+
comply: ['GDPR', 'SOC2'],
|
|
122
|
+
alerts: {
|
|
123
|
+
slack: process.env.SLACK_WEBHOOK_URL,
|
|
124
|
+
pagerduty: process.env.PAGERDUTY_KEY,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export default config;
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Example Code
|
|
132
|
+
|
|
133
|
+
The generated `continum-example.ts` includes:
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { protect } from '@continum/sdk';
|
|
137
|
+
import OpenAI from 'openai';
|
|
138
|
+
|
|
139
|
+
const openai = new OpenAI();
|
|
140
|
+
|
|
141
|
+
// Example 1: Basic usage
|
|
142
|
+
const response = await protect(
|
|
143
|
+
() => openai.chat.completions.create({
|
|
144
|
+
model: 'gpt-4',
|
|
145
|
+
messages: [{ role: 'user', content: 'Hello!' }]
|
|
146
|
+
}),
|
|
147
|
+
{
|
|
148
|
+
apiKey: process.env.CONTINUM_API_KEY!,
|
|
149
|
+
preset: 'customer-support',
|
|
150
|
+
comply: ['GDPR', 'SOC2'],
|
|
151
|
+
alerts: {
|
|
152
|
+
slack: process.env.SLACK_WEBHOOK_URL,
|
|
153
|
+
pagerduty: process.env.PAGERDUTY_KEY,
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
console.log(response.choices[0].message.content);
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Product Types
|
|
162
|
+
|
|
163
|
+
Choose from:
|
|
164
|
+
- 💬 Customer support bot
|
|
165
|
+
- 💻 Coding assistant
|
|
166
|
+
- ⚖️ Legal or research tool
|
|
167
|
+
- 🏥 Healthcare application
|
|
168
|
+
- 💰 Financial services
|
|
169
|
+
- 🤖 AI agent with tool use
|
|
170
|
+
- ✍️ Content generation
|
|
171
|
+
- 🔧 Internal team tool
|
|
172
|
+
- 📊 Data pipeline
|
|
173
|
+
- 🎓 Education tool
|
|
174
|
+
|
|
175
|
+
## Compliance Frameworks
|
|
176
|
+
|
|
177
|
+
Supported frameworks:
|
|
178
|
+
- GDPR (EU data protection)
|
|
179
|
+
- CCPA (California privacy)
|
|
180
|
+
- SOC2 (Security & availability)
|
|
181
|
+
- HIPAA (US healthcare data)
|
|
182
|
+
- EU AI Act (EU AI regulation)
|
|
183
|
+
- ISO 27001 (Information security)
|
|
184
|
+
- FINRA (US financial services)
|
|
185
|
+
- PCI DSS (Payment card security)
|
|
186
|
+
- NIST AI RMF (AI risk management)
|
|
187
|
+
|
|
188
|
+
## Alert Channels
|
|
189
|
+
|
|
190
|
+
Configure real-time alerts for:
|
|
191
|
+
- **Slack** - Team notifications
|
|
192
|
+
- **PagerDuty** - Critical incidents
|
|
193
|
+
- **Discord** - Community alerts
|
|
194
|
+
- **Custom Webhook** - Your own endpoint
|
|
195
|
+
|
|
196
|
+
All webhook URLs must use HTTPS for security.
|
|
197
|
+
|
|
198
|
+
## Requirements
|
|
199
|
+
|
|
200
|
+
- Node.js >= 18.0.0
|
|
201
|
+
- A Continum API key (get one at https://app.continum.co)
|
|
202
|
+
|
|
203
|
+
## Get Your API Key
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
# Individual Account (DEV plan - Free: 1,000 audits)
|
|
207
|
+
curl -X POST https://api.continum.io/customers \
|
|
208
|
+
-H "Content-Type: application/json" \
|
|
209
|
+
-d '{"email":"you@example.com"}'
|
|
210
|
+
|
|
211
|
+
# Company Account (PRO/PRO_MAX/ENTERPRISE)
|
|
212
|
+
curl -X POST https://api.continum.io/customers \
|
|
213
|
+
-H "Content-Type: application/json" \
|
|
214
|
+
-d '{
|
|
215
|
+
"email":"you@company.com",
|
|
216
|
+
"name":"Your Company",
|
|
217
|
+
"accountType":"COMPANY",
|
|
218
|
+
"plan":"PRO"
|
|
219
|
+
}'
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Next Steps
|
|
223
|
+
|
|
224
|
+
After running `npx create-continum`:
|
|
225
|
+
|
|
226
|
+
1. Install the SDK:
|
|
227
|
+
```bash
|
|
228
|
+
npm install @continum/sdk
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
2. Import and use in your code:
|
|
232
|
+
```typescript
|
|
233
|
+
import { protect } from '@continum/sdk';
|
|
234
|
+
|
|
235
|
+
const response = await protect(() => yourLLMCall());
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
3. Check the dashboard:
|
|
239
|
+
```
|
|
240
|
+
https://app.continum.co
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Documentation
|
|
244
|
+
|
|
245
|
+
- [Quick Start Guide](https://docs.continum.co/quickstart)
|
|
246
|
+
- [SDK Documentation](https://docs.continum.co/sdk)
|
|
247
|
+
- [Alert Setup Guide](https://docs.continum.co/alerts)
|
|
248
|
+
- [API Reference](https://docs.continum.co/api)
|
|
249
|
+
|
|
250
|
+
## Support
|
|
251
|
+
|
|
252
|
+
- **Docs**: https://docs.continum.co
|
|
253
|
+
- **Dashboard**: https://app.continum.co
|
|
254
|
+
- **Email**: support@continum.co
|
|
255
|
+
|
|
256
|
+
## License
|
|
257
|
+
|
|
258
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import * as p from "@clack/prompts";
|
|
5
|
+
import { existsSync, writeFileSync, readFileSync } from "fs";
|
|
6
|
+
import pc from "picocolors";
|
|
7
|
+
var args = process.argv.slice(2);
|
|
8
|
+
var command = args[0];
|
|
9
|
+
if (command !== "init") {
|
|
10
|
+
console.error(pc.red(`
|
|
11
|
+
Error: Command required`));
|
|
12
|
+
console.log(`
|
|
13
|
+
Usage: ${pc.cyan("npx continum init")}
|
|
14
|
+
`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
main().catch(console.error);
|
|
18
|
+
async function main() {
|
|
19
|
+
console.clear();
|
|
20
|
+
p.intro(pc.bgCyan(pc.black(" Continum Setup ")));
|
|
21
|
+
const shouldContinue = await p.confirm({
|
|
22
|
+
message: "Set up Continum AI compliance in your project?",
|
|
23
|
+
initialValue: true
|
|
24
|
+
});
|
|
25
|
+
if (p.isCancel(shouldContinue) || !shouldContinue) {
|
|
26
|
+
p.cancel("Setup cancelled");
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
const config = {};
|
|
30
|
+
const productType = await p.select({
|
|
31
|
+
message: "What kind of AI product are you building?",
|
|
32
|
+
options: [
|
|
33
|
+
{ value: "customer-support", label: "\u{1F4AC} Customer support bot", hint: "Chat, help desk, Q&A" },
|
|
34
|
+
{ value: "coding-assistant", label: "\u{1F4BB} Coding assistant", hint: "Code generation, review" },
|
|
35
|
+
{ value: "legal-ai", label: "\u2696\uFE0F Legal or research tool", hint: "Document analysis, research" },
|
|
36
|
+
{ value: "healthcare-ai", label: "\u{1F3E5} Healthcare application", hint: "Medical advice, diagnosis" },
|
|
37
|
+
{ value: "fintech-ai", label: "\u{1F4B0} Financial services", hint: "Trading, advice, analysis" },
|
|
38
|
+
{ value: "agent", label: "\u{1F916} AI agent with tool use", hint: "Autonomous actions, APIs" },
|
|
39
|
+
{ value: "content-generation", label: "\u270D\uFE0F Content generation", hint: "Writing, marketing, creative" },
|
|
40
|
+
{ value: "internal-tool", label: "\u{1F527} Internal team tool", hint: "Productivity, automation" },
|
|
41
|
+
{ value: "data-pipeline", label: "\u{1F4CA} Data pipeline", hint: "ETL, processing, analysis" },
|
|
42
|
+
{ value: "education-ai", label: "\u{1F393} Education tool", hint: "Tutoring, learning, training" }
|
|
43
|
+
]
|
|
44
|
+
});
|
|
45
|
+
if (p.isCancel(productType)) {
|
|
46
|
+
p.cancel("Setup cancelled");
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
config.productType = productType;
|
|
50
|
+
const providers = await p.multiselect({
|
|
51
|
+
message: "Which LLM providers are you using?",
|
|
52
|
+
options: [
|
|
53
|
+
{ value: "openai", label: "OpenAI", hint: "GPT-4o, GPT-4, o1, o3" },
|
|
54
|
+
{ value: "anthropic", label: "Anthropic", hint: "Claude Opus, Sonnet, Haiku" },
|
|
55
|
+
{ value: "google", label: "Google Gemini", hint: "Gemini Pro, Flash" },
|
|
56
|
+
{ value: "azure", label: "Azure OpenAI", hint: "OpenAI via Azure" },
|
|
57
|
+
{ value: "bedrock", label: "AWS Bedrock", hint: "Any model via Bedrock" },
|
|
58
|
+
{ value: "other", label: "Other", hint: "Custom or multiple providers" }
|
|
59
|
+
],
|
|
60
|
+
required: true
|
|
61
|
+
});
|
|
62
|
+
if (p.isCancel(providers)) {
|
|
63
|
+
p.cancel("Setup cancelled");
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
config.providers = providers;
|
|
67
|
+
const frameworks = await p.multiselect({
|
|
68
|
+
message: "Do you need to comply with any specific frameworks?",
|
|
69
|
+
options: [
|
|
70
|
+
{ value: "GDPR", label: "GDPR", hint: "EU data protection" },
|
|
71
|
+
{ value: "CCPA", label: "CCPA", hint: "California privacy" },
|
|
72
|
+
{ value: "SOC2", label: "SOC2", hint: "Security & availability" },
|
|
73
|
+
{ value: "HIPAA", label: "HIPAA", hint: "US healthcare data" },
|
|
74
|
+
{ value: "EU_AI_ACT", label: "EU AI Act", hint: "EU AI regulation" },
|
|
75
|
+
{ value: "ISO_27001", label: "ISO 27001", hint: "Information security" },
|
|
76
|
+
{ value: "FINRA", label: "FINRA", hint: "US financial services" },
|
|
77
|
+
{ value: "PCI_DSS", label: "PCI DSS", hint: "Payment card security" },
|
|
78
|
+
{ value: "NIST_AI_RMF", label: "NIST AI RMF", hint: "AI risk management" }
|
|
79
|
+
],
|
|
80
|
+
required: false
|
|
81
|
+
});
|
|
82
|
+
if (p.isCancel(frameworks)) {
|
|
83
|
+
p.cancel("Setup cancelled");
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
config.frameworks = frameworks || [];
|
|
87
|
+
const wantsAlerts = await p.confirm({
|
|
88
|
+
message: "Set up real-time alerts for compliance violations?",
|
|
89
|
+
initialValue: true
|
|
90
|
+
});
|
|
91
|
+
if (p.isCancel(wantsAlerts)) {
|
|
92
|
+
p.cancel("Setup cancelled");
|
|
93
|
+
process.exit(0);
|
|
94
|
+
}
|
|
95
|
+
config.alerts = { enabled: wantsAlerts };
|
|
96
|
+
if (wantsAlerts) {
|
|
97
|
+
p.note(
|
|
98
|
+
"Configure alert channels to receive notifications when violations are detected.\nYou can add webhook URLs now or set them as environment variables later.",
|
|
99
|
+
"Alert Channels"
|
|
100
|
+
);
|
|
101
|
+
const alertChannels = await p.multiselect({
|
|
102
|
+
message: "Which alert channels do you want to configure?",
|
|
103
|
+
options: [
|
|
104
|
+
{ value: "slack", label: "Slack", hint: "Team notifications" },
|
|
105
|
+
{ value: "pagerduty", label: "PagerDuty", hint: "Critical incidents" },
|
|
106
|
+
{ value: "discord", label: "Discord", hint: "Community alerts" },
|
|
107
|
+
{ value: "webhook", label: "Custom Webhook", hint: "Your own endpoint" }
|
|
108
|
+
],
|
|
109
|
+
required: false
|
|
110
|
+
});
|
|
111
|
+
if (!p.isCancel(alertChannels)) {
|
|
112
|
+
const channels = alertChannels;
|
|
113
|
+
if (channels.includes("slack")) {
|
|
114
|
+
const slackWebhook = await p.text({
|
|
115
|
+
message: "Slack webhook URL (or press Enter to use env var):",
|
|
116
|
+
placeholder: "https://hooks.slack.com/services/...",
|
|
117
|
+
validate: (v) => {
|
|
118
|
+
if (!v) return void 0;
|
|
119
|
+
if (!v.startsWith("https://")) return "Must be HTTPS URL";
|
|
120
|
+
return void 0;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
if (!p.isCancel(slackWebhook) && slackWebhook) {
|
|
124
|
+
config.alerts.slack = slackWebhook;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (channels.includes("pagerduty")) {
|
|
128
|
+
const pagerdutyKey = await p.text({
|
|
129
|
+
message: "PagerDuty integration key (or press Enter to use env var):",
|
|
130
|
+
placeholder: "R0123456789ABCDEF",
|
|
131
|
+
validate: (v) => {
|
|
132
|
+
if (!v) return void 0;
|
|
133
|
+
return void 0;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
if (!p.isCancel(pagerdutyKey) && pagerdutyKey) {
|
|
137
|
+
config.alerts.pagerduty = pagerdutyKey;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (channels.includes("discord")) {
|
|
141
|
+
const discordWebhook = await p.text({
|
|
142
|
+
message: "Discord webhook URL (or press Enter to use env var):",
|
|
143
|
+
placeholder: "https://discord.com/api/webhooks/...",
|
|
144
|
+
validate: (v) => {
|
|
145
|
+
if (!v) return void 0;
|
|
146
|
+
if (!v.startsWith("https://")) return "Must be HTTPS URL";
|
|
147
|
+
return void 0;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
if (!p.isCancel(discordWebhook) && discordWebhook) {
|
|
151
|
+
config.alerts.discord = discordWebhook;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (channels.includes("webhook")) {
|
|
155
|
+
const customWebhook = await p.text({
|
|
156
|
+
message: "Custom webhook URL (or press Enter to use env var):",
|
|
157
|
+
placeholder: "https://your-app.com/continum-alerts",
|
|
158
|
+
validate: (v) => {
|
|
159
|
+
if (!v) return void 0;
|
|
160
|
+
if (!v.startsWith("https://")) return "Must be HTTPS URL";
|
|
161
|
+
return void 0;
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
if (!p.isCancel(customWebhook) && customWebhook) {
|
|
165
|
+
config.alerts.webhook = customWebhook;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const shouldInstallSDK = await p.confirm({
|
|
171
|
+
message: "Install @continum/sdk now?",
|
|
172
|
+
initialValue: true
|
|
173
|
+
});
|
|
174
|
+
if (p.isCancel(shouldInstallSDK)) {
|
|
175
|
+
p.cancel("Setup cancelled");
|
|
176
|
+
process.exit(0);
|
|
177
|
+
}
|
|
178
|
+
if (shouldInstallSDK) {
|
|
179
|
+
const installSpinner = p.spinner();
|
|
180
|
+
installSpinner.start("Installing @continum/sdk...");
|
|
181
|
+
try {
|
|
182
|
+
await installSDK();
|
|
183
|
+
installSpinner.stop("\u2713 @continum/sdk installed!");
|
|
184
|
+
} catch (error) {
|
|
185
|
+
installSpinner.stop("Installation failed");
|
|
186
|
+
p.log.error(error instanceof Error ? error.message : "Unknown error");
|
|
187
|
+
p.note(
|
|
188
|
+
`You can install it manually later:
|
|
189
|
+
|
|
190
|
+
${pc.cyan("npm install @continum/sdk")}`,
|
|
191
|
+
"Manual Installation"
|
|
192
|
+
);
|
|
193
|
+
const continueAnyway = await p.confirm({
|
|
194
|
+
message: "Continue with configuration setup?",
|
|
195
|
+
initialValue: true
|
|
196
|
+
});
|
|
197
|
+
if (p.isCancel(continueAnyway) || !continueAnyway) {
|
|
198
|
+
p.cancel("Setup cancelled");
|
|
199
|
+
process.exit(0);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const hasTypeScript = existsSync("tsconfig.json");
|
|
204
|
+
config.projectType = hasTypeScript ? "typescript" : "javascript";
|
|
205
|
+
const s = p.spinner();
|
|
206
|
+
s.start("Creating configuration files...");
|
|
207
|
+
try {
|
|
208
|
+
await generateConfigFile(config);
|
|
209
|
+
await updateEnvFile(config);
|
|
210
|
+
await generateExampleFile(config);
|
|
211
|
+
s.stop("\u2713 Configuration files created!");
|
|
212
|
+
const filesCreated = [
|
|
213
|
+
`${pc.green("\u2713")} Created ${pc.cyan("continum.config." + (config.projectType === "typescript" ? "ts" : "js"))}`,
|
|
214
|
+
`${pc.green("\u2713")} Created ${pc.cyan(".env")} with placeholder values`,
|
|
215
|
+
`${pc.green("\u2713")} Created ${pc.cyan("continum-example." + (config.projectType === "typescript" ? "ts" : "js"))} with sample code`
|
|
216
|
+
];
|
|
217
|
+
if (shouldInstallSDK) {
|
|
218
|
+
filesCreated.push(`${pc.green("\u2713")} Installed ${pc.cyan("@continum/sdk")}`);
|
|
219
|
+
}
|
|
220
|
+
p.note(filesCreated.join("\n"), "Setup Complete");
|
|
221
|
+
p.note(
|
|
222
|
+
`${pc.yellow("\u26A0")} Add your Continum API key to ${pc.cyan(".env")}:
|
|
223
|
+
|
|
224
|
+
${pc.dim("CONTINUM_API_KEY=")}${pc.cyan("your_api_key_here")}
|
|
225
|
+
|
|
226
|
+
Get your API key at: ${pc.underline("https://app.continum.co")}`,
|
|
227
|
+
"Action Required"
|
|
228
|
+
);
|
|
229
|
+
const nextSteps = shouldInstallSDK ? [
|
|
230
|
+
` 1. Add your API key to ${pc.cyan(".env")}`,
|
|
231
|
+
` 2. Wrap your first LLM call with ${pc.cyan("protect()")}`,
|
|
232
|
+
` 3. Check ${pc.cyan("continum-example." + (config.projectType === "typescript" ? "ts" : "js"))} for examples`,
|
|
233
|
+
` 4. Run your app and view violations in the dashboard`
|
|
234
|
+
] : [
|
|
235
|
+
` 1. Install the SDK: ${pc.cyan("npm install @continum/sdk")}`,
|
|
236
|
+
` 2. Add your API key to ${pc.cyan(".env")}`,
|
|
237
|
+
` 3. Wrap your first LLM call with ${pc.cyan("protect()")}`,
|
|
238
|
+
` 4. Check ${pc.cyan("continum-example." + (config.projectType === "typescript" ? "ts" : "js"))} for examples`,
|
|
239
|
+
` 5. Run your app and view violations in the dashboard`
|
|
240
|
+
];
|
|
241
|
+
p.outro(
|
|
242
|
+
pc.bold("Next steps:\n\n") + nextSteps.join("\n") + `
|
|
243
|
+
|
|
244
|
+
${pc.dim("Docs:")} ${pc.underline("https://docs.continum.co/quickstart")}
|
|
245
|
+
${pc.dim("Dashboard:")} ${pc.underline("https://app.continum.co")}`
|
|
246
|
+
);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
s.stop("Setup failed");
|
|
249
|
+
p.cancel(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async function generateConfigFile(config) {
|
|
254
|
+
const ext = config.projectType === "typescript" ? "ts" : "js";
|
|
255
|
+
const filename = `continum.config.${ext}`;
|
|
256
|
+
let content = "";
|
|
257
|
+
if (config.projectType === "typescript") {
|
|
258
|
+
content += `import type { ContinumConfig } from '@continum/sdk';
|
|
259
|
+
|
|
260
|
+
`;
|
|
261
|
+
content += `const config: ContinumConfig = {
|
|
262
|
+
`;
|
|
263
|
+
} else {
|
|
264
|
+
content += `/** @type {import('@continum/sdk').ContinumConfig} */
|
|
265
|
+
`;
|
|
266
|
+
content += `const config = {
|
|
267
|
+
`;
|
|
268
|
+
}
|
|
269
|
+
content += ` apiKey: process.env.CONTINUM_API_KEY,
|
|
270
|
+
`;
|
|
271
|
+
content += ` preset: '${config.productType}',
|
|
272
|
+
`;
|
|
273
|
+
if (config.frameworks.length > 0) {
|
|
274
|
+
content += ` comply: [${config.frameworks.map((f) => `'${f}'`).join(", ")}],
|
|
275
|
+
`;
|
|
276
|
+
}
|
|
277
|
+
if (config.alerts.enabled) {
|
|
278
|
+
content += ` alerts: {
|
|
279
|
+
`;
|
|
280
|
+
if (config.alerts.slack) {
|
|
281
|
+
content += ` slack: process.env.SLACK_WEBHOOK_URL,
|
|
282
|
+
`;
|
|
283
|
+
}
|
|
284
|
+
if (config.alerts.pagerduty) {
|
|
285
|
+
content += ` pagerduty: process.env.PAGERDUTY_KEY,
|
|
286
|
+
`;
|
|
287
|
+
}
|
|
288
|
+
if (config.alerts.discord) {
|
|
289
|
+
content += ` discord: process.env.DISCORD_WEBHOOK_URL,
|
|
290
|
+
`;
|
|
291
|
+
}
|
|
292
|
+
if (config.alerts.webhook) {
|
|
293
|
+
content += ` webhook: process.env.CUSTOM_WEBHOOK_URL,
|
|
294
|
+
`;
|
|
295
|
+
}
|
|
296
|
+
content += ` },
|
|
297
|
+
`;
|
|
298
|
+
}
|
|
299
|
+
content += `};
|
|
300
|
+
|
|
301
|
+
`;
|
|
302
|
+
if (config.projectType === "typescript") {
|
|
303
|
+
content += `export default config;
|
|
304
|
+
`;
|
|
305
|
+
} else {
|
|
306
|
+
content += `module.exports = config;
|
|
307
|
+
`;
|
|
308
|
+
}
|
|
309
|
+
writeFileSync(filename, content);
|
|
310
|
+
}
|
|
311
|
+
async function updateEnvFile(config) {
|
|
312
|
+
const envFile = ".env";
|
|
313
|
+
let envContent = "";
|
|
314
|
+
if (existsSync(envFile)) {
|
|
315
|
+
envContent = readFileSync(envFile, "utf-8");
|
|
316
|
+
}
|
|
317
|
+
const newVars = [];
|
|
318
|
+
if (!envContent.includes("CONTINUM_API_KEY")) {
|
|
319
|
+
newVars.push(`CONTINUM_API_KEY=your_api_key_here`);
|
|
320
|
+
}
|
|
321
|
+
if (config.alerts.enabled) {
|
|
322
|
+
if (config.alerts.slack && !envContent.includes("SLACK_WEBHOOK_URL")) {
|
|
323
|
+
newVars.push(`SLACK_WEBHOOK_URL=${config.alerts.slack}`);
|
|
324
|
+
} else if (!config.alerts.slack && !envContent.includes("SLACK_WEBHOOK_URL")) {
|
|
325
|
+
newVars.push(`SLACK_WEBHOOK_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL`);
|
|
326
|
+
}
|
|
327
|
+
if (config.alerts.pagerduty && !envContent.includes("PAGERDUTY_KEY")) {
|
|
328
|
+
newVars.push(`PAGERDUTY_KEY=${config.alerts.pagerduty}`);
|
|
329
|
+
} else if (!config.alerts.pagerduty && !envContent.includes("PAGERDUTY_KEY")) {
|
|
330
|
+
newVars.push(`PAGERDUTY_KEY=YOUR_PAGERDUTY_INTEGRATION_KEY`);
|
|
331
|
+
}
|
|
332
|
+
if (config.alerts.discord && !envContent.includes("DISCORD_WEBHOOK_URL")) {
|
|
333
|
+
newVars.push(`DISCORD_WEBHOOK_URL=${config.alerts.discord}`);
|
|
334
|
+
} else if (!config.alerts.discord && !envContent.includes("DISCORD_WEBHOOK_URL")) {
|
|
335
|
+
newVars.push(`DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR/WEBHOOK`);
|
|
336
|
+
}
|
|
337
|
+
if (config.alerts.webhook && !envContent.includes("CUSTOM_WEBHOOK_URL")) {
|
|
338
|
+
newVars.push(`CUSTOM_WEBHOOK_URL=${config.alerts.webhook}`);
|
|
339
|
+
} else if (!config.alerts.webhook && !envContent.includes("CUSTOM_WEBHOOK_URL")) {
|
|
340
|
+
newVars.push(`CUSTOM_WEBHOOK_URL=https://your-app.com/continum-alerts`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (newVars.length > 0) {
|
|
344
|
+
if (envContent && !envContent.endsWith("\n")) {
|
|
345
|
+
envContent += "\n";
|
|
346
|
+
}
|
|
347
|
+
envContent += "\n# Continum Configuration\n";
|
|
348
|
+
envContent += newVars.join("\n") + "\n";
|
|
349
|
+
writeFileSync(envFile, envContent);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
async function generateExampleFile(config) {
|
|
353
|
+
const ext = config.projectType === "typescript" ? "ts" : "js";
|
|
354
|
+
const filename = `continum-example.${ext}`;
|
|
355
|
+
let content = "";
|
|
356
|
+
content += `import { protect } from '@continum/sdk';
|
|
357
|
+
`;
|
|
358
|
+
if (config.providers.includes("openai")) {
|
|
359
|
+
content += `import OpenAI from 'openai';
|
|
360
|
+
`;
|
|
361
|
+
}
|
|
362
|
+
if (config.providers.includes("anthropic")) {
|
|
363
|
+
content += `import Anthropic from '@anthropic-ai/sdk';
|
|
364
|
+
`;
|
|
365
|
+
}
|
|
366
|
+
if (config.providers.includes("google")) {
|
|
367
|
+
content += `import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
368
|
+
`;
|
|
369
|
+
}
|
|
370
|
+
content += `
|
|
371
|
+
`;
|
|
372
|
+
if (config.providers.includes("openai")) {
|
|
373
|
+
content += `const openai = new OpenAI();
|
|
374
|
+
`;
|
|
375
|
+
}
|
|
376
|
+
if (config.providers.includes("anthropic")) {
|
|
377
|
+
content += `const anthropic = new Anthropic();
|
|
378
|
+
`;
|
|
379
|
+
}
|
|
380
|
+
if (config.providers.includes("google")) {
|
|
381
|
+
content += `const genai = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY!);
|
|
382
|
+
`;
|
|
383
|
+
}
|
|
384
|
+
content += `
|
|
385
|
+
`;
|
|
386
|
+
content += `// Example 1: Basic usage with protect()
|
|
387
|
+
`;
|
|
388
|
+
content += `async function example1() {
|
|
389
|
+
`;
|
|
390
|
+
content += ` const response = await protect(
|
|
391
|
+
`;
|
|
392
|
+
if (config.providers.includes("openai")) {
|
|
393
|
+
content += ` () => openai.chat.completions.create({
|
|
394
|
+
`;
|
|
395
|
+
content += ` model: 'gpt-4',
|
|
396
|
+
`;
|
|
397
|
+
content += ` messages: [{ role: 'user', content: 'Hello!' }]
|
|
398
|
+
`;
|
|
399
|
+
content += ` }),
|
|
400
|
+
`;
|
|
401
|
+
} else if (config.providers.includes("anthropic")) {
|
|
402
|
+
content += ` () => anthropic.messages.create({
|
|
403
|
+
`;
|
|
404
|
+
content += ` model: 'claude-3-5-sonnet-20241022',
|
|
405
|
+
`;
|
|
406
|
+
content += ` max_tokens: 1024,
|
|
407
|
+
`;
|
|
408
|
+
content += ` messages: [{ role: 'user', content: 'Hello!' }]
|
|
409
|
+
`;
|
|
410
|
+
content += ` }),
|
|
411
|
+
`;
|
|
412
|
+
} else {
|
|
413
|
+
content += ` () => yourLLMCall(),
|
|
414
|
+
`;
|
|
415
|
+
}
|
|
416
|
+
content += ` {
|
|
417
|
+
`;
|
|
418
|
+
content += ` apiKey: process.env.CONTINUM_API_KEY!,
|
|
419
|
+
`;
|
|
420
|
+
content += ` preset: '${config.productType}',
|
|
421
|
+
`;
|
|
422
|
+
if (config.frameworks.length > 0) {
|
|
423
|
+
content += ` comply: [${config.frameworks.map((f) => `'${f}'`).join(", ")}],
|
|
424
|
+
`;
|
|
425
|
+
}
|
|
426
|
+
if (config.alerts.enabled) {
|
|
427
|
+
content += ` alerts: {
|
|
428
|
+
`;
|
|
429
|
+
if (config.alerts.slack) {
|
|
430
|
+
content += ` slack: process.env.SLACK_WEBHOOK_URL,
|
|
431
|
+
`;
|
|
432
|
+
}
|
|
433
|
+
if (config.alerts.pagerduty) {
|
|
434
|
+
content += ` pagerduty: process.env.PAGERDUTY_KEY,
|
|
435
|
+
`;
|
|
436
|
+
}
|
|
437
|
+
if (config.alerts.discord) {
|
|
438
|
+
content += ` discord: process.env.DISCORD_WEBHOOK_URL,
|
|
439
|
+
`;
|
|
440
|
+
}
|
|
441
|
+
if (config.alerts.webhook) {
|
|
442
|
+
content += ` webhook: process.env.CUSTOM_WEBHOOK_URL,
|
|
443
|
+
`;
|
|
444
|
+
}
|
|
445
|
+
content += ` },
|
|
446
|
+
`;
|
|
447
|
+
}
|
|
448
|
+
content += ` }
|
|
449
|
+
`;
|
|
450
|
+
content += ` );
|
|
451
|
+
|
|
452
|
+
`;
|
|
453
|
+
if (config.providers.includes("openai")) {
|
|
454
|
+
content += ` console.log(response.choices[0].message.content);
|
|
455
|
+
`;
|
|
456
|
+
} else if (config.providers.includes("anthropic")) {
|
|
457
|
+
content += ` console.log(response.content[0].text);
|
|
458
|
+
`;
|
|
459
|
+
} else {
|
|
460
|
+
content += ` console.log(response);
|
|
461
|
+
`;
|
|
462
|
+
}
|
|
463
|
+
content += `}
|
|
464
|
+
|
|
465
|
+
`;
|
|
466
|
+
content += `// Example 2: Global configuration (recommended for production)
|
|
467
|
+
`;
|
|
468
|
+
content += `import { continum } from '@continum/sdk';
|
|
469
|
+
|
|
470
|
+
`;
|
|
471
|
+
content += `continum.configure({
|
|
472
|
+
`;
|
|
473
|
+
content += ` apiKey: process.env.CONTINUM_API_KEY!,
|
|
474
|
+
`;
|
|
475
|
+
content += ` preset: '${config.productType}',
|
|
476
|
+
`;
|
|
477
|
+
if (config.frameworks.length > 0) {
|
|
478
|
+
content += ` comply: [${config.frameworks.map((f) => `'${f}'`).join(", ")}],
|
|
479
|
+
`;
|
|
480
|
+
}
|
|
481
|
+
if (config.alerts.enabled) {
|
|
482
|
+
content += ` alerts: {
|
|
483
|
+
`;
|
|
484
|
+
if (config.alerts.slack) {
|
|
485
|
+
content += ` slack: process.env.SLACK_WEBHOOK_URL,
|
|
486
|
+
`;
|
|
487
|
+
}
|
|
488
|
+
if (config.alerts.pagerduty) {
|
|
489
|
+
content += ` pagerduty: process.env.PAGERDUTY_KEY,
|
|
490
|
+
`;
|
|
491
|
+
}
|
|
492
|
+
if (config.alerts.discord) {
|
|
493
|
+
content += ` discord: process.env.DISCORD_WEBHOOK_URL,
|
|
494
|
+
`;
|
|
495
|
+
}
|
|
496
|
+
if (config.alerts.webhook) {
|
|
497
|
+
content += ` webhook: process.env.CUSTOM_WEBHOOK_URL,
|
|
498
|
+
`;
|
|
499
|
+
}
|
|
500
|
+
content += ` },
|
|
501
|
+
`;
|
|
502
|
+
}
|
|
503
|
+
content += `});
|
|
504
|
+
|
|
505
|
+
`;
|
|
506
|
+
content += `async function example2() {
|
|
507
|
+
`;
|
|
508
|
+
content += ` // Now you can use protect() without passing config every time
|
|
509
|
+
`;
|
|
510
|
+
content += ` const response = await continum.protect(
|
|
511
|
+
`;
|
|
512
|
+
if (config.providers.includes("openai")) {
|
|
513
|
+
content += ` () => openai.chat.completions.create({
|
|
514
|
+
`;
|
|
515
|
+
content += ` model: 'gpt-4',
|
|
516
|
+
`;
|
|
517
|
+
content += ` messages: [{ role: 'user', content: 'Hello!' }]
|
|
518
|
+
`;
|
|
519
|
+
content += ` })
|
|
520
|
+
`;
|
|
521
|
+
} else {
|
|
522
|
+
content += ` () => yourLLMCall()
|
|
523
|
+
`;
|
|
524
|
+
}
|
|
525
|
+
content += ` );
|
|
526
|
+
`;
|
|
527
|
+
content += `}
|
|
528
|
+
|
|
529
|
+
`;
|
|
530
|
+
content += `// Example 3: Blocking mode (wait for audit before returning)
|
|
531
|
+
`;
|
|
532
|
+
content += `async function example3() {
|
|
533
|
+
`;
|
|
534
|
+
content += ` try {
|
|
535
|
+
`;
|
|
536
|
+
content += ` const response = await protect(
|
|
537
|
+
`;
|
|
538
|
+
if (config.providers.includes("openai")) {
|
|
539
|
+
content += ` () => openai.chat.completions.create({
|
|
540
|
+
`;
|
|
541
|
+
content += ` model: 'gpt-4',
|
|
542
|
+
`;
|
|
543
|
+
content += ` messages: [{ role: 'user', content: 'Process this payment' }]
|
|
544
|
+
`;
|
|
545
|
+
content += ` }),
|
|
546
|
+
`;
|
|
547
|
+
} else {
|
|
548
|
+
content += ` () => yourLLMCall(),
|
|
549
|
+
`;
|
|
550
|
+
}
|
|
551
|
+
content += ` {
|
|
552
|
+
`;
|
|
553
|
+
content += ` apiKey: process.env.CONTINUM_API_KEY!,
|
|
554
|
+
`;
|
|
555
|
+
content += ` blockOn: 'HIGH' // Block if risk level is HIGH or CRITICAL
|
|
556
|
+
`;
|
|
557
|
+
content += ` }
|
|
558
|
+
`;
|
|
559
|
+
content += ` );
|
|
560
|
+
`;
|
|
561
|
+
content += ` } catch (error) {
|
|
562
|
+
`;
|
|
563
|
+
content += ` if (error.name === 'ContinumBlockedError') {
|
|
564
|
+
`;
|
|
565
|
+
content += ` console.error('Blocked:', error.signal.violations);
|
|
566
|
+
`;
|
|
567
|
+
content += ` // Handle blocked request
|
|
568
|
+
`;
|
|
569
|
+
content += ` }
|
|
570
|
+
`;
|
|
571
|
+
content += ` }
|
|
572
|
+
`;
|
|
573
|
+
content += `}
|
|
574
|
+
`;
|
|
575
|
+
writeFileSync(filename, content);
|
|
576
|
+
}
|
|
577
|
+
async function installSDK() {
|
|
578
|
+
const { execSync } = await import("child_process");
|
|
579
|
+
if (!existsSync("package.json")) {
|
|
580
|
+
throw new Error('No package.json found. Please run "npm init" first.');
|
|
581
|
+
}
|
|
582
|
+
let packageManager = "npm";
|
|
583
|
+
if (existsSync("pnpm-lock.yaml")) {
|
|
584
|
+
packageManager = "pnpm";
|
|
585
|
+
} else if (existsSync("yarn.lock")) {
|
|
586
|
+
packageManager = "yarn";
|
|
587
|
+
} else if (existsSync("bun.lockb")) {
|
|
588
|
+
packageManager = "bun";
|
|
589
|
+
}
|
|
590
|
+
try {
|
|
591
|
+
if (packageManager === "npm") {
|
|
592
|
+
execSync("npm install @continum/sdk", { stdio: "ignore" });
|
|
593
|
+
} else if (packageManager === "pnpm") {
|
|
594
|
+
execSync("pnpm add @continum/sdk", { stdio: "ignore" });
|
|
595
|
+
} else if (packageManager === "yarn") {
|
|
596
|
+
execSync("yarn add @continum/sdk", { stdio: "ignore" });
|
|
597
|
+
} else if (packageManager === "bun") {
|
|
598
|
+
execSync("bun add @continum/sdk", { stdio: "ignore" });
|
|
599
|
+
}
|
|
600
|
+
} catch (error) {
|
|
601
|
+
throw new Error(`Failed to install @continum/sdk using ${packageManager}`);
|
|
602
|
+
}
|
|
603
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "continum",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Interactive CLI to set up Continum SDK in your project",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"continum": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
14
|
+
"dev": "tsup src/index.ts --format esm --watch",
|
|
15
|
+
"test": "vitest"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"continum",
|
|
19
|
+
"ai",
|
|
20
|
+
"compliance",
|
|
21
|
+
"llm",
|
|
22
|
+
"setup",
|
|
23
|
+
"init"
|
|
24
|
+
],
|
|
25
|
+
"author": "Continum",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@clack/prompts": "^0.7.0",
|
|
29
|
+
"picocolors": "^1.0.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^20.0.0",
|
|
33
|
+
"tsup": "^8.0.0",
|
|
34
|
+
"typescript": "^5.3.0",
|
|
35
|
+
"vitest": "^1.0.0"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18.0.0"
|
|
39
|
+
}
|
|
40
|
+
}
|