@securevector/cli 5.3.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 +75 -0
- package/bin/securevector.js +153 -0
- package/lib/bootstrap.js +127 -0
- package/lib/python.js +108 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# SecureVector for npm
|
|
2
|
+
|
|
3
|
+
Security and observability for AI agents. This package is the launcher for
|
|
4
|
+
people whose toolchain is Node; the product itself is a Python application
|
|
5
|
+
published on PyPI, and this installs and runs the matching version.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @securevector/cli
|
|
9
|
+
securevector
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
or without installing anything globally:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx @securevector/cli
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## What happens when you install it
|
|
19
|
+
|
|
20
|
+
Nothing. `npm install` writes files and stops. It runs no install script and
|
|
21
|
+
makes no network request beyond fetching the package itself.
|
|
22
|
+
|
|
23
|
+
The first time you actually run `securevector`, it creates a virtual
|
|
24
|
+
environment under your own data directory and installs the matching release
|
|
25
|
+
from PyPI, telling you what it is doing as it does it. Every run after that
|
|
26
|
+
starts immediately.
|
|
27
|
+
|
|
28
|
+
That is deliberate. A postinstall script that downloads and executes code is
|
|
29
|
+
the exact supply-chain shape this product exists to warn people about. It would
|
|
30
|
+
be a strange thing for a security tool to ship, however convenient, so it does
|
|
31
|
+
not.
|
|
32
|
+
|
|
33
|
+
## Requirements
|
|
34
|
+
|
|
35
|
+
Python 3.10 or newer, on PATH. This package will not install a language runtime
|
|
36
|
+
for you. If Python is missing or too old, `securevector doctor` says so and
|
|
37
|
+
tells you what to do.
|
|
38
|
+
|
|
39
|
+
## Commands
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
securevector [app] [...] Start the local app (default)
|
|
43
|
+
securevector monitor [...] The sv-monitor CLI
|
|
44
|
+
securevector proxy [...] The LLM proxy
|
|
45
|
+
securevector mcp [...] The MCP server
|
|
46
|
+
securevector doctor Check this machine and report what is missing
|
|
47
|
+
securevector where Print the managed environment path
|
|
48
|
+
securevector --version
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Everything after the verb is passed through untouched, so
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
securevector monitor status
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
is exactly `sv-monitor status`.
|
|
58
|
+
|
|
59
|
+
## Versions
|
|
60
|
+
|
|
61
|
+
The npm version and the PyPI version are the same number for the same product.
|
|
62
|
+
`npm install @securevector/cli@5.3.0` gives you exactly what
|
|
63
|
+
`pip install securevector-ai-monitor==5.3.0` gives you. The launcher pins that
|
|
64
|
+
exact version and never resolves `latest`.
|
|
65
|
+
|
|
66
|
+
## Uninstalling
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm uninstall -g @securevector/cli
|
|
70
|
+
rm -rf "$(securevector where)" # run this first, it needs the command
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Licence
|
|
74
|
+
|
|
75
|
+
Apache-2.0. See the repository for the full text.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `securevector` for people who install things with npm.
|
|
6
|
+
*
|
|
7
|
+
* This is a launcher, not a reimplementation. It finds a usable Python, makes
|
|
8
|
+
* sure the matching PyPI release is installed in a virtual environment it
|
|
9
|
+
* manages, and then hands over. Everything the product does, it does in the
|
|
10
|
+
* Python process; nothing about behaviour is decided here.
|
|
11
|
+
*
|
|
12
|
+
* The one thing this file is opinionated about is that installing software is
|
|
13
|
+
* an explicit act. `npm install` of this package touches the network exactly
|
|
14
|
+
* zero times. The first real run says what it is going to do before it does it.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { spawn } = require('node:child_process');
|
|
18
|
+
const path = require('node:path');
|
|
19
|
+
|
|
20
|
+
const pkg = require('../package.json');
|
|
21
|
+
const { findPython, explainMissing, MIN_MAJOR, MIN_MINOR } = require('../lib/python');
|
|
22
|
+
const { isReady, install, venvBin, venvPath, envRoot } = require('../lib/bootstrap');
|
|
23
|
+
|
|
24
|
+
// The npm version and the PyPI version are the same product, so the launcher
|
|
25
|
+
// pins to its own version rather than resolving "latest". Installing
|
|
26
|
+
// @securevector/cli@6.0.0 must never give you a different SecureVector than
|
|
27
|
+
// pip install securevector-ai-monitor==6.0.0 does.
|
|
28
|
+
const VERSION = pkg.version;
|
|
29
|
+
|
|
30
|
+
// Which Python entry point each verb runs. Names must match setup.py's
|
|
31
|
+
// console_scripts; a typo here is a confusing "command not found" inside the
|
|
32
|
+
// venv rather than an error anyone can act on, so there is a test for it.
|
|
33
|
+
const ENTRY_POINTS = {
|
|
34
|
+
app: 'securevector-app',
|
|
35
|
+
monitor: 'sv-monitor',
|
|
36
|
+
proxy: 'securevector-proxy',
|
|
37
|
+
mcp: 'securevector-mcp',
|
|
38
|
+
};
|
|
39
|
+
const DEFAULT_VERB = 'app';
|
|
40
|
+
|
|
41
|
+
function usage() {
|
|
42
|
+
return [
|
|
43
|
+
`SecureVector ${VERSION}`,
|
|
44
|
+
'',
|
|
45
|
+
'Usage:',
|
|
46
|
+
' securevector [app] [...] Start the local app (default)',
|
|
47
|
+
' securevector monitor [...] The sv-monitor CLI, including session commands',
|
|
48
|
+
' securevector proxy [...] The LLM proxy',
|
|
49
|
+
' securevector mcp [...] The MCP server',
|
|
50
|
+
' securevector doctor Check this machine and report what is missing',
|
|
51
|
+
' securevector where Print the managed environment path',
|
|
52
|
+
' securevector --version',
|
|
53
|
+
'',
|
|
54
|
+
'Anything after the verb is passed through untouched, so',
|
|
55
|
+
' securevector monitor session list',
|
|
56
|
+
'is exactly sv-monitor session list.',
|
|
57
|
+
].join('\n');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function doctor() {
|
|
61
|
+
const { best, seen } = findPython();
|
|
62
|
+
const lines = [`SecureVector ${VERSION} (npm launcher)`, ''];
|
|
63
|
+
lines.push(`node ${process.version}`);
|
|
64
|
+
lines.push(`platform ${process.platform} ${process.arch}`);
|
|
65
|
+
lines.push(`python floor ${MIN_MAJOR}.${MIN_MINOR}`);
|
|
66
|
+
if (best) {
|
|
67
|
+
lines.push(`python found ${best.candidate} (${best.version.join('.')})`);
|
|
68
|
+
} else {
|
|
69
|
+
lines.push('python found none usable');
|
|
70
|
+
}
|
|
71
|
+
if (seen.length) {
|
|
72
|
+
lines.push('interpreters ' + seen.map((s) => `${s.candidate}=${s.version.join('.')}`).join(' '));
|
|
73
|
+
}
|
|
74
|
+
lines.push(`environment ${venvPath(VERSION)}`);
|
|
75
|
+
const ready = isReady(VERSION, ENTRY_POINTS[DEFAULT_VERB]);
|
|
76
|
+
lines.push(`installed ${ready ? 'yes' : 'no, the first run will set it up'}`);
|
|
77
|
+
lines.push('');
|
|
78
|
+
if (!best) {
|
|
79
|
+
lines.push(explainMissing(seen));
|
|
80
|
+
return { text: lines.join('\n'), code: 1 };
|
|
81
|
+
}
|
|
82
|
+
lines.push(ready ? 'Ready.' : 'Ready to set up. Run `securevector` to start.');
|
|
83
|
+
return { text: lines.join('\n'), code: 0 };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function main(argv) {
|
|
87
|
+
const args = argv.slice(2);
|
|
88
|
+
|
|
89
|
+
if (args[0] === '--version' || args[0] === '-v') {
|
|
90
|
+
process.stdout.write(VERSION + '\n');
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
if (args[0] === '--help' || args[0] === '-h' || args[0] === 'help') {
|
|
94
|
+
process.stdout.write(usage() + '\n');
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
if (args[0] === 'doctor') {
|
|
98
|
+
const r = doctor();
|
|
99
|
+
process.stdout.write(r.text + '\n');
|
|
100
|
+
return r.code;
|
|
101
|
+
}
|
|
102
|
+
if (args[0] === 'where') {
|
|
103
|
+
process.stdout.write(envRoot() + '\n');
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// An unrecognised first argument is a flag or an argument for the default
|
|
108
|
+
// verb, not a typo to reject: `securevector --web` has to keep working.
|
|
109
|
+
const verb = Object.prototype.hasOwnProperty.call(ENTRY_POINTS, args[0]) ? args[0] : DEFAULT_VERB;
|
|
110
|
+
const rest = verb === args[0] ? args.slice(1) : args;
|
|
111
|
+
const entryPoint = ENTRY_POINTS[verb];
|
|
112
|
+
|
|
113
|
+
const { best, seen } = findPython();
|
|
114
|
+
if (!best) {
|
|
115
|
+
process.stderr.write('\n' + explainMissing(seen) + '\n\n');
|
|
116
|
+
return 1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!isReady(VERSION, entryPoint)) {
|
|
120
|
+
const result = install(best.candidate, VERSION);
|
|
121
|
+
if (!result.ok) {
|
|
122
|
+
process.stderr.write('\n' + result.reason + '\n\n');
|
|
123
|
+
return 1;
|
|
124
|
+
}
|
|
125
|
+
if (!isReady(VERSION, entryPoint)) {
|
|
126
|
+
process.stderr.write(
|
|
127
|
+
`\nThe install finished but ${entryPoint} is not in ${venvPath(VERSION)}.\n` +
|
|
128
|
+
'Run `securevector doctor`, and if that looks fine, delete the path above and try again.\n\n'
|
|
129
|
+
);
|
|
130
|
+
return 1;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const child = spawn(venvBin(VERSION, entryPoint), rest, { stdio: 'inherit' });
|
|
135
|
+
child.on('error', (err) => {
|
|
136
|
+
process.stderr.write(`\nCould not start ${entryPoint}: ${err.message}\n\n`);
|
|
137
|
+
process.exitCode = 1;
|
|
138
|
+
});
|
|
139
|
+
// Signals belong to the child: it owns a server, a proxy or a terminal
|
|
140
|
+
// session, and this process is a thin parent that must not swallow a Ctrl-C
|
|
141
|
+
// the child needs to shut down cleanly.
|
|
142
|
+
child.on('exit', (code, signal) => {
|
|
143
|
+
process.exitCode = signal ? 1 : (code ?? 0);
|
|
144
|
+
});
|
|
145
|
+
return null; // async from here; exit code set on the child's exit
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (require.main === module) {
|
|
149
|
+
const code = main(process.argv);
|
|
150
|
+
if (code !== null) process.exitCode = code;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = { main, doctor, usage, ENTRY_POINTS, DEFAULT_VERB, VERSION };
|
package/lib/bootstrap.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The managed virtual environment this launcher installs into, and the rules
|
|
5
|
+
* about when it is allowed to touch the network.
|
|
6
|
+
*
|
|
7
|
+
* The shape of the whole thing: `npm install` does NOTHING but put files on
|
|
8
|
+
* disk. The first time someone actually runs `securevector`, and only then, we
|
|
9
|
+
* create a virtual environment under their own data directory and pip install
|
|
10
|
+
* the exact matching version from PyPI, saying so as it happens. Every run
|
|
11
|
+
* after that is a process exec and nothing else.
|
|
12
|
+
*
|
|
13
|
+
* A postinstall script that downloads and executes code is precisely the
|
|
14
|
+
* supply-chain shape this product warns people about. Shipping one in a
|
|
15
|
+
* security tool would be indefensible whatever the convenience argument, so
|
|
16
|
+
* this package does not have one.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const { spawnSync } = require('node:child_process');
|
|
20
|
+
const fs = require('node:fs');
|
|
21
|
+
const os = require('node:os');
|
|
22
|
+
const path = require('node:path');
|
|
23
|
+
|
|
24
|
+
const PYPI_PACKAGE = 'securevector-ai-monitor';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Where the managed environment lives.
|
|
28
|
+
*
|
|
29
|
+
* Under the user's own data directory, never inside node_modules: a global npm
|
|
30
|
+
* prefix can be root-owned or on a read-only volume, and reinstalling or
|
|
31
|
+
* updating the npm package would otherwise throw away a working environment
|
|
32
|
+
* and re-download it.
|
|
33
|
+
*/
|
|
34
|
+
function envRoot() {
|
|
35
|
+
const home = os.homedir();
|
|
36
|
+
if (os.platform() === 'win32') {
|
|
37
|
+
return path.join(process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local'), 'SecureVector', 'npm-runtime');
|
|
38
|
+
}
|
|
39
|
+
if (os.platform() === 'darwin') {
|
|
40
|
+
return path.join(home, 'Library', 'Application Support', 'SecureVector', 'npm-runtime');
|
|
41
|
+
}
|
|
42
|
+
return path.join(process.env.XDG_DATA_HOME || path.join(home, '.local', 'share'), 'securevector', 'npm-runtime');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** One environment per version, so upgrading never half-migrates an old one. */
|
|
46
|
+
function venvPath(version) {
|
|
47
|
+
return path.join(envRoot(), `v${version}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function venvBin(version, exe) {
|
|
51
|
+
const dir = os.platform() === 'win32' ? 'Scripts' : 'bin';
|
|
52
|
+
const suffix = os.platform() === 'win32' ? '.exe' : '';
|
|
53
|
+
return path.join(venvPath(version), dir, exe + suffix);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Is the environment for this version present and carrying the entry points? */
|
|
57
|
+
function isReady(version, entryPoint) {
|
|
58
|
+
try {
|
|
59
|
+
return fs.existsSync(venvBin(version, entryPoint));
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function run(command, args, opts = {}) {
|
|
66
|
+
const res = spawnSync(command, args, { stdio: 'inherit', ...opts });
|
|
67
|
+
if (res.error) throw res.error;
|
|
68
|
+
return res.status === 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Create the environment and install the pinned version. Talks while it works,
|
|
73
|
+
* because it is a network install and a silent 30 second pause during what
|
|
74
|
+
* looked like a launch is indistinguishable from a hang.
|
|
75
|
+
*/
|
|
76
|
+
function install(python, version, { extras = 'app', quiet = false } = {}) {
|
|
77
|
+
const target = venvPath(version);
|
|
78
|
+
const spec = `${PYPI_PACKAGE}[${extras}]==${version}`;
|
|
79
|
+
const say = (line) => {
|
|
80
|
+
if (!quiet) process.stderr.write(line + '\n');
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
say('');
|
|
84
|
+
say(` SecureVector ${version} is not set up yet on this machine.`);
|
|
85
|
+
say('');
|
|
86
|
+
say(' This will now, once:');
|
|
87
|
+
say(` create a virtual environment ${target}`);
|
|
88
|
+
say(` install from PyPI ${spec}`);
|
|
89
|
+
say('');
|
|
90
|
+
say(' Nothing was downloaded during npm install. Later runs start immediately.');
|
|
91
|
+
say('');
|
|
92
|
+
|
|
93
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
94
|
+
|
|
95
|
+
const { command, args } = splitPython(python);
|
|
96
|
+
if (!run(command, [...args, '-m', 'venv', target])) {
|
|
97
|
+
return { ok: false, reason: 'Could not create the virtual environment.' };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const pip = venvBin(version, 'python');
|
|
101
|
+
// Upgrade pip inside the venv first: an old pip on the system Python is a
|
|
102
|
+
// common cause of a wheel resolving wrongly, and this keeps that out of the
|
|
103
|
+
// failure surface. Failure here is not fatal; the install may still work.
|
|
104
|
+
run(pip, ['-m', 'pip', 'install', '--upgrade', 'pip'], { stdio: 'ignore' });
|
|
105
|
+
|
|
106
|
+
if (!run(pip, ['-m', 'pip', 'install', spec])) {
|
|
107
|
+
return {
|
|
108
|
+
ok: false,
|
|
109
|
+
reason: [
|
|
110
|
+
`Could not install ${spec} from PyPI.`,
|
|
111
|
+
'',
|
|
112
|
+
'Common causes: no network, a proxy that needs configuring, or this',
|
|
113
|
+
'version not being published yet. The environment was left at',
|
|
114
|
+
` ${target}`,
|
|
115
|
+
'so you can look, or delete it and try again.',
|
|
116
|
+
].join('\n'),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return { ok: true };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function splitPython(candidate) {
|
|
123
|
+
const parts = candidate.split(' ');
|
|
124
|
+
return { command: parts[0], args: parts.slice(1) };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = { envRoot, venvPath, venvBin, isReady, install, splitPython, PYPI_PACKAGE };
|
package/lib/python.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Finding a Python this product can actually run on, and saying something
|
|
5
|
+
* useful when there isn't one.
|
|
6
|
+
*
|
|
7
|
+
* SecureVector is a Python application. This package exists so that people
|
|
8
|
+
* whose toolchain is Node do not have to know that before they can try it, but
|
|
9
|
+
* "do not have to know" is not the same as "we will install a language runtime
|
|
10
|
+
* behind your back". A security tool that silently provisions an interpreter is
|
|
11
|
+
* doing the thing it warns other software about. So: detect, explain, stop.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { execFileSync } = require('node:child_process');
|
|
15
|
+
const os = require('node:os');
|
|
16
|
+
|
|
17
|
+
// The app extras need 3.10. The base SDK claims 3.9, but nothing this launcher
|
|
18
|
+
// starts is the base SDK, so 3.10 is the honest floor to check against.
|
|
19
|
+
const MIN_MAJOR = 3;
|
|
20
|
+
const MIN_MINOR = 10;
|
|
21
|
+
|
|
22
|
+
// Ordered by how likely each is to be the one the user means. A bare `python`
|
|
23
|
+
// is last: on a machine with both, `python` is as likely to be a 2.x relic as
|
|
24
|
+
// anything, and picking it would produce a syntax error rather than a message.
|
|
25
|
+
const CANDIDATES =
|
|
26
|
+
os.platform() === 'win32'
|
|
27
|
+
? ['py -3', 'python3', 'python']
|
|
28
|
+
: ['python3.13', 'python3.12', 'python3.11', 'python3.10', 'python3', 'python'];
|
|
29
|
+
|
|
30
|
+
function splitCommand(candidate) {
|
|
31
|
+
const parts = candidate.split(' ');
|
|
32
|
+
return { command: parts[0], args: parts.slice(1) };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** `[major, minor]` for an interpreter, or null if it cannot be asked. */
|
|
36
|
+
function versionOf(candidate) {
|
|
37
|
+
const { command, args } = splitCommand(candidate);
|
|
38
|
+
try {
|
|
39
|
+
const out = execFileSync(
|
|
40
|
+
command,
|
|
41
|
+
[...args, '-c', 'import sys; print("%d.%d" % sys.version_info[:2])'],
|
|
42
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 10000 }
|
|
43
|
+
).trim();
|
|
44
|
+
const [major, minor] = out.split('.').map(Number);
|
|
45
|
+
if (!Number.isInteger(major) || !Number.isInteger(minor)) return null;
|
|
46
|
+
return [major, minor];
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isNewEnough([major, minor]) {
|
|
53
|
+
return major > MIN_MAJOR || (major === MIN_MAJOR && minor >= MIN_MINOR);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The best interpreter on this machine, as `{ candidate, version }`.
|
|
58
|
+
*
|
|
59
|
+
* Returns the highest usable version rather than the first found: a machine
|
|
60
|
+
* with 3.9 on PATH and 3.12 beside it should use 3.12, and "first match wins"
|
|
61
|
+
* would hand back the one that cannot run the app.
|
|
62
|
+
*/
|
|
63
|
+
function findPython() {
|
|
64
|
+
const seen = [];
|
|
65
|
+
let best = null;
|
|
66
|
+
for (const candidate of CANDIDATES) {
|
|
67
|
+
const version = versionOf(candidate);
|
|
68
|
+
if (!version) continue;
|
|
69
|
+
seen.push({ candidate, version });
|
|
70
|
+
if (!isNewEnough(version)) continue;
|
|
71
|
+
if (!best || version[0] > best.version[0] || (version[0] === best.version[0] && version[1] > best.version[1])) {
|
|
72
|
+
best = { candidate, version };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { best, seen };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** What to tell someone who has no usable Python. Never a stack trace. */
|
|
79
|
+
function explainMissing(seen) {
|
|
80
|
+
const floor = `${MIN_MAJOR}.${MIN_MINOR}`;
|
|
81
|
+
if (seen.length === 0) {
|
|
82
|
+
return [
|
|
83
|
+
`SecureVector needs Python ${floor} or newer, and no Python was found on PATH.`,
|
|
84
|
+
'',
|
|
85
|
+
'Install one, then run this again:',
|
|
86
|
+
os.platform() === 'darwin'
|
|
87
|
+
? ' brew install python@3.12 (or python.org/downloads)'
|
|
88
|
+
: os.platform() === 'win32'
|
|
89
|
+
? ' winget install Python.Python.3.12 (or python.org/downloads)'
|
|
90
|
+
: ' sudo apt install python3.12 (or your distribution equivalent)',
|
|
91
|
+
'',
|
|
92
|
+
'This package will not install a Python runtime for you. Provisioning a',
|
|
93
|
+
'language runtime without being asked is the behaviour this product exists',
|
|
94
|
+
'to flag in other software.',
|
|
95
|
+
].join('\n');
|
|
96
|
+
}
|
|
97
|
+
const found = seen.map((s) => ` ${s.candidate}: ${s.version.join('.')}`).join('\n');
|
|
98
|
+
return [
|
|
99
|
+
`SecureVector needs Python ${floor} or newer. The interpreters found are older:`,
|
|
100
|
+
'',
|
|
101
|
+
found,
|
|
102
|
+
'',
|
|
103
|
+
`Install Python ${floor}+ and run this again. If you already have one, make sure`,
|
|
104
|
+
'it is on PATH ahead of the versions above.',
|
|
105
|
+
].join('\n');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = { findPython, explainMissing, versionOf, isNewEnough, MIN_MAJOR, MIN_MINOR, CANDIDATES };
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@securevector/cli",
|
|
3
|
+
"version": "5.3.0",
|
|
4
|
+
"description": "Security and observability for AI agents. Launcher for npm and Node users; the product itself is published on PyPI and this installs the matching version.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"securevector",
|
|
7
|
+
"ai-security",
|
|
8
|
+
"llm-security",
|
|
9
|
+
"prompt-injection",
|
|
10
|
+
"agent-security",
|
|
11
|
+
"observability",
|
|
12
|
+
"threat-detection"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://securevector.io",
|
|
15
|
+
"bugs": "https://github.com/Secure-Vector/securevector-ai-threat-monitor/issues",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/Secure-Vector/securevector-ai-threat-monitor.git",
|
|
19
|
+
"directory": "npm"
|
|
20
|
+
},
|
|
21
|
+
"license": "Apache-2.0",
|
|
22
|
+
"author": "SecureVector",
|
|
23
|
+
"type": "commonjs",
|
|
24
|
+
"bin": {
|
|
25
|
+
"securevector": "bin/securevector.js"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"bin",
|
|
29
|
+
"lib",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"test": "node --test 'test/*.test.js'"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
}
|
|
41
|
+
}
|