subto 9.0.4 → 9.0.5
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/index.js +42 -15
- 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/index.js
CHANGED
|
@@ -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
|
|
@@ -623,12 +629,12 @@ Common commands:
|
|
|
623
629
|
subto account --json
|
|
624
630
|
Show your account name, email, API call count, scan count, and member-since date.
|
|
625
631
|
|
|
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.
|
|
632
|
+
subto scan <url> basic
|
|
633
|
+
subto scan <url> basic --wait
|
|
634
|
+
subto scan <url> full yes
|
|
635
|
+
subto scan <url> full no --json
|
|
636
|
+
subto scan <url> full yes --chat
|
|
637
|
+
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
638
|
|
|
633
639
|
subto scan upload [dir]
|
|
634
640
|
subto scan upload [dir] --wait
|
|
@@ -730,19 +736,40 @@ Notes:
|
|
|
730
736
|
}
|
|
731
737
|
});
|
|
732
738
|
|
|
733
|
-
program
|
|
734
|
-
.command('scan
|
|
735
|
-
.description('Request a scan for
|
|
739
|
+
const scanCommand = program
|
|
740
|
+
.command('scan')
|
|
741
|
+
.description('Request a remote scan for a URL, or use a scan subcommand')
|
|
742
|
+
.argument('<url>')
|
|
743
|
+
.argument('<mode>')
|
|
744
|
+
.argument('[videoChoice]')
|
|
736
745
|
.option('--json', 'Output raw JSON')
|
|
737
746
|
.option('--wait', 'Poll for completion and show progress')
|
|
738
747
|
.option('--no-wait', 'Do not poll; return immediately')
|
|
739
748
|
.option('--chat', 'Open interactive AI assistant after scan completes')
|
|
740
|
-
.action(async (url, opts) => {
|
|
749
|
+
.action(async (url, mode, videoChoice, opts) => {
|
|
741
750
|
if (!validateUrl(url)) { console.error(chalk.red('Invalid URL. Provide a full URL including http:// or https://')); process.exit(1); }
|
|
751
|
+
const normalizedMode = String(mode || '').trim().toLowerCase();
|
|
752
|
+
if (!['basic', 'full'].includes(normalizedMode)) {
|
|
753
|
+
console.error(chalk.red('Invalid scan mode. Use `basic` or `full`.'));
|
|
754
|
+
process.exit(1);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
let effectiveVideoChoice = null;
|
|
758
|
+
if (normalizedMode === 'full') {
|
|
759
|
+
effectiveVideoChoice = String(videoChoice || '').trim().toLowerCase();
|
|
760
|
+
if (!['yes', 'no'].includes(effectiveVideoChoice)) {
|
|
761
|
+
console.error(chalk.red('Full scan mode requires an explicit video choice: `yes` or `no`.'));
|
|
762
|
+
process.exit(1);
|
|
763
|
+
}
|
|
764
|
+
} else if (videoChoice !== undefined) {
|
|
765
|
+
console.error(chalk.red('Basic scan mode does not accept a video argument. Use `subto scan <url> basic`.'));
|
|
766
|
+
process.exit(1);
|
|
767
|
+
}
|
|
768
|
+
|
|
742
769
|
const cfg = await readConfig(); if (!cfg || !cfg.apiKey) { console.error(chalk.red('Missing API key. Run:'), chalk.cyan('subto login')); process.exit(1); }
|
|
743
770
|
try {
|
|
744
771
|
const DEBUG = (program && typeof program.opts === 'function') ? Boolean(program.opts().debug) : false;
|
|
745
|
-
const resp = await postScan(url, cfg.apiKey, DEBUG);
|
|
772
|
+
const resp = await postScan(url, cfg.apiKey, normalizedMode, effectiveVideoChoice === 'yes', DEBUG);
|
|
746
773
|
if (resp.status === 429) {
|
|
747
774
|
let retrySeconds = null;
|
|
748
775
|
if (resp.body && typeof resp.body === 'object' && resp.body.retry_after) retrySeconds = resp.body.retry_after;
|
|
@@ -1264,8 +1291,8 @@ Notes:
|
|
|
1264
1291
|
});
|
|
1265
1292
|
|
|
1266
1293
|
// Upload and scan locally via server: `subto scan upload [dir]`
|
|
1267
|
-
|
|
1268
|
-
.command('
|
|
1294
|
+
scanCommand
|
|
1295
|
+
.command('upload [dir]')
|
|
1269
1296
|
.description('Upload a directory to the server and request a scan (respects .subtoignore)')
|
|
1270
1297
|
.option('--wait', 'Poll until analysis completes')
|
|
1271
1298
|
.action(async (dir, opts) => {
|