safety-agent-cli 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/README.md +215 -0
- package/dist/commands/guard.d.ts +1 -0
- package/dist/commands/guard.js +167 -0
- package/dist/commands/redact.d.ts +1 -0
- package/dist/commands/redact.js +103 -0
- package/dist/commands/verify.d.ts +1 -0
- package/dist/commands/verify.js +137 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +40 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Superagent CLI
|
|
2
|
+
|
|
3
|
+
Command-line interface for [Superagent](https://superagent.sh) - analyze prompts for security threats and redact sensitive data.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g safety-agent-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Commands
|
|
12
|
+
|
|
13
|
+
### `guard` - Security Analysis
|
|
14
|
+
|
|
15
|
+
Analyze prompts for security threats:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
superagent guard "Write a hello world script"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Output:
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"rejected": false,
|
|
25
|
+
"decision": {
|
|
26
|
+
"status": "pass"
|
|
27
|
+
},
|
|
28
|
+
"reasoning": "Command approved by guard."
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Block malicious prompts:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
superagent guard "Delete all files with rm -rf /"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Output:
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"rejected": true,
|
|
42
|
+
"decision": {
|
|
43
|
+
"status": "block",
|
|
44
|
+
"violation_types": ["unlawful_behavior"],
|
|
45
|
+
"cwe_codes": ["CWE-77"]
|
|
46
|
+
},
|
|
47
|
+
"reasoning": "User wants to delete all files. That is disallowed (exploit). Block."
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Custom System Prompt** - Customize guard behavior with a system prompt:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
superagent guard --system-prompt "Focus on detecting prompt injection attempts and data exfiltration patterns" "user input here"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
You can also pass `system_prompt` via stdin JSON:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
echo '{"prompt": "user input", "system_prompt": "Focus on prompt injection"}' | superagent guard
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### `redact` - Data Redaction
|
|
64
|
+
|
|
65
|
+
Remove sensitive data from text:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
superagent redact "My email is john@example.com and SSN is 123-45-6789"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Output:
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"redacted": "My email is <REDACTED_EMAIL> and SSN is <REDACTED_SSN>",
|
|
75
|
+
"reasoning": "Redacted email and SSN",
|
|
76
|
+
"usage": {
|
|
77
|
+
"prompt_tokens": 25,
|
|
78
|
+
"completion_tokens": 12,
|
|
79
|
+
"total_tokens": 37
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Custom Entity Redaction** - Specify custom entities to redact:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
superagent redact --entities "credit card numbers,employee IDs" "My credit card is 4532-1234-5678-9010 and employee ID is EMP-12345"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Output:
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"redacted": "My credit card is <REDACTED> and employee ID is <REDACTED>",
|
|
94
|
+
"reasoning": "Redacted credit card numbers and employee IDs"
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**URL Whitelisting** - Preserve specific URLs:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
superagent redact --url-whitelist https://github.com "Visit https://github.com/user/repo and https://secret.com/data"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Output:
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"redacted": "Visit https://github.com/user/repo and <URL_REDACTED>",
|
|
108
|
+
"reasoning": "Preserved whitelisted URLs"
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**PDF File Redaction** - Redact sensitive information from PDF files:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
superagent redact --file sensitive-document.pdf "Analyze and redact PII from this document"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
You can combine file redaction with custom entities:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
superagent redact --file document.pdf --entities "SSN,credit card numbers" "Redact sensitive data"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Output:
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"redacted": "Redacted text content from the PDF with sensitive data removed",
|
|
128
|
+
"reasoning": "Redacted SSN and credit card numbers from PDF document",
|
|
129
|
+
"usage": {
|
|
130
|
+
"prompt_tokens": 150,
|
|
131
|
+
"completion_tokens": 45,
|
|
132
|
+
"total_tokens": 195
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Note:** File redaction currently supports PDF format only.
|
|
138
|
+
|
|
139
|
+
## Help
|
|
140
|
+
|
|
141
|
+
Get help for any command:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
superagent --help
|
|
145
|
+
superagent guard --help
|
|
146
|
+
superagent redact --help
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Claude Code Hook
|
|
150
|
+
|
|
151
|
+
Validate all prompts before Claude processes them by adding a hook to your `~/.claude/settings.json`:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"env": {
|
|
156
|
+
"SUPERAGENT_API_KEY": "your_api_key_here"
|
|
157
|
+
},
|
|
158
|
+
"hooks": {
|
|
159
|
+
"UserPromptSubmit": [
|
|
160
|
+
{
|
|
161
|
+
"matcher": "*",
|
|
162
|
+
"hooks": [
|
|
163
|
+
{
|
|
164
|
+
"type": "command",
|
|
165
|
+
"command": "superagent guard"
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The CLI will:
|
|
175
|
+
- ✅ Allow safe prompts to proceed
|
|
176
|
+
- 🛡️ Block malicious prompts with detailed reasoning
|
|
177
|
+
- 🔍 Show violation types and CWE codes for blocked prompts
|
|
178
|
+
|
|
179
|
+
### Environment Variables
|
|
180
|
+
|
|
181
|
+
- `SUPERAGENT_API_KEY` - Your Superagent API key (required)
|
|
182
|
+
|
|
183
|
+
Get your API key at [app.superagent.sh](https://app.superagent.sh)
|
|
184
|
+
|
|
185
|
+
## How It Works
|
|
186
|
+
|
|
187
|
+
The CLI uses [Superagent](https://superagent.sh) to analyze prompts for:
|
|
188
|
+
|
|
189
|
+
- **Security vulnerabilities** (SQL injection, command injection, etc.)
|
|
190
|
+
- **Malicious intent** (data destruction, unauthorized access)
|
|
191
|
+
- **Privacy violations** (credential exposure, PII leaks)
|
|
192
|
+
- **CWE violations** (Common Weakness Enumeration codes)
|
|
193
|
+
|
|
194
|
+
When used as a Claude Code hook, it automatically:
|
|
195
|
+
1. Receives the user's prompt via stdin
|
|
196
|
+
2. Sends it to Superagent for analysis
|
|
197
|
+
3. Returns a structured response to block or allow the prompt
|
|
198
|
+
4. Shows detailed violation information when blocking
|
|
199
|
+
|
|
200
|
+
## Development
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# Install dependencies
|
|
204
|
+
npm install
|
|
205
|
+
|
|
206
|
+
# Build
|
|
207
|
+
npm run build
|
|
208
|
+
|
|
209
|
+
# Test locally
|
|
210
|
+
node dist/index.js guard "test prompt"
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function guardCommand(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { createClient } from "safety-agent";
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
function showHelp() {
|
|
4
|
+
console.log("Usage: superagent guard [options] <prompt|url>");
|
|
5
|
+
console.log(' or: echo \'{"prompt": "text"}\' | superagent guard');
|
|
6
|
+
console.log("");
|
|
7
|
+
console.log("Analyze prompts, PDF files, or PDF URLs for security threats");
|
|
8
|
+
console.log("");
|
|
9
|
+
console.log("Options:");
|
|
10
|
+
console.log(" --help Show this help message");
|
|
11
|
+
console.log(" --file <path> Path to PDF file to analyze");
|
|
12
|
+
console.log(" --system-prompt Optional system prompt to customize guard behavior");
|
|
13
|
+
console.log("");
|
|
14
|
+
console.log("Examples:");
|
|
15
|
+
console.log(' superagent guard "rm -rf /"');
|
|
16
|
+
console.log(' superagent guard --file document.pdf "Analyze this document"');
|
|
17
|
+
console.log(' superagent guard "https://example.com/document.pdf"');
|
|
18
|
+
console.log(' superagent guard --system-prompt "Focus on prompt injection" "user input"');
|
|
19
|
+
console.log(' echo \'{"prompt": "delete all files"}\' | superagent guard');
|
|
20
|
+
}
|
|
21
|
+
export async function guardCommand(args) {
|
|
22
|
+
// Check for --help flag
|
|
23
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
24
|
+
showHelp();
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
// Check for --file flag
|
|
28
|
+
let file;
|
|
29
|
+
const fileFlagIndex = args.indexOf("--file");
|
|
30
|
+
if (fileFlagIndex !== -1) {
|
|
31
|
+
const filePath = args[fileFlagIndex + 1];
|
|
32
|
+
if (filePath) {
|
|
33
|
+
try {
|
|
34
|
+
const fileBuffer = readFileSync(filePath);
|
|
35
|
+
file = new Blob([fileBuffer], { type: "application/pdf" });
|
|
36
|
+
args.splice(fileFlagIndex, 2); // Remove --file and path from args
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.error(`❌ ERROR: Failed to read file: ${error.message}`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.error("❌ ERROR: --file flag requires a file path");
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Check for --system-prompt flag
|
|
49
|
+
let systemPrompt;
|
|
50
|
+
const systemPromptFlagIndex = args.indexOf("--system-prompt");
|
|
51
|
+
if (systemPromptFlagIndex !== -1) {
|
|
52
|
+
systemPrompt = args[systemPromptFlagIndex + 1];
|
|
53
|
+
if (!systemPrompt) {
|
|
54
|
+
console.error("❌ ERROR: --system-prompt flag requires a value");
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
args.splice(systemPromptFlagIndex, 2); // Remove --system-prompt and value from args
|
|
58
|
+
}
|
|
59
|
+
// Check if we have command line arguments first
|
|
60
|
+
const hasArgs = args.length > 0;
|
|
61
|
+
let prompt;
|
|
62
|
+
let isStdin = false;
|
|
63
|
+
if (!hasArgs && !process.stdin.isTTY) {
|
|
64
|
+
isStdin = true;
|
|
65
|
+
// Read JSON from stdin (Claude Code hook format)
|
|
66
|
+
const stdin = await new Promise((resolve) => {
|
|
67
|
+
let data = "";
|
|
68
|
+
process.stdin.on("data", (chunk) => (data += chunk));
|
|
69
|
+
process.stdin.on("end", () => resolve(data));
|
|
70
|
+
});
|
|
71
|
+
try {
|
|
72
|
+
const inputData = JSON.parse(stdin);
|
|
73
|
+
prompt = inputData.prompt;
|
|
74
|
+
// Also check for system_prompt in stdin JSON
|
|
75
|
+
if (inputData.system_prompt && !systemPrompt) {
|
|
76
|
+
systemPrompt = inputData.system_prompt;
|
|
77
|
+
}
|
|
78
|
+
if (!prompt) {
|
|
79
|
+
console.error("❌ ERROR: No prompt provided in stdin JSON");
|
|
80
|
+
process.exit(2);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
console.error("❌ ERROR: Failed to parse JSON from stdin");
|
|
85
|
+
process.exit(2);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// Command line argument
|
|
90
|
+
prompt = args.join(" ");
|
|
91
|
+
if (!prompt) {
|
|
92
|
+
console.error("Usage: superagent guard <prompt>");
|
|
93
|
+
console.error(' or: echo \'{"prompt": "text"}\' | superagent guard');
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Ensure API key is available
|
|
98
|
+
if (!process.env.SUPERAGENT_API_KEY) {
|
|
99
|
+
console.error("❌ ERROR: SUPERAGENT_API_KEY environment variable not set");
|
|
100
|
+
process.exit(2);
|
|
101
|
+
}
|
|
102
|
+
// Create client instance
|
|
103
|
+
const client = createClient({
|
|
104
|
+
apiKey: process.env.SUPERAGENT_API_KEY,
|
|
105
|
+
});
|
|
106
|
+
try {
|
|
107
|
+
// Pass file as first parameter if provided, otherwise pass prompt
|
|
108
|
+
const input = file || prompt;
|
|
109
|
+
const result = await client.guard({ input, systemPrompt });
|
|
110
|
+
const { classification, violation_types, cwe_codes, usage } = result;
|
|
111
|
+
const isBlocked = classification === "block";
|
|
112
|
+
if (isBlocked) {
|
|
113
|
+
if (isStdin) {
|
|
114
|
+
// Claude Code hook format
|
|
115
|
+
const violationInfo = violation_types?.length
|
|
116
|
+
? ` Violations: ${violation_types.join(", ")}.`
|
|
117
|
+
: "";
|
|
118
|
+
const cweInfo = cwe_codes?.length
|
|
119
|
+
? ` CWE: ${cwe_codes.join(", ")}.`
|
|
120
|
+
: "";
|
|
121
|
+
const response = {
|
|
122
|
+
decision: "block",
|
|
123
|
+
reason: `🛡️ Superagent Guard blocked this prompt.${violationInfo}${cweInfo}`,
|
|
124
|
+
hookSpecificOutput: {
|
|
125
|
+
hookEventName: "UserPromptSubmit",
|
|
126
|
+
additionalContext: `Blocked by Superagent Guard`,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
console.log(JSON.stringify(response));
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// CLI output - JSON format matching SDK
|
|
133
|
+
const output = {
|
|
134
|
+
classification,
|
|
135
|
+
violation_types,
|
|
136
|
+
cwe_codes,
|
|
137
|
+
usage,
|
|
138
|
+
};
|
|
139
|
+
console.log(JSON.stringify(output, null, 2));
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
if (!isStdin) {
|
|
145
|
+
// CLI output - JSON format matching SDK
|
|
146
|
+
const output = {
|
|
147
|
+
classification,
|
|
148
|
+
violation_types,
|
|
149
|
+
cwe_codes,
|
|
150
|
+
usage,
|
|
151
|
+
};
|
|
152
|
+
console.log(JSON.stringify(output, null, 2));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
process.exit(0);
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
console.error(`⚠️ Guard check failed: ${error.message}`);
|
|
159
|
+
if (isStdin) {
|
|
160
|
+
console.error("Allowing prompt to proceed...");
|
|
161
|
+
process.exit(0);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
process.exit(2);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function redactCommand(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createClient } from "safety-agent";
|
|
2
|
+
export async function redactCommand(args) {
|
|
3
|
+
// Check for --entities flag
|
|
4
|
+
const entitiesFlagIndex = args.indexOf("--entities");
|
|
5
|
+
let entities;
|
|
6
|
+
if (entitiesFlagIndex !== -1) {
|
|
7
|
+
// Get the value after --entities (comma-separated entities)
|
|
8
|
+
const entitiesValue = args[entitiesFlagIndex + 1];
|
|
9
|
+
if (entitiesValue) {
|
|
10
|
+
entities = entitiesValue.split(",").map((entity) => entity.trim());
|
|
11
|
+
// Remove --entities and its value from args
|
|
12
|
+
args.splice(entitiesFlagIndex, 2);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
console.error("❌ ERROR: --entities requires a comma-separated list of entity types");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// Check for --rewrite flag
|
|
20
|
+
const rewriteFlagIndex = args.indexOf("--rewrite");
|
|
21
|
+
let rewrite;
|
|
22
|
+
if (rewriteFlagIndex !== -1) {
|
|
23
|
+
rewrite = true;
|
|
24
|
+
// Remove --rewrite from args
|
|
25
|
+
args.splice(rewriteFlagIndex, 1);
|
|
26
|
+
}
|
|
27
|
+
// Check if we have command line arguments first
|
|
28
|
+
const hasArgs = args.length > 0;
|
|
29
|
+
let text;
|
|
30
|
+
let isStdin = false;
|
|
31
|
+
if (!hasArgs && !process.stdin.isTTY) {
|
|
32
|
+
isStdin = true;
|
|
33
|
+
// Read JSON from stdin
|
|
34
|
+
const stdin = await new Promise((resolve) => {
|
|
35
|
+
let data = "";
|
|
36
|
+
process.stdin.on("data", (chunk) => (data += chunk));
|
|
37
|
+
process.stdin.on("end", () => resolve(data));
|
|
38
|
+
});
|
|
39
|
+
try {
|
|
40
|
+
const inputData = JSON.parse(stdin);
|
|
41
|
+
text = inputData.text || inputData.prompt;
|
|
42
|
+
// Check for rewrite in stdin JSON
|
|
43
|
+
if (inputData.rewrite !== undefined) {
|
|
44
|
+
rewrite = Boolean(inputData.rewrite);
|
|
45
|
+
}
|
|
46
|
+
if (!text) {
|
|
47
|
+
console.error("❌ ERROR: No text provided in stdin JSON");
|
|
48
|
+
process.exit(2);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.error("❌ ERROR: Failed to parse JSON from stdin");
|
|
53
|
+
process.exit(2);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Command line argument
|
|
58
|
+
text = args.join(" ");
|
|
59
|
+
if (!text) {
|
|
60
|
+
console.error("Usage: superagent redact [--entities <entity1,entity2>] [--rewrite] <text>");
|
|
61
|
+
console.error(' or: echo \'{"text": "..."}\' | superagent redact [--entities <entity1,entity2>] [--rewrite]');
|
|
62
|
+
console.error("");
|
|
63
|
+
console.error("Options:");
|
|
64
|
+
console.error(" --entities <entities> Comma-separated list of entity types to redact");
|
|
65
|
+
console.error(" --rewrite Naturally rewrite content instead of using placeholders");
|
|
66
|
+
console.error("");
|
|
67
|
+
console.error("Examples:");
|
|
68
|
+
console.error(' superagent redact "My email is john@example.com"');
|
|
69
|
+
console.error(' superagent redact --entities "emails,phones" "Contact: john@example.com, 555-1234"');
|
|
70
|
+
console.error(' superagent redact --rewrite "Contact me at john@example.com"');
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Ensure API key is available
|
|
75
|
+
if (!process.env.SUPERAGENT_API_KEY) {
|
|
76
|
+
console.error("❌ ERROR: SUPERAGENT_API_KEY environment variable not set");
|
|
77
|
+
process.exit(2);
|
|
78
|
+
}
|
|
79
|
+
// Create client instance
|
|
80
|
+
const client = createClient({
|
|
81
|
+
apiKey: process.env.SUPERAGENT_API_KEY,
|
|
82
|
+
});
|
|
83
|
+
try {
|
|
84
|
+
const result = await client.redact({
|
|
85
|
+
input: text,
|
|
86
|
+
model: "openai/gpt-4o-mini",
|
|
87
|
+
entities,
|
|
88
|
+
rewrite,
|
|
89
|
+
});
|
|
90
|
+
// JSON output
|
|
91
|
+
const output = {
|
|
92
|
+
redacted: result.redacted,
|
|
93
|
+
findings: result.findings,
|
|
94
|
+
usage: result.usage,
|
|
95
|
+
};
|
|
96
|
+
console.log(JSON.stringify(output, null, 2));
|
|
97
|
+
process.exit(0);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
console.error(`⚠️ Redact failed: ${error.message}`);
|
|
101
|
+
process.exit(2);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function verifyCommand(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { createClient } from 'superagent-ai';
|
|
2
|
+
function showHelp() {
|
|
3
|
+
console.log('Usage: superagent verify [options] <text>');
|
|
4
|
+
console.log(' or: echo \'{"text": "...", "sources": [...]}\' | superagent verify');
|
|
5
|
+
console.log('');
|
|
6
|
+
console.log('Verify claims in text against source materials');
|
|
7
|
+
console.log('');
|
|
8
|
+
console.log('Options:');
|
|
9
|
+
console.log(' --help Show this help message');
|
|
10
|
+
console.log(' --sources <json> JSON string containing array of sources');
|
|
11
|
+
console.log('');
|
|
12
|
+
console.log('Source format:');
|
|
13
|
+
console.log(' [{"name": "Source Name", "content": "...", "url": "https://..."}]');
|
|
14
|
+
console.log('');
|
|
15
|
+
console.log('Examples:');
|
|
16
|
+
console.log(' superagent verify --sources \'[{"name":"About","content":"Founded in 2020"}]\' "The company was founded in 2020"');
|
|
17
|
+
console.log(' echo \'{"text": "...", "sources": [...]}\' | superagent verify');
|
|
18
|
+
}
|
|
19
|
+
export async function verifyCommand(args) {
|
|
20
|
+
// Check for --help flag
|
|
21
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
22
|
+
showHelp();
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
// Check for --sources flag
|
|
26
|
+
let sources;
|
|
27
|
+
const sourcesFlagIndex = args.indexOf('--sources');
|
|
28
|
+
if (sourcesFlagIndex !== -1) {
|
|
29
|
+
const sourcesJson = args[sourcesFlagIndex + 1];
|
|
30
|
+
if (sourcesJson) {
|
|
31
|
+
try {
|
|
32
|
+
sources = JSON.parse(sourcesJson);
|
|
33
|
+
if (!Array.isArray(sources)) {
|
|
34
|
+
console.error('❌ ERROR: --sources must be a JSON array');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
// Remove --sources and its value from args
|
|
38
|
+
args.splice(sourcesFlagIndex, 2);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.error(`❌ ERROR: Failed to parse sources JSON: ${error.message}`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.error('❌ ERROR: --sources flag requires a JSON string');
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Check if we have command line arguments first
|
|
51
|
+
const hasArgs = args.length > 0;
|
|
52
|
+
let text;
|
|
53
|
+
let isStdin = false;
|
|
54
|
+
if (!hasArgs && !process.stdin.isTTY) {
|
|
55
|
+
isStdin = true;
|
|
56
|
+
// Read JSON from stdin
|
|
57
|
+
const stdin = await new Promise((resolve) => {
|
|
58
|
+
let data = '';
|
|
59
|
+
process.stdin.on('data', chunk => data += chunk);
|
|
60
|
+
process.stdin.on('end', () => resolve(data));
|
|
61
|
+
});
|
|
62
|
+
try {
|
|
63
|
+
const inputData = JSON.parse(stdin);
|
|
64
|
+
text = inputData.text;
|
|
65
|
+
// Get sources from stdin if not provided via command line
|
|
66
|
+
if (!sources && inputData.sources) {
|
|
67
|
+
sources = inputData.sources;
|
|
68
|
+
}
|
|
69
|
+
if (!text) {
|
|
70
|
+
console.error('❌ ERROR: No text provided in stdin JSON');
|
|
71
|
+
process.exit(2);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
console.error('❌ ERROR: Failed to parse JSON from stdin');
|
|
76
|
+
process.exit(2);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// Command line argument
|
|
81
|
+
text = args.join(' ');
|
|
82
|
+
if (!text) {
|
|
83
|
+
showHelp();
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Validate sources
|
|
88
|
+
if (!sources || !Array.isArray(sources) || sources.length === 0) {
|
|
89
|
+
console.error('❌ ERROR: sources are required. Use --sources flag or provide via stdin');
|
|
90
|
+
console.error('');
|
|
91
|
+
showHelp();
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
// Validate each source
|
|
95
|
+
for (const source of sources) {
|
|
96
|
+
if (!source.content || typeof source.content !== 'string') {
|
|
97
|
+
console.error('❌ ERROR: Each source must have a "content" field (string)');
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
if (!source.name || typeof source.name !== 'string') {
|
|
101
|
+
console.error('❌ ERROR: Each source must have a "name" field (string)');
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
if (source.url !== undefined && typeof source.url !== 'string') {
|
|
105
|
+
console.error('❌ ERROR: If provided, "url" must be a string');
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Ensure API key is available
|
|
110
|
+
if (!process.env.SUPERAGENT_API_KEY) {
|
|
111
|
+
console.error('❌ ERROR: SUPERAGENT_API_KEY environment variable not set');
|
|
112
|
+
process.exit(2);
|
|
113
|
+
}
|
|
114
|
+
// Create client instance
|
|
115
|
+
const client = createClient({
|
|
116
|
+
apiKey: process.env.SUPERAGENT_API_KEY,
|
|
117
|
+
});
|
|
118
|
+
try {
|
|
119
|
+
const result = await client.verify(text, sources);
|
|
120
|
+
const output = {
|
|
121
|
+
claims: result.claims,
|
|
122
|
+
usage: result.usage,
|
|
123
|
+
};
|
|
124
|
+
console.log(JSON.stringify(output, null, 2));
|
|
125
|
+
process.exit(0);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
console.error(`⚠️ Verify failed: ${error.message}`);
|
|
129
|
+
if (isStdin) {
|
|
130
|
+
console.error('Allowing to proceed...');
|
|
131
|
+
process.exit(0);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
process.exit(2);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { guardCommand } from './commands/guard.js';
|
|
3
|
+
import { redactCommand } from './commands/redact.js';
|
|
4
|
+
function showHelp() {
|
|
5
|
+
console.log('Usage: superagent <command> [options]');
|
|
6
|
+
console.log('');
|
|
7
|
+
console.log('AI security and privacy toolkit');
|
|
8
|
+
console.log('');
|
|
9
|
+
console.log('Commands:');
|
|
10
|
+
console.log(' guard Analyze prompts for security threats');
|
|
11
|
+
console.log(' redact Remove sensitive data from text');
|
|
12
|
+
console.log('');
|
|
13
|
+
console.log('Options:');
|
|
14
|
+
console.log(' --help Show help for a command');
|
|
15
|
+
console.log('');
|
|
16
|
+
console.log('Examples:');
|
|
17
|
+
console.log(' superagent guard --help');
|
|
18
|
+
console.log(' superagent redact --help');
|
|
19
|
+
console.log(' superagent guard "rm -rf /"');
|
|
20
|
+
console.log(' superagent redact "My email is john@example.com"');
|
|
21
|
+
}
|
|
22
|
+
const args = process.argv.slice(2);
|
|
23
|
+
const command = args[0];
|
|
24
|
+
if (!command || command === '--help' || command === '-h') {
|
|
25
|
+
showHelp();
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
switch (command) {
|
|
29
|
+
case 'guard':
|
|
30
|
+
await guardCommand(args.slice(1));
|
|
31
|
+
break;
|
|
32
|
+
case 'redact':
|
|
33
|
+
await redactCommand(args.slice(1));
|
|
34
|
+
break;
|
|
35
|
+
default:
|
|
36
|
+
console.error(`Unknown command: ${command}`);
|
|
37
|
+
console.error('');
|
|
38
|
+
showHelp();
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "safety-agent-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for Superagent - validate prompts and tool calls for security",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"superagent": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"dev": "tsc --watch",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"superagent",
|
|
22
|
+
"guard",
|
|
23
|
+
"cli",
|
|
24
|
+
"security",
|
|
25
|
+
"claude-code",
|
|
26
|
+
"hooks",
|
|
27
|
+
"ai-safety"
|
|
28
|
+
],
|
|
29
|
+
"author": "Superagent",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/superagent-ai/superagent"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^20.11.0",
|
|
37
|
+
"dotenv": "^16.3.1",
|
|
38
|
+
"typescript": "^5.4.0",
|
|
39
|
+
"vitest": "^2.1.0"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"safety-agent": "^0.1.0"
|
|
46
|
+
}
|
|
47
|
+
}
|