sapper-ai 0.2.1
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 +21 -0
- package/README.md +30 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +213 -0
- package/dist/createGuard.d.ts +17 -0
- package/dist/createGuard.d.ts.map +1 -0
- package/dist/createGuard.js +83 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/postinstall.d.ts +2 -0
- package/dist/postinstall.d.ts.map +1 -0
- package/dist/postinstall.js +14 -0
- package/dist/presets.d.ts +9 -0
- package/dist/presets.d.ts.map +1 -0
- package/dist/presets.js +71 -0
- package/dist/scan.d.ts +6 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +201 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SapperAI
|
|
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,30 @@
|
|
|
1
|
+
# sapper-ai
|
|
2
|
+
|
|
3
|
+
Single-install AI security guardrails for tool-calling systems.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add sapper-ai
|
|
9
|
+
npx sapper-ai init
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { createGuard } from 'sapper-ai'
|
|
14
|
+
|
|
15
|
+
const guard = createGuard()
|
|
16
|
+
const decision = await guard.check({ toolName: 'shell', arguments: { cmd: 'ls' } })
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Presets
|
|
20
|
+
|
|
21
|
+
| Preset | Description |
|
|
22
|
+
|---|---|
|
|
23
|
+
| monitor | Monitor only - logs threats but never blocks |
|
|
24
|
+
| standard | Balanced protection with sensible defaults |
|
|
25
|
+
| strict | Strict enforcement with lower thresholds |
|
|
26
|
+
| paranoid | Maximum security - aggressive blocking, fail closed, LLM analysis |
|
|
27
|
+
| ci | CI/CD pipeline - deterministic, fail closed, no LLM |
|
|
28
|
+
| development | Development mode - permissive, monitor only |
|
|
29
|
+
|
|
30
|
+
More details: https://github.com/sapper-ai/sapperai#readme
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAUA,wBAAsB,MAAM,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,OAAO,CAAC,MAAM,CAAC,CA2BpF"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.runCli = runCli;
|
|
38
|
+
const node_fs_1 = require("node:fs");
|
|
39
|
+
const node_child_process_1 = require("node:child_process");
|
|
40
|
+
const node_path_1 = require("node:path");
|
|
41
|
+
const readline = __importStar(require("node:readline"));
|
|
42
|
+
const presets_1 = require("./presets");
|
|
43
|
+
const scan_1 = require("./scan");
|
|
44
|
+
async function runCli(argv = process.argv.slice(2)) {
|
|
45
|
+
if (argv[0] === '--help' || argv[0] === '-h') {
|
|
46
|
+
printUsage();
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
if (argv[0] === 'scan') {
|
|
50
|
+
const parsed = parseScanArgs(argv.slice(1));
|
|
51
|
+
if (!parsed) {
|
|
52
|
+
printUsage();
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
return (0, scan_1.runScan)(parsed);
|
|
56
|
+
}
|
|
57
|
+
if (argv[0] === 'dashboard') {
|
|
58
|
+
return runDashboard();
|
|
59
|
+
}
|
|
60
|
+
if (argv[0] !== 'init') {
|
|
61
|
+
printUsage();
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
await runInitWizard();
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
function printUsage() {
|
|
68
|
+
console.log(`
|
|
69
|
+
sapper-ai - AI security guardrails
|
|
70
|
+
|
|
71
|
+
Usage:
|
|
72
|
+
sapper-ai scan Scan environment for threats
|
|
73
|
+
sapper-ai scan --fix Scan and quarantine blocked files
|
|
74
|
+
sapper-ai init Interactive setup wizard
|
|
75
|
+
sapper-ai dashboard Launch web dashboard
|
|
76
|
+
sapper-ai --help Show this help
|
|
77
|
+
|
|
78
|
+
Learn more: https://github.com/sapper-ai/sapperai
|
|
79
|
+
`);
|
|
80
|
+
}
|
|
81
|
+
function parseScanArgs(argv) {
|
|
82
|
+
const targets = [];
|
|
83
|
+
let fix = false;
|
|
84
|
+
for (const arg of argv) {
|
|
85
|
+
if (arg === '--fix') {
|
|
86
|
+
fix = true;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (arg.startsWith('-')) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
targets.push(arg);
|
|
93
|
+
}
|
|
94
|
+
return { targets, fix };
|
|
95
|
+
}
|
|
96
|
+
async function runDashboard() {
|
|
97
|
+
const configuredPort = process.env.PORT;
|
|
98
|
+
const standalonePort = configuredPort ?? '4100';
|
|
99
|
+
const devPort = configuredPort ?? '3000';
|
|
100
|
+
try {
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
102
|
+
const startPath = require.resolve('@sapper-ai/dashboard/bin/start');
|
|
103
|
+
process.env.PORT = standalonePort;
|
|
104
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
105
|
+
require(startPath);
|
|
106
|
+
return await new Promise((resolveExit) => {
|
|
107
|
+
const stop = () => resolveExit(0);
|
|
108
|
+
process.once('SIGINT', stop);
|
|
109
|
+
process.once('SIGTERM', stop);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
}
|
|
114
|
+
const webDir = (0, node_path_1.resolve)(__dirname, '../../../apps/web');
|
|
115
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.resolve)(webDir, 'package.json'))) {
|
|
116
|
+
console.log(`\n SapperAI Dashboard (dev): http://localhost:${devPort}/dashboard\n`);
|
|
117
|
+
console.log(' Press Ctrl+C to stop\n');
|
|
118
|
+
const child = (0, node_child_process_1.spawn)('npx', ['next', 'dev', '--port', devPort], {
|
|
119
|
+
cwd: webDir,
|
|
120
|
+
stdio: 'inherit',
|
|
121
|
+
env: process.env,
|
|
122
|
+
});
|
|
123
|
+
process.on('SIGINT', () => child.kill('SIGINT'));
|
|
124
|
+
process.on('SIGTERM', () => child.kill('SIGTERM'));
|
|
125
|
+
return await new Promise((resolveExit) => {
|
|
126
|
+
child.on('close', (code) => resolveExit(code ?? 0));
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
console.error('\n Install @sapper-ai/dashboard for standalone mode:');
|
|
130
|
+
console.error(' pnpm add @sapper-ai/dashboard\n');
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
133
|
+
async function runInitWizard() {
|
|
134
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
135
|
+
const ask = (q) => new Promise((res) => rl.question(q, res));
|
|
136
|
+
console.log('\n SapperAI Setup\n');
|
|
137
|
+
console.log(' Available presets:\n');
|
|
138
|
+
const entries = Object.entries(presets_1.presets);
|
|
139
|
+
entries.forEach(([key, p], i) => {
|
|
140
|
+
const marker = key === 'standard' ? ' (default)' : '';
|
|
141
|
+
console.log(` ${i + 1}) ${key}${marker} - ${p.description}`);
|
|
142
|
+
});
|
|
143
|
+
console.log();
|
|
144
|
+
const presetAnswer = await ask(` Choose preset [1-${entries.length}] (default: 2): `);
|
|
145
|
+
const presetIndex = (Number.parseInt(presetAnswer, 10) || 2) - 1;
|
|
146
|
+
const clampedIndex = Math.max(0, Math.min(presetIndex, entries.length - 1));
|
|
147
|
+
const selectedPreset = entries[clampedIndex][0];
|
|
148
|
+
const auditAnswer = await ask(' Audit log file path (enter to skip): ');
|
|
149
|
+
const auditLogPath = auditAnswer.trim() || undefined;
|
|
150
|
+
const outputPath = (0, node_path_1.resolve)(process.cwd(), 'sapperai.config.yaml');
|
|
151
|
+
if ((0, node_fs_1.existsSync)(outputPath)) {
|
|
152
|
+
const overwrite = await ask(' sapperai.config.yaml exists. Overwrite? [y/N]: ');
|
|
153
|
+
if (overwrite.trim().toLowerCase() !== 'y') {
|
|
154
|
+
console.log(' Aborted.');
|
|
155
|
+
rl.close();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const lines = [
|
|
160
|
+
'# SapperAI Configuration',
|
|
161
|
+
'# Generated by: sapper-ai init',
|
|
162
|
+
'# Docs: https://github.com/sapper-ai/sapperai',
|
|
163
|
+
'',
|
|
164
|
+
...buildPolicyYaml(selectedPreset, auditLogPath),
|
|
165
|
+
];
|
|
166
|
+
(0, node_fs_1.writeFileSync)(outputPath, `${lines.join('\n')}\n`, 'utf8');
|
|
167
|
+
console.log(`\n Created ${outputPath}\n`);
|
|
168
|
+
console.log(' Quick start:\n');
|
|
169
|
+
console.log(" import { createGuard } from 'sapper-ai'");
|
|
170
|
+
console.log(' const guard = createGuard()');
|
|
171
|
+
console.log(' const decision = await guard.check(toolCall)');
|
|
172
|
+
console.log();
|
|
173
|
+
rl.close();
|
|
174
|
+
}
|
|
175
|
+
function buildPolicyYaml(preset, auditLogPath) {
|
|
176
|
+
const p = presets_1.presets[preset].policy;
|
|
177
|
+
const lines = [];
|
|
178
|
+
lines.push(`mode: ${p.mode}`);
|
|
179
|
+
lines.push(`defaultAction: ${p.defaultAction}`);
|
|
180
|
+
lines.push(`failOpen: ${p.failOpen}`);
|
|
181
|
+
lines.push('');
|
|
182
|
+
lines.push('detectors:');
|
|
183
|
+
const detectors = p.detectors ?? ['rules'];
|
|
184
|
+
for (const d of detectors) {
|
|
185
|
+
lines.push(` - ${d}`);
|
|
186
|
+
}
|
|
187
|
+
lines.push('');
|
|
188
|
+
lines.push('thresholds:');
|
|
189
|
+
const thresholds = p.thresholds ?? {};
|
|
190
|
+
lines.push(` riskThreshold: ${thresholds.riskThreshold ?? 0.7}`);
|
|
191
|
+
lines.push(` blockMinConfidence: ${thresholds.blockMinConfidence ?? 0.5}`);
|
|
192
|
+
if (auditLogPath) {
|
|
193
|
+
lines.push('');
|
|
194
|
+
lines.push(`auditLogPath: ${auditLogPath}`);
|
|
195
|
+
}
|
|
196
|
+
return lines;
|
|
197
|
+
}
|
|
198
|
+
function isDirectExecution(argv) {
|
|
199
|
+
const entry = argv[1];
|
|
200
|
+
if (!entry) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
return entry.endsWith('/cli.js') || entry.endsWith('\\cli.js') || entry.endsWith('/cli.ts');
|
|
204
|
+
}
|
|
205
|
+
if (isDirectExecution(process.argv)) {
|
|
206
|
+
runCli().then((exitCode) => {
|
|
207
|
+
process.exit(exitCode);
|
|
208
|
+
}, (error) => {
|
|
209
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
210
|
+
console.error(msg);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Guard } from '@sapper-ai/core';
|
|
2
|
+
import type { Decision, Policy, ToolCall, ToolResult } from '@sapper-ai/types';
|
|
3
|
+
import { type PresetName } from './presets';
|
|
4
|
+
export interface CreateGuardOptions {
|
|
5
|
+
preset?: PresetName;
|
|
6
|
+
policy?: Partial<Policy>;
|
|
7
|
+
configPath?: string;
|
|
8
|
+
auditLogPath?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface SapperGuard {
|
|
11
|
+
check(toolCall: ToolCall): Promise<Decision>;
|
|
12
|
+
checkResult(toolCall: ToolCall, toolResult: ToolResult): Promise<Decision>;
|
|
13
|
+
guard: Guard;
|
|
14
|
+
policy: Policy;
|
|
15
|
+
}
|
|
16
|
+
export declare function createGuard(options?: CreateGuardOptions | PresetName): SapperGuard;
|
|
17
|
+
//# sourceMappingURL=createGuard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createGuard.d.ts","sourceRoot":"","sources":["../src/createGuard.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgD,KAAK,EAAiB,MAAM,iBAAiB,CAAA;AACpG,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE9E,OAAO,EAAW,KAAK,UAAU,EAAE,MAAM,WAAW,CAAA;AAEpD,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;IACxB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC5C,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC1E,KAAK,EAAE,KAAK,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,UAAU,GAAG,WAAW,CAiBlF"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createGuard = createGuard;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const core_1 = require("@sapper-ai/core");
|
|
7
|
+
const presets_1 = require("./presets");
|
|
8
|
+
function createGuard(options) {
|
|
9
|
+
const opts = typeof options === 'string' ? { preset: options } : (options ?? {});
|
|
10
|
+
const policy = resolvePolicy(opts);
|
|
11
|
+
warnIfLlmMisconfigured(policy);
|
|
12
|
+
const detectors = (0, core_1.createDetectors)({ policy });
|
|
13
|
+
const engine = new core_1.DecisionEngine(detectors);
|
|
14
|
+
const logger = opts.auditLogPath ? new core_1.AuditLogger({ filePath: opts.auditLogPath }) : new core_1.AuditLogger();
|
|
15
|
+
const guard = new core_1.Guard(engine, logger, policy);
|
|
16
|
+
return {
|
|
17
|
+
check: (toolCall) => guard.preTool(toolCall),
|
|
18
|
+
checkResult: (toolCall, toolResult) => guard.postTool(toolCall, toolResult),
|
|
19
|
+
guard,
|
|
20
|
+
policy,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function resolvePolicy(opts) {
|
|
24
|
+
const base = resolveBasePolicy(opts);
|
|
25
|
+
if (!opts.policy) {
|
|
26
|
+
return base;
|
|
27
|
+
}
|
|
28
|
+
return mergePolicy(base, opts.policy);
|
|
29
|
+
}
|
|
30
|
+
function resolveBasePolicy(opts) {
|
|
31
|
+
if (opts.preset) {
|
|
32
|
+
const preset = presets_1.presets[opts.preset];
|
|
33
|
+
if (!preset) {
|
|
34
|
+
const valid = Object.keys(presets_1.presets).join(', ');
|
|
35
|
+
throw new Error(`Unknown preset "${opts.preset}". Valid presets: ${valid}`);
|
|
36
|
+
}
|
|
37
|
+
return { ...preset.policy };
|
|
38
|
+
}
|
|
39
|
+
const fromFile = loadConfigFile(opts.configPath);
|
|
40
|
+
return fromFile ?? { ...presets_1.presets.standard.policy };
|
|
41
|
+
}
|
|
42
|
+
function mergePolicy(base, override) {
|
|
43
|
+
const mergedThresholds = {
|
|
44
|
+
...(base.thresholds ?? {}),
|
|
45
|
+
...(override.thresholds ?? {}),
|
|
46
|
+
};
|
|
47
|
+
const hasThresholds = typeof mergedThresholds.riskThreshold === 'number' || typeof mergedThresholds.blockMinConfidence === 'number';
|
|
48
|
+
return {
|
|
49
|
+
...base,
|
|
50
|
+
...override,
|
|
51
|
+
thresholds: hasThresholds ? mergedThresholds : undefined,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function loadConfigFile(explicitPath) {
|
|
55
|
+
const configPath = explicitPath ?? findConfigFile();
|
|
56
|
+
if (!configPath)
|
|
57
|
+
return null;
|
|
58
|
+
if (!(0, node_fs_1.existsSync)(configPath))
|
|
59
|
+
return null;
|
|
60
|
+
try {
|
|
61
|
+
const manager = new core_1.PolicyManager();
|
|
62
|
+
return manager.loadFromFile(configPath);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
66
|
+
throw new Error(`Failed to load config from ${configPath}: ${message}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const CONFIG_FILE_NAMES = ['sapperai.config.yaml', 'sapperai.config.yml'];
|
|
70
|
+
function findConfigFile() {
|
|
71
|
+
for (const name of CONFIG_FILE_NAMES) {
|
|
72
|
+
const fullPath = (0, node_path_1.resolve)(process.cwd(), name);
|
|
73
|
+
if ((0, node_fs_1.existsSync)(fullPath))
|
|
74
|
+
return fullPath;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
function warnIfLlmMisconfigured(policy) {
|
|
79
|
+
if (policy.detectors?.includes('llm') && !policy.llm) {
|
|
80
|
+
console.warn('[sapper-ai] Warning: Policy includes "llm" detector but no llm config provided. ' +
|
|
81
|
+
'LLM detector will be inactive. Set policy.llm to enable it.');
|
|
82
|
+
}
|
|
83
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createGuard, type CreateGuardOptions, type SapperGuard } from './createGuard';
|
|
2
|
+
export { presets, type Preset, type PresetName } from './presets';
|
|
3
|
+
export { Guard, DecisionEngine, RulesDetector, LlmDetector, ThreatIntelDetector, createDetectors, AuditLogger, PolicyManager, validatePolicy, Scanner, QuarantineManager, ThreatIntelStore, } from '@sapper-ai/core';
|
|
4
|
+
export type { Policy, ToolCall, ToolResult, ToolMetadata, ToolPolicy, Decision, DetectorOutput, Detector, AssessmentContext, AuditLogEntry, GuardAction, MatchList, ThreatFeedConfig, LlmConfig, } from '@sapper-ai/types';
|
|
5
|
+
export type { QuarantineRecord, ThreatIntelEntry, ThreatIntelSnapshot, IntelMatchList } from '@sapper-ai/core';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAA;AACtF,OAAO,EAAE,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAA;AAEjE,OAAO,EACL,KAAK,EACL,cAAc,EACd,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,eAAe,EACf,WAAW,EACX,aAAa,EACb,cAAc,EACd,OAAO,EACP,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,iBAAiB,CAAA;AAExB,YAAY,EACV,MAAM,EACN,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,cAAc,EACd,QAAQ,EACR,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,SAAS,EACT,gBAAgB,EAChB,SAAS,GACV,MAAM,kBAAkB,CAAA;AAEzB,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ThreatIntelStore = exports.QuarantineManager = exports.Scanner = exports.validatePolicy = exports.PolicyManager = exports.AuditLogger = exports.createDetectors = exports.ThreatIntelDetector = exports.LlmDetector = exports.RulesDetector = exports.DecisionEngine = exports.Guard = exports.presets = exports.createGuard = void 0;
|
|
4
|
+
var createGuard_1 = require("./createGuard");
|
|
5
|
+
Object.defineProperty(exports, "createGuard", { enumerable: true, get: function () { return createGuard_1.createGuard; } });
|
|
6
|
+
var presets_1 = require("./presets");
|
|
7
|
+
Object.defineProperty(exports, "presets", { enumerable: true, get: function () { return presets_1.presets; } });
|
|
8
|
+
var core_1 = require("@sapper-ai/core");
|
|
9
|
+
Object.defineProperty(exports, "Guard", { enumerable: true, get: function () { return core_1.Guard; } });
|
|
10
|
+
Object.defineProperty(exports, "DecisionEngine", { enumerable: true, get: function () { return core_1.DecisionEngine; } });
|
|
11
|
+
Object.defineProperty(exports, "RulesDetector", { enumerable: true, get: function () { return core_1.RulesDetector; } });
|
|
12
|
+
Object.defineProperty(exports, "LlmDetector", { enumerable: true, get: function () { return core_1.LlmDetector; } });
|
|
13
|
+
Object.defineProperty(exports, "ThreatIntelDetector", { enumerable: true, get: function () { return core_1.ThreatIntelDetector; } });
|
|
14
|
+
Object.defineProperty(exports, "createDetectors", { enumerable: true, get: function () { return core_1.createDetectors; } });
|
|
15
|
+
Object.defineProperty(exports, "AuditLogger", { enumerable: true, get: function () { return core_1.AuditLogger; } });
|
|
16
|
+
Object.defineProperty(exports, "PolicyManager", { enumerable: true, get: function () { return core_1.PolicyManager; } });
|
|
17
|
+
Object.defineProperty(exports, "validatePolicy", { enumerable: true, get: function () { return core_1.validatePolicy; } });
|
|
18
|
+
Object.defineProperty(exports, "Scanner", { enumerable: true, get: function () { return core_1.Scanner; } });
|
|
19
|
+
Object.defineProperty(exports, "QuarantineManager", { enumerable: true, get: function () { return core_1.QuarantineManager; } });
|
|
20
|
+
Object.defineProperty(exports, "ThreatIntelStore", { enumerable: true, get: function () { return core_1.ThreatIntelStore; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postinstall.d.ts","sourceRoot":"","sources":["../src/postinstall.ts"],"names":[],"mappings":"AAEA,wBAAgB,cAAc,IAAI,IAAI,CAKrC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runPostinstall = runPostinstall;
|
|
4
|
+
const MESSAGE = "SapperAI installed. Run 'npx sapper-ai scan' to check your environment.";
|
|
5
|
+
function runPostinstall() {
|
|
6
|
+
try {
|
|
7
|
+
console.log(MESSAGE);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
if (require.main === module) {
|
|
13
|
+
runPostinstall();
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Policy } from '@sapper-ai/types';
|
|
2
|
+
export interface Preset {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
policy: Policy;
|
|
6
|
+
}
|
|
7
|
+
export type PresetName = 'monitor' | 'standard' | 'strict' | 'paranoid' | 'ci' | 'development';
|
|
8
|
+
export declare const presets: Record<PresetName, Preset>;
|
|
9
|
+
//# sourceMappingURL=presets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../src/presets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAE9C,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,IAAI,GAAG,aAAa,CAAA;AAE9F,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAwE9C,CAAA"}
|
package/dist/presets.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.presets = void 0;
|
|
4
|
+
exports.presets = {
|
|
5
|
+
monitor: {
|
|
6
|
+
name: 'monitor',
|
|
7
|
+
description: 'Monitor only - logs threats but never blocks',
|
|
8
|
+
policy: {
|
|
9
|
+
mode: 'monitor',
|
|
10
|
+
defaultAction: 'allow',
|
|
11
|
+
failOpen: true,
|
|
12
|
+
detectors: ['rules'],
|
|
13
|
+
thresholds: { riskThreshold: 0.7, blockMinConfidence: 0.5 },
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
standard: {
|
|
17
|
+
name: 'standard',
|
|
18
|
+
description: 'Balanced protection with sensible defaults',
|
|
19
|
+
policy: {
|
|
20
|
+
mode: 'enforce',
|
|
21
|
+
defaultAction: 'allow',
|
|
22
|
+
failOpen: true,
|
|
23
|
+
detectors: ['rules'],
|
|
24
|
+
thresholds: { riskThreshold: 0.7, blockMinConfidence: 0.5 },
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
strict: {
|
|
28
|
+
name: 'strict',
|
|
29
|
+
description: 'Strict enforcement with lower thresholds',
|
|
30
|
+
policy: {
|
|
31
|
+
mode: 'enforce',
|
|
32
|
+
defaultAction: 'allow',
|
|
33
|
+
failOpen: false,
|
|
34
|
+
detectors: ['rules'],
|
|
35
|
+
thresholds: { riskThreshold: 0.5, blockMinConfidence: 0.3 },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
paranoid: {
|
|
39
|
+
name: 'paranoid',
|
|
40
|
+
description: 'Maximum security - aggressive blocking, fail closed, LLM analysis',
|
|
41
|
+
policy: {
|
|
42
|
+
mode: 'enforce',
|
|
43
|
+
defaultAction: 'allow',
|
|
44
|
+
failOpen: false,
|
|
45
|
+
detectors: ['rules', 'llm'],
|
|
46
|
+
thresholds: { riskThreshold: 0.3, blockMinConfidence: 0.2 },
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
ci: {
|
|
50
|
+
name: 'ci',
|
|
51
|
+
description: 'CI/CD pipeline - deterministic, fail closed, no LLM',
|
|
52
|
+
policy: {
|
|
53
|
+
mode: 'enforce',
|
|
54
|
+
defaultAction: 'allow',
|
|
55
|
+
failOpen: false,
|
|
56
|
+
detectors: ['rules'],
|
|
57
|
+
thresholds: { riskThreshold: 0.7, blockMinConfidence: 0.5 },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
development: {
|
|
61
|
+
name: 'development',
|
|
62
|
+
description: 'Development mode - permissive, monitor only',
|
|
63
|
+
policy: {
|
|
64
|
+
mode: 'monitor',
|
|
65
|
+
defaultAction: 'allow',
|
|
66
|
+
failOpen: true,
|
|
67
|
+
detectors: ['rules'],
|
|
68
|
+
thresholds: { riskThreshold: 0.9, blockMinConfidence: 0.8 },
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
};
|
package/dist/scan.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,GAAG,CAAC,EAAE,OAAO,CAAA;CACd;AAqMD,wBAAsB,OAAO,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwDxE"}
|
package/dist/scan.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runScan = runScan;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const promises_1 = require("node:fs/promises");
|
|
6
|
+
const node_os_1 = require("node:os");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const core_1 = require("@sapper-ai/core");
|
|
9
|
+
const presets_1 = require("./presets");
|
|
10
|
+
const CONFIG_FILE_NAMES = ['sapperai.config.yaml', 'sapperai.config.yml'];
|
|
11
|
+
function findConfigFile(cwd) {
|
|
12
|
+
for (const name of CONFIG_FILE_NAMES) {
|
|
13
|
+
const fullPath = (0, node_path_1.resolve)(cwd, name);
|
|
14
|
+
if ((0, node_fs_1.existsSync)(fullPath)) {
|
|
15
|
+
return fullPath;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function resolvePolicy(cwd) {
|
|
21
|
+
const configPath = findConfigFile(cwd);
|
|
22
|
+
if (!configPath) {
|
|
23
|
+
return { ...presets_1.presets.standard.policy };
|
|
24
|
+
}
|
|
25
|
+
return new core_1.PolicyManager().loadFromFile(configPath);
|
|
26
|
+
}
|
|
27
|
+
function getThresholds(policy) {
|
|
28
|
+
const extended = policy;
|
|
29
|
+
const riskThreshold = typeof extended.thresholds?.riskThreshold === 'number' ? extended.thresholds.riskThreshold : 0.7;
|
|
30
|
+
const blockMinConfidence = typeof extended.thresholds?.blockMinConfidence === 'number' ? extended.thresholds.blockMinConfidence : 0.5;
|
|
31
|
+
return { riskThreshold, blockMinConfidence };
|
|
32
|
+
}
|
|
33
|
+
function toDefaultTargets(cwd) {
|
|
34
|
+
const home = (0, node_os_1.homedir)();
|
|
35
|
+
return [(0, node_path_1.join)(home, '.claude', 'plugins'), (0, node_path_1.join)(home, '.config', 'claude-code'), cwd];
|
|
36
|
+
}
|
|
37
|
+
function shouldSkipDir(dirName) {
|
|
38
|
+
const base = (0, node_path_1.basename)(dirName);
|
|
39
|
+
return base === 'node_modules' || base === '.git' || base === 'dist';
|
|
40
|
+
}
|
|
41
|
+
async function collectFiles(targetPath) {
|
|
42
|
+
try {
|
|
43
|
+
const info = await (0, promises_1.stat)(targetPath);
|
|
44
|
+
if (info.isFile()) {
|
|
45
|
+
return [targetPath];
|
|
46
|
+
}
|
|
47
|
+
if (!info.isDirectory()) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
const results = [];
|
|
55
|
+
const stack = [targetPath];
|
|
56
|
+
while (stack.length > 0) {
|
|
57
|
+
const current = stack.pop();
|
|
58
|
+
if (!current)
|
|
59
|
+
continue;
|
|
60
|
+
let entries;
|
|
61
|
+
try {
|
|
62
|
+
entries = await (0, promises_1.readdir)(current, { withFileTypes: true });
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
const fullPath = (0, node_path_1.join)(current, entry.name);
|
|
69
|
+
if (entry.isDirectory()) {
|
|
70
|
+
if (shouldSkipDir(fullPath)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
stack.push(fullPath);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (entry.isFile()) {
|
|
77
|
+
results.push(fullPath);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return results;
|
|
82
|
+
}
|
|
83
|
+
function isThreat(decision, policy) {
|
|
84
|
+
const { riskThreshold, blockMinConfidence } = getThresholds(policy);
|
|
85
|
+
return decision.risk >= riskThreshold && decision.confidence >= blockMinConfidence;
|
|
86
|
+
}
|
|
87
|
+
async function readFileIfPresent(filePath) {
|
|
88
|
+
try {
|
|
89
|
+
return await (0, promises_1.readFile)(filePath, 'utf8');
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function scanFile(filePath, policy, scanner, detectors, fix, quarantineManager) {
|
|
96
|
+
if (!(0, core_1.isConfigLikeFile)(filePath)) {
|
|
97
|
+
return { scanned: false };
|
|
98
|
+
}
|
|
99
|
+
const raw = await readFileIfPresent(filePath);
|
|
100
|
+
if (raw === null || raw.trim().length === 0) {
|
|
101
|
+
return { scanned: false };
|
|
102
|
+
}
|
|
103
|
+
const fileSurface = (0, core_1.normalizeSurfaceText)(raw);
|
|
104
|
+
const targetType = (0, core_1.classifyTargetType)(filePath);
|
|
105
|
+
const targets = [
|
|
106
|
+
{
|
|
107
|
+
id: `${targetType}:${(0, core_1.buildEntryName)(filePath)}`,
|
|
108
|
+
surface: fileSurface,
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
if (filePath.endsWith('.json')) {
|
|
112
|
+
try {
|
|
113
|
+
const parsed = JSON.parse(raw);
|
|
114
|
+
const mcpTargets = (0, core_1.collectMcpTargetsFromJson)(filePath, parsed);
|
|
115
|
+
for (const t of mcpTargets) {
|
|
116
|
+
targets.push({ id: `${t.type}:${t.name}`, surface: t.surface });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
let bestThreat = null;
|
|
123
|
+
for (const target of targets) {
|
|
124
|
+
const decision = await scanner.scanTool(target.id, target.surface, policy, detectors);
|
|
125
|
+
if (!isThreat(decision, policy)) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (!bestThreat || decision.risk > bestThreat.risk) {
|
|
129
|
+
bestThreat = decision;
|
|
130
|
+
}
|
|
131
|
+
if (fix && decision.action === 'block') {
|
|
132
|
+
try {
|
|
133
|
+
const record = await quarantineManager.quarantine(filePath, decision);
|
|
134
|
+
return { scanned: true, finding: { filePath, decision, quarantinedId: record.id } };
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (!bestThreat) {
|
|
141
|
+
return { scanned: true };
|
|
142
|
+
}
|
|
143
|
+
return { scanned: true, finding: { filePath, decision: bestThreat } };
|
|
144
|
+
}
|
|
145
|
+
function formatFindingLine(index, finding) {
|
|
146
|
+
const reason = finding.decision.reasons[0];
|
|
147
|
+
const label = reason?.startsWith('Detected pattern: ')
|
|
148
|
+
? reason.slice('Detected pattern: '.length)
|
|
149
|
+
: reason ?? 'threat';
|
|
150
|
+
const risk = finding.decision.risk.toFixed(2);
|
|
151
|
+
const quarantineSuffix = finding.quarantinedId ? ` (quarantined: ${finding.quarantinedId})` : '';
|
|
152
|
+
return ` ${index}. ${finding.filePath}\n Risk: ${risk} | ${label}${quarantineSuffix}`;
|
|
153
|
+
}
|
|
154
|
+
async function runScan(options = {}) {
|
|
155
|
+
const cwd = process.cwd();
|
|
156
|
+
const policy = resolvePolicy(cwd);
|
|
157
|
+
const fix = options.fix === true;
|
|
158
|
+
const targets = options.targets && options.targets.length > 0 ? options.targets : toDefaultTargets(cwd);
|
|
159
|
+
const scanner = new core_1.Scanner();
|
|
160
|
+
const detectors = (0, core_1.createDetectors)({ policy });
|
|
161
|
+
const quarantineDir = process.env.SAPPERAI_QUARANTINE_DIR;
|
|
162
|
+
const quarantineManager = quarantineDir ? new core_1.QuarantineManager({ quarantineDir }) : new core_1.QuarantineManager();
|
|
163
|
+
console.log('\n SapperAI Environment Scan\n');
|
|
164
|
+
console.log(' Scanning:');
|
|
165
|
+
for (const target of targets) {
|
|
166
|
+
console.log(` ${target}`);
|
|
167
|
+
}
|
|
168
|
+
console.log();
|
|
169
|
+
const fileSet = new Set();
|
|
170
|
+
for (const target of targets) {
|
|
171
|
+
const files = await collectFiles(target);
|
|
172
|
+
for (const f of files) {
|
|
173
|
+
fileSet.add(f);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
let scannedFiles = 0;
|
|
177
|
+
const findings = [];
|
|
178
|
+
for (const filePath of Array.from(fileSet).sort()) {
|
|
179
|
+
const result = await scanFile(filePath, policy, scanner, detectors, fix, quarantineManager);
|
|
180
|
+
if (result.scanned) {
|
|
181
|
+
scannedFiles += 1;
|
|
182
|
+
}
|
|
183
|
+
if (result.finding) {
|
|
184
|
+
findings.push(result.finding);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
console.log(' Results:');
|
|
188
|
+
console.log(` ${scannedFiles} files scanned, ${findings.length} threats detected`);
|
|
189
|
+
console.log();
|
|
190
|
+
if (findings.length > 0) {
|
|
191
|
+
findings.forEach((finding, idx) => {
|
|
192
|
+
console.log(formatFindingLine(idx + 1, finding));
|
|
193
|
+
console.log();
|
|
194
|
+
});
|
|
195
|
+
if (!fix) {
|
|
196
|
+
console.log(" Run 'npx sapper-ai scan --fix' to quarantine blocked files.");
|
|
197
|
+
console.log();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return findings.length > 0 ? 1 : 0;
|
|
201
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sapper-ai",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "AI security guardrails - single install, sensible defaults",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"security",
|
|
7
|
+
"ai",
|
|
8
|
+
"guardrails",
|
|
9
|
+
"prompt-injection",
|
|
10
|
+
"detection"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "SapperAI",
|
|
14
|
+
"homepage": "https://github.com/sapper-ai/core#readme",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/sapper-ai/core.git",
|
|
18
|
+
"directory": "packages/sapper-ai"
|
|
19
|
+
},
|
|
20
|
+
"main": "dist/index.js",
|
|
21
|
+
"types": "dist/index.d.ts",
|
|
22
|
+
"bin": {
|
|
23
|
+
"sapper-ai": "./dist/cli.js"
|
|
24
|
+
},
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./presets": {
|
|
31
|
+
"types": "./dist/presets.d.ts",
|
|
32
|
+
"default": "./dist/presets.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist"
|
|
37
|
+
],
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@sapper-ai/core": "0.2.0",
|
|
43
|
+
"@sapper-ai/types": "0.2.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@sapper-ai/mcp": ">=0.2.0",
|
|
47
|
+
"@sapper-ai/openai": ">=0.2.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"@sapper-ai/mcp": {
|
|
51
|
+
"optional": true
|
|
52
|
+
},
|
|
53
|
+
"@sapper-ai/openai": {
|
|
54
|
+
"optional": true
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "^20.0.0",
|
|
59
|
+
"typescript": "^5.3.0",
|
|
60
|
+
"vitest": "^1.0.0"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "tsc",
|
|
64
|
+
"postinstall": "node dist/postinstall.js || exit 0",
|
|
65
|
+
"test": "vitest --run"
|
|
66
|
+
}
|
|
67
|
+
}
|