subto 9.0.4 → 9.0.6
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 +20 -8
- package/dist/package/index.js +2 -2
- package/index.js +49 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,8 +23,8 @@ npm install -g subto
|
|
|
23
23
|
```bash
|
|
24
24
|
subto login
|
|
25
25
|
subto account
|
|
26
|
-
subto scan https://example.com
|
|
27
|
-
subto scan https://example.com --wait
|
|
26
|
+
subto scan https://example.com basic
|
|
27
|
+
subto scan https://example.com full yes --wait
|
|
28
28
|
subto chat
|
|
29
29
|
```
|
|
30
30
|
|
|
@@ -74,18 +74,24 @@ Output includes:
|
|
|
74
74
|
- scan count
|
|
75
75
|
- member-since date
|
|
76
76
|
|
|
77
|
-
### `subto scan <url
|
|
77
|
+
### `subto scan <url> <basic|full> [yes|no]`
|
|
78
78
|
|
|
79
79
|
Requests a remote scan for a URL via the Subto API.
|
|
80
80
|
|
|
81
81
|
```bash
|
|
82
|
-
subto scan https://example.com
|
|
83
|
-
subto scan https://example.com --wait
|
|
84
|
-
subto scan https://example.com
|
|
85
|
-
subto scan https://example.com --json
|
|
86
|
-
subto scan https://example.com --chat
|
|
82
|
+
subto scan https://example.com basic
|
|
83
|
+
subto scan https://example.com basic --wait
|
|
84
|
+
subto scan https://example.com full yes
|
|
85
|
+
subto scan https://example.com full no --json
|
|
86
|
+
subto scan https://example.com full yes --chat
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
+
Arguments:
|
|
90
|
+
|
|
91
|
+
- `<basic|full>` is required.
|
|
92
|
+
- `[yes|no]` is required when mode is `full`.
|
|
93
|
+
- Do not provide a video argument when mode is `basic`.
|
|
94
|
+
|
|
89
95
|
Options:
|
|
90
96
|
|
|
91
97
|
- `--json` prints the raw JSON response.
|
|
@@ -93,6 +99,12 @@ Options:
|
|
|
93
99
|
- `--no-wait` returns immediately instead of polling.
|
|
94
100
|
- `--chat` opens the local interactive assistant after the scan completes.
|
|
95
101
|
|
|
102
|
+
Examples:
|
|
103
|
+
|
|
104
|
+
- `subto scan https://example.com basic`
|
|
105
|
+
- `subto scan https://example.com full yes`
|
|
106
|
+
- `subto scan https://example.com full no --wait`
|
|
107
|
+
|
|
96
108
|
If the server returns HTML instead of JSON, the CLI attempts to recover the `scanId` automatically. If it cannot, it saves the HTML response to a temporary file for inspection.
|
|
97
109
|
|
|
98
110
|
### `subto scan upload [dir]`
|
package/dist/package/index.js
CHANGED
|
@@ -11,7 +11,7 @@ const chalk = (_chalk && _chalk.default) ? _chalk.default : _chalk;
|
|
|
11
11
|
const CONFIG_DIR = path.join(os.homedir(), '.subto');
|
|
12
12
|
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
|
|
13
13
|
const DEFAULT_API_BASE = 'https://subto.one';
|
|
14
|
-
const CLIENT_META = { name: 'subto-cli', version: '9.0.
|
|
14
|
+
const CLIENT_META = { name: 'subto-cli', version: '9.0.5' };
|
|
15
15
|
|
|
16
16
|
function configFilePath() { return CONFIG_PATH; }
|
|
17
17
|
|
|
@@ -167,7 +167,7 @@ function printAccountSummary(payload) {
|
|
|
167
167
|
|
|
168
168
|
async function run(argv) {
|
|
169
169
|
const program = new Command();
|
|
170
|
-
program.name('subto').description('Subto CLI — wrapper around Subto.One API').version(CLIENT_META.version || '9.0.
|
|
170
|
+
program.name('subto').description('Subto CLI — wrapper around Subto.One API').version(CLIENT_META.version || '9.0.5');
|
|
171
171
|
|
|
172
172
|
program.command('login').description('Store your API key in ~/.subto/config.json').action(async () => {
|
|
173
173
|
try {
|
package/index.js
CHANGED
|
@@ -11,7 +11,7 @@ const chalk = (_chalk && _chalk.default) ? _chalk.default : _chalk;
|
|
|
11
11
|
const CONFIG_DIR = path.join(os.homedir(), '.subto');
|
|
12
12
|
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
|
|
13
13
|
const DEFAULT_API_BASE = 'https://subto.one/api/v1';
|
|
14
|
-
const CLIENT_META = { name: 'subto-cli', version: '9.0.
|
|
14
|
+
const CLIENT_META = { name: 'subto-cli', version: '9.0.5' };
|
|
15
15
|
const cp = require('child_process');
|
|
16
16
|
|
|
17
17
|
// Normalize SUBTO API base so callers can set either
|
|
@@ -215,13 +215,19 @@ async function _maskHeaders(obj){
|
|
|
215
215
|
return out;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
async function postScan(url, apiKey, debug=false) {
|
|
218
|
+
async function postScan(url, apiKey, scanProfile, recordVideo, debug=false) {
|
|
219
219
|
// Use normalized API base
|
|
220
220
|
const endpointObj = new URL('scan', SUBTO_API_BASE_SLASH);
|
|
221
221
|
// API key is sent via headers only — never in query strings to prevent
|
|
222
222
|
// leakage via server logs, proxy logs, and Referer headers.
|
|
223
223
|
const endpoint = endpointObj.toString();
|
|
224
|
-
const body = {
|
|
224
|
+
const body = {
|
|
225
|
+
url,
|
|
226
|
+
source: 'cli',
|
|
227
|
+
client: CLIENT_META,
|
|
228
|
+
scanProfile,
|
|
229
|
+
recordVideo
|
|
230
|
+
};
|
|
225
231
|
const fetchFn = global.fetch;
|
|
226
232
|
if (typeof fetchFn !== 'function') throw new Error('Global fetch() is not available in this Node runtime. Use Node 18+');
|
|
227
233
|
// Validate header-safety to avoid undici/node fetch ByteString conversion errors
|
|
@@ -399,8 +405,10 @@ async function printFullReport(data) {
|
|
|
399
405
|
// Malware reporting via VirusTotal has been disabled in API responses.
|
|
400
406
|
|
|
401
407
|
// Video link
|
|
402
|
-
|
|
403
|
-
|
|
408
|
+
const nestedVideoUrl = scan.comprehensive?.videoMp4Url || scan.comprehensive?.videoUrl || null;
|
|
409
|
+
const nestedVideoPath = scan.comprehensive?.videoPath || null;
|
|
410
|
+
if (scan.videoUrl || scan.videoPath || scan.hasVideo || nestedVideoUrl || nestedVideoPath || scan.comprehensive?.hasVideo) {
|
|
411
|
+
const vurl = scan.videoUrl || nestedVideoUrl || ((scan.videoPath || nestedVideoPath) ? `${SUBTO_HOST_BASE}/video/${scan.scanId || scan.id}` : null);
|
|
404
412
|
if (vurl) {
|
|
405
413
|
// attempt to HEAD the video URL to detect expiry / 404
|
|
406
414
|
let note = '';
|
|
@@ -563,7 +571,7 @@ async function answerFromScan(scan, question){
|
|
|
563
571
|
// Local heuristic fallback
|
|
564
572
|
const lq = q.toLowerCase();
|
|
565
573
|
if(lq.includes('video')||lq.includes('record')){
|
|
566
|
-
const url = scan.videoUrl || (scan.videoPath? `${SUBTO_HOST_BASE}/video/${scan.scanId||scan.id}` : null);
|
|
574
|
+
const url = scan.videoUrl || scan.comprehensive?.videoMp4Url || scan.comprehensive?.videoUrl || ((scan.videoPath || scan.comprehensive?.videoPath) ? `${SUBTO_HOST_BASE}/video/${scan.scanId||scan.id}` : null);
|
|
567
575
|
return url? `Session video: ${url}` : 'No session video available for this scan.';
|
|
568
576
|
}
|
|
569
577
|
if(lq.includes('issues')||lq.includes('problem')){
|
|
@@ -612,7 +620,7 @@ async function startChatREPL(scanData){
|
|
|
612
620
|
|
|
613
621
|
async function run(argv) {
|
|
614
622
|
const program = new Command();
|
|
615
|
-
program.name('subto').description('Subto CLI — wrapper around Subto.One API').version(CLIENT_META.version || '9.0.
|
|
623
|
+
program.name('subto').description('Subto CLI — wrapper around Subto.One API').version(CLIENT_META.version || '9.0.5');
|
|
616
624
|
program.addHelpText('after', `
|
|
617
625
|
|
|
618
626
|
Common commands:
|
|
@@ -623,12 +631,12 @@ Common commands:
|
|
|
623
631
|
subto account --json
|
|
624
632
|
Show your account name, email, API call count, scan count, and member-since date.
|
|
625
633
|
|
|
626
|
-
subto scan <url>
|
|
627
|
-
subto scan <url> --wait
|
|
628
|
-
subto scan <url>
|
|
629
|
-
subto scan <url> --json
|
|
630
|
-
subto scan <url> --chat
|
|
631
|
-
Request a remote scan for a URL.
|
|
634
|
+
subto scan <url> basic
|
|
635
|
+
subto scan <url> basic --wait
|
|
636
|
+
subto scan <url> full yes
|
|
637
|
+
subto scan <url> full no --json
|
|
638
|
+
subto scan <url> full yes --chat
|
|
639
|
+
Request a remote scan for a URL. You must choose basic or full. Full scans also require an explicit video choice: yes or no.
|
|
632
640
|
|
|
633
641
|
subto scan upload [dir]
|
|
634
642
|
subto scan upload [dir] --wait
|
|
@@ -730,19 +738,40 @@ Notes:
|
|
|
730
738
|
}
|
|
731
739
|
});
|
|
732
740
|
|
|
733
|
-
program
|
|
734
|
-
.command('scan
|
|
735
|
-
.description('Request a scan for
|
|
741
|
+
const scanCommand = program
|
|
742
|
+
.command('scan')
|
|
743
|
+
.description('Request a remote scan for a URL, or use a scan subcommand')
|
|
744
|
+
.argument('<url>')
|
|
745
|
+
.argument('<mode>')
|
|
746
|
+
.argument('[videoChoice]')
|
|
736
747
|
.option('--json', 'Output raw JSON')
|
|
737
748
|
.option('--wait', 'Poll for completion and show progress')
|
|
738
749
|
.option('--no-wait', 'Do not poll; return immediately')
|
|
739
750
|
.option('--chat', 'Open interactive AI assistant after scan completes')
|
|
740
|
-
.action(async (url, opts) => {
|
|
751
|
+
.action(async (url, mode, videoChoice, opts) => {
|
|
741
752
|
if (!validateUrl(url)) { console.error(chalk.red('Invalid URL. Provide a full URL including http:// or https://')); process.exit(1); }
|
|
753
|
+
const normalizedMode = String(mode || '').trim().toLowerCase();
|
|
754
|
+
if (!['basic', 'full'].includes(normalizedMode)) {
|
|
755
|
+
console.error(chalk.red('Invalid scan mode. Use `basic` or `full`.'));
|
|
756
|
+
process.exit(1);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
let effectiveVideoChoice = null;
|
|
760
|
+
if (normalizedMode === 'full') {
|
|
761
|
+
effectiveVideoChoice = String(videoChoice || '').trim().toLowerCase();
|
|
762
|
+
if (!['yes', 'no'].includes(effectiveVideoChoice)) {
|
|
763
|
+
console.error(chalk.red('Full scan mode requires an explicit video choice: `yes` or `no`.'));
|
|
764
|
+
process.exit(1);
|
|
765
|
+
}
|
|
766
|
+
} else if (videoChoice !== undefined) {
|
|
767
|
+
console.error(chalk.red('Basic scan mode does not accept a video argument. Use `subto scan <url> basic`.'));
|
|
768
|
+
process.exit(1);
|
|
769
|
+
}
|
|
770
|
+
|
|
742
771
|
const cfg = await readConfig(); if (!cfg || !cfg.apiKey) { console.error(chalk.red('Missing API key. Run:'), chalk.cyan('subto login')); process.exit(1); }
|
|
743
772
|
try {
|
|
744
773
|
const DEBUG = (program && typeof program.opts === 'function') ? Boolean(program.opts().debug) : false;
|
|
745
|
-
const resp = await postScan(url, cfg.apiKey, DEBUG);
|
|
774
|
+
const resp = await postScan(url, cfg.apiKey, normalizedMode, effectiveVideoChoice === 'yes', DEBUG);
|
|
746
775
|
if (resp.status === 429) {
|
|
747
776
|
let retrySeconds = null;
|
|
748
777
|
if (resp.body && typeof resp.body === 'object' && resp.body.retry_after) retrySeconds = resp.body.retry_after;
|
|
@@ -1264,8 +1293,8 @@ Notes:
|
|
|
1264
1293
|
});
|
|
1265
1294
|
|
|
1266
1295
|
// Upload and scan locally via server: `subto scan upload [dir]`
|
|
1267
|
-
|
|
1268
|
-
.command('
|
|
1296
|
+
scanCommand
|
|
1297
|
+
.command('upload [dir]')
|
|
1269
1298
|
.description('Upload a directory to the server and request a scan (respects .subtoignore)')
|
|
1270
1299
|
.option('--wait', 'Poll until analysis completes')
|
|
1271
1300
|
.action(async (dir, opts) => {
|