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 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 --no-wait
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]`
@@ -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.3' };
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.3');
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.3' };
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 = { url, source: 'cli', client: CLIENT_META };
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
- if (scan.videoUrl || scan.videoPath || scan.hasVideo) {
403
- const vurl = scan.videoUrl || ((scan.videoPath) ? `${SUBTO_HOST_BASE}/video/${scan.scanId || scan.id}` : null);
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.3');
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> --no-wait
629
- subto scan <url> --json
630
- subto scan <url> --chat
631
- Request a remote scan for a URL. Polling happens automatically when the server queues the scan unless you pass --no-wait.
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 <url>')
735
- .description('Request a scan for <url> via the Subto API')
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
- program
1268
- .command('scan upload [dir]')
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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "subto",
3
- "version": "9.0.4",
3
+ "version": "9.0.6",
4
4
  "description": "Subto CLI — thin wrapper around the Subto.One API",
5
5
  "bin": {
6
6
  "subto": "bin/subto.js"