@midscene/cli 1.14.0 → 1.15.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/dist/es/index.mjs CHANGED
@@ -1,18 +1,19 @@
1
1
  /*! For license information please see index.mjs.LICENSE.txt */
2
- import { ReportMergingTool, createReportCliCommands, runConnectivityTest } from "@midscene/core";
2
+ import { ReportMergingTool, createReportCliCommands } from "@midscene/core";
3
3
  import { runToolsCLI } from "@midscene/shared/cli";
4
4
  import { basename, dirname as external_node_path_dirname, extname, join, posix, relative as external_node_path_relative, resolve as external_node_path_resolve, sep as external_node_path_sep, win32 } from "node:path";
5
5
  import { interpolateEnvVars, midsceneYamlTargetKeys, parseYamlScript as yaml_parseYamlScript, resolveWebTarget } from "@midscene/core/yaml";
6
6
  import { getDebug } from "@midscene/shared/logger";
7
7
  import { fileURLToPath, pathToFileURL } from "node:url";
8
- import { lstat, readdir, readlink, realpath } from "node:fs/promises";
8
+ import { lstat, mkdir, readFile, readdir, readlink, realpath, rename, rm, writeFile } from "node:fs/promises";
9
9
  import { EventEmitter } from "node:events";
10
10
  import node_stream from "node:stream";
11
11
  import { StringDecoder } from "node:string_decoder";
12
12
  import { fileURLToPath as external_url_fileURLToPath } from "url";
13
13
  import node_process, { cwd as external_node_process_cwd } from "node:process";
14
14
  import lodash_merge from "lodash.merge";
15
- import { createHash } from "node:crypto";
15
+ import { loadDotenvConfig, runModelCommand } from "@midscene/test/internal/model-command";
16
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
16
17
  import { getMidsceneRunSubDir } from "@midscene/shared/common";
17
18
  import { buildChromeArgs, buildDownloadBehavior, defaultViewportHeight, defaultViewportWidth } from "@midscene/web/puppeteer-agent-launcher";
18
19
  import puppeteer from "puppeteer";
@@ -20,10 +21,12 @@ import { createYamlPlayer as yaml_runtime_createYamlPlayer } from "@midscene/tes
20
21
  import { stripVTControlCharacters } from "node:util";
21
22
  import { createRequire } from "node:module";
22
23
  import { serializeError } from "@midscene/shared/agent-tools/error-formatter";
23
- import { globalModelConfigManager } from "@midscene/shared/env";
24
+ import { spawn } from "node:child_process";
25
+ import { homedir } from "node:os";
26
+ import { createServer } from "node:http";
27
+ import { createRemoteJWKSet, jwtVerify } from "jose";
24
28
  import { classifyWorkflowDocumentFormat } from "@midscene/core/internal/test-runner";
25
29
  import * as __rspack_external_assert from "assert";
26
- import * as __rspack_external_crypto from "crypto";
27
30
  import * as __rspack_external_fs from "fs";
28
31
  import * as __rspack_external_os from "os";
29
32
  import * as __rspack_external_path from "path";
@@ -2475,232 +2478,6 @@ var __webpack_modules__ = {
2475
2478
  ]
2476
2479
  };
2477
2480
  },
2478
- "../../node_modules/.pnpm/dotenv@16.4.5/node_modules/dotenv/lib/main.js" (module, __unused_rspack_exports, __webpack_require__) {
2479
- const fs = __webpack_require__("fs");
2480
- const path = __webpack_require__("path");
2481
- const os = __webpack_require__("os");
2482
- const crypto = __webpack_require__("crypto");
2483
- const packageJson = __webpack_require__("../../node_modules/.pnpm/dotenv@16.4.5/node_modules/dotenv/package.json");
2484
- const version = packageJson.version;
2485
- const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
2486
- function parse(src) {
2487
- const obj = {};
2488
- let lines = src.toString();
2489
- lines = lines.replace(/\r\n?/mg, '\n');
2490
- let match;
2491
- while(null != (match = LINE.exec(lines))){
2492
- const key = match[1];
2493
- let value = match[2] || '';
2494
- value = value.trim();
2495
- const maybeQuote = value[0];
2496
- value = value.replace(/^(['"`])([\s\S]*)\1$/mg, '$2');
2497
- if ('"' === maybeQuote) {
2498
- value = value.replace(/\\n/g, '\n');
2499
- value = value.replace(/\\r/g, '\r');
2500
- }
2501
- obj[key] = value;
2502
- }
2503
- return obj;
2504
- }
2505
- function _parseVault(options) {
2506
- const vaultPath = _vaultPath(options);
2507
- const result = DotenvModule.configDotenv({
2508
- path: vaultPath
2509
- });
2510
- if (!result.parsed) {
2511
- const err = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
2512
- err.code = 'MISSING_DATA';
2513
- throw err;
2514
- }
2515
- const keys = _dotenvKey(options).split(',');
2516
- const length = keys.length;
2517
- let decrypted;
2518
- for(let i = 0; i < length; i++)try {
2519
- const key = keys[i].trim();
2520
- const attrs = _instructions(result, key);
2521
- decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
2522
- break;
2523
- } catch (error) {
2524
- if (i + 1 >= length) throw error;
2525
- }
2526
- return DotenvModule.parse(decrypted);
2527
- }
2528
- function _log(message) {
2529
- console.log(`[dotenv@${version}][INFO] ${message}`);
2530
- }
2531
- function _warn(message) {
2532
- console.log(`[dotenv@${version}][WARN] ${message}`);
2533
- }
2534
- function _debug(message) {
2535
- console.log(`[dotenv@${version}][DEBUG] ${message}`);
2536
- }
2537
- function _dotenvKey(options) {
2538
- if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) return options.DOTENV_KEY;
2539
- if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) return process.env.DOTENV_KEY;
2540
- return '';
2541
- }
2542
- function _instructions(result, dotenvKey) {
2543
- let uri;
2544
- try {
2545
- uri = new URL(dotenvKey);
2546
- } catch (error) {
2547
- if ('ERR_INVALID_URL' === error.code) {
2548
- const err = new Error('INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development');
2549
- err.code = 'INVALID_DOTENV_KEY';
2550
- throw err;
2551
- }
2552
- throw error;
2553
- }
2554
- const key = uri.password;
2555
- if (!key) {
2556
- const err = new Error('INVALID_DOTENV_KEY: Missing key part');
2557
- err.code = 'INVALID_DOTENV_KEY';
2558
- throw err;
2559
- }
2560
- const environment = uri.searchParams.get('environment');
2561
- if (!environment) {
2562
- const err = new Error('INVALID_DOTENV_KEY: Missing environment part');
2563
- err.code = 'INVALID_DOTENV_KEY';
2564
- throw err;
2565
- }
2566
- const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
2567
- const ciphertext = result.parsed[environmentKey];
2568
- if (!ciphertext) {
2569
- const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
2570
- err.code = 'NOT_FOUND_DOTENV_ENVIRONMENT';
2571
- throw err;
2572
- }
2573
- return {
2574
- ciphertext,
2575
- key
2576
- };
2577
- }
2578
- function _vaultPath(options) {
2579
- let possibleVaultPath = null;
2580
- if (options && options.path && options.path.length > 0) if (Array.isArray(options.path)) {
2581
- for (const filepath of options.path)if (fs.existsSync(filepath)) possibleVaultPath = filepath.endsWith('.vault') ? filepath : `${filepath}.vault`;
2582
- } else possibleVaultPath = options.path.endsWith('.vault') ? options.path : `${options.path}.vault`;
2583
- else possibleVaultPath = path.resolve(process.cwd(), '.env.vault');
2584
- if (fs.existsSync(possibleVaultPath)) return possibleVaultPath;
2585
- return null;
2586
- }
2587
- function _resolveHome(envPath) {
2588
- return '~' === envPath[0] ? path.join(os.homedir(), envPath.slice(1)) : envPath;
2589
- }
2590
- function _configVault(options) {
2591
- _log('Loading env from encrypted .env.vault');
2592
- const parsed = DotenvModule._parseVault(options);
2593
- let processEnv = process.env;
2594
- if (options && null != options.processEnv) processEnv = options.processEnv;
2595
- DotenvModule.populate(processEnv, parsed, options);
2596
- return {
2597
- parsed
2598
- };
2599
- }
2600
- function configDotenv(options) {
2601
- const dotenvPath = path.resolve(process.cwd(), '.env');
2602
- let encoding = 'utf8';
2603
- const debug = Boolean(options && options.debug);
2604
- if (options && options.encoding) encoding = options.encoding;
2605
- else if (debug) _debug('No encoding is specified. UTF-8 is used by default');
2606
- let optionPaths = [
2607
- dotenvPath
2608
- ];
2609
- if (options && options.path) if (Array.isArray(options.path)) {
2610
- optionPaths = [];
2611
- for (const filepath of options.path)optionPaths.push(_resolveHome(filepath));
2612
- } else optionPaths = [
2613
- _resolveHome(options.path)
2614
- ];
2615
- let lastError;
2616
- const parsedAll = {};
2617
- for (const path of optionPaths)try {
2618
- const parsed = DotenvModule.parse(fs.readFileSync(path, {
2619
- encoding
2620
- }));
2621
- DotenvModule.populate(parsedAll, parsed, options);
2622
- } catch (e1) {
2623
- if (debug) _debug(`Failed to load ${path} ${e1.message}`);
2624
- lastError = e1;
2625
- }
2626
- let processEnv = process.env;
2627
- if (options && null != options.processEnv) processEnv = options.processEnv;
2628
- DotenvModule.populate(processEnv, parsedAll, options);
2629
- if (lastError) return {
2630
- parsed: parsedAll,
2631
- error: lastError
2632
- };
2633
- return {
2634
- parsed: parsedAll
2635
- };
2636
- }
2637
- function config(options) {
2638
- if (0 === _dotenvKey(options).length) return DotenvModule.configDotenv(options);
2639
- const vaultPath = _vaultPath(options);
2640
- if (!vaultPath) {
2641
- _warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`);
2642
- return DotenvModule.configDotenv(options);
2643
- }
2644
- return DotenvModule._configVault(options);
2645
- }
2646
- function decrypt(encrypted, keyStr) {
2647
- const key = Buffer.from(keyStr.slice(-64), 'hex');
2648
- let ciphertext = Buffer.from(encrypted, 'base64');
2649
- const nonce = ciphertext.subarray(0, 12);
2650
- const authTag = ciphertext.subarray(-16);
2651
- ciphertext = ciphertext.subarray(12, -16);
2652
- try {
2653
- const aesgcm = crypto.createDecipheriv('aes-256-gcm', key, nonce);
2654
- aesgcm.setAuthTag(authTag);
2655
- return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
2656
- } catch (error) {
2657
- const isRange = error instanceof RangeError;
2658
- const invalidKeyLength = 'Invalid key length' === error.message;
2659
- const decryptionFailed = 'Unsupported state or unable to authenticate data' === error.message;
2660
- if (isRange || invalidKeyLength) {
2661
- const err = new Error('INVALID_DOTENV_KEY: It must be 64 characters long (or more)');
2662
- err.code = 'INVALID_DOTENV_KEY';
2663
- throw err;
2664
- }
2665
- if (decryptionFailed) {
2666
- const err = new Error('DECRYPTION_FAILED: Please check your DOTENV_KEY');
2667
- err.code = 'DECRYPTION_FAILED';
2668
- throw err;
2669
- }
2670
- throw error;
2671
- }
2672
- }
2673
- function populate(processEnv, parsed, options = {}) {
2674
- const debug = Boolean(options && options.debug);
2675
- const override = Boolean(options && options.override);
2676
- if ('object' != typeof parsed) {
2677
- const err = new Error('OBJECT_REQUIRED: Please check the processEnv argument being passed to populate');
2678
- err.code = 'OBJECT_REQUIRED';
2679
- throw err;
2680
- }
2681
- for (const key of Object.keys(parsed))if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
2682
- if (true === override) processEnv[key] = parsed[key];
2683
- if (debug) true === override ? _debug(`"${key}" is already defined and WAS overwritten`) : _debug(`"${key}" is already defined and was NOT overwritten`);
2684
- } else processEnv[key] = parsed[key];
2685
- }
2686
- const DotenvModule = {
2687
- configDotenv,
2688
- _configVault,
2689
- _parseVault,
2690
- config,
2691
- decrypt,
2692
- parse,
2693
- populate
2694
- };
2695
- module.exports.configDotenv = DotenvModule.configDotenv;
2696
- module.exports._configVault = DotenvModule._configVault;
2697
- module.exports._parseVault = DotenvModule._parseVault;
2698
- module.exports.config = DotenvModule.config;
2699
- module.exports.decrypt = DotenvModule.decrypt;
2700
- module.exports.parse = DotenvModule.parse;
2701
- module.exports.populate = DotenvModule.populate;
2702
- module.exports = DotenvModule;
2703
- },
2704
2481
  "../../node_modules/.pnpm/emoji-regex@8.0.0/node_modules/emoji-regex/index.js" (module) {
2705
2482
  module.exports = function() {
2706
2483
  return /\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62(?:\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74|\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F|\uD83D\uDC68(?:\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68\uD83C\uDFFB|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFE])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83D\uDC68|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D[\uDC66\uDC67])|[\u2695\u2696\u2708]\uFE0F|\uD83D[\uDC66\uDC67]|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|(?:\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708])\uFE0F|\uD83C\uDFFB\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C[\uDFFB-\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFB\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)\uD83C\uDFFB|\uD83E\uDDD1(?:\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])|\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1)|(?:\uD83E\uDDD1\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB-\uDFFE])|(?:\uD83E\uDDD1\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)(?:\uD83C[\uDFFB\uDFFC])|\uD83D\uDC69(?:\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFB\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFC-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|(?:\uD83E\uDDD1\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)(?:\uD83C[\uDFFB-\uDFFD])|\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D[\uDC66\uDC67])|(?:\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8|\uD83D\uDC69(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])|(?:(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)\uFE0F|\uD83D\uDC6F|\uD83E[\uDD3C\uDDDE\uDDDF])\u200D[\u2640\u2642]|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD6-\uDDDD])(?:(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]|\u200D[\u2640\u2642])|\uD83C\uDFF4\u200D\u2620)\uFE0F|\uD83D\uDC69\u200D\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08|\uD83D\uDC15\u200D\uD83E\uDDBA|\uD83D\uDC69\u200D\uD83D\uDC66|\uD83D\uDC69\u200D\uD83D\uDC67|\uD83C\uDDFD\uD83C\uDDF0|\uD83C\uDDF4\uD83C\uDDF2|\uD83C\uDDF6\uD83C\uDDE6|[#\*0-9]\uFE0F\u20E3|\uD83C\uDDE7(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF])|\uD83C\uDDF9(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF])|\uD83C\uDDEA(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA])|\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])|\uD83C\uDDF7(?:\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC])|\uD83D\uDC69(?:\uD83C[\uDFFB-\uDFFF])|\uD83C\uDDF2(?:\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF])|\uD83C\uDDE6(?:\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF])|\uD83C\uDDF0(?:\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF])|\uD83C\uDDED(?:\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA])|\uD83C\uDDE9(?:\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF])|\uD83C\uDDFE(?:\uD83C[\uDDEA\uDDF9])|\uD83C\uDDEC(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE])|\uD83C\uDDF8(?:\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF])|\uD83C\uDDEB(?:\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7])|\uD83C\uDDF5(?:\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE])|\uD83C\uDDFB(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA])|\uD83C\uDDF3(?:\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF])|\uD83C\uDDE8(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF])|\uD83C\uDDF1(?:\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE])|\uD83C\uDDFF(?:\uD83C[\uDDE6\uDDF2\uDDFC])|\uD83C\uDDFC(?:\uD83C[\uDDEB\uDDF8])|\uD83C\uDDFA(?:\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF])|\uD83C\uDDEE(?:\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9])|\uD83C\uDDEF(?:\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5])|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u270A-\u270D]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC70\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDCAA\uDD74\uDD7A\uDD90\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD0F\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD36\uDDB5\uDDB6\uDDBB\uDDD2-\uDDD5])(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDED5\uDEEB\uDEEC\uDEF4-\uDEFA\uDFE0-\uDFEB]|\uD83E[\uDD0D-\uDD3A\uDD3C-\uDD45\uDD47-\uDD71\uDD73-\uDD76\uDD7A-\uDDA2\uDDA5-\uDDAA\uDDAE-\uDDCA\uDDCD-\uDDFF\uDE70-\uDE73\uDE78-\uDE7A\uDE80-\uDE82\uDE90-\uDE95])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEFA\uDFE0-\uDFEB]|\uD83E[\uDD0D-\uDD3A\uDD3C-\uDD45\uDD47-\uDD71\uDD73-\uDD76\uDD7A-\uDDA2\uDDA5-\uDDAA\uDDAE-\uDDCA\uDDCD-\uDDFF\uDE70-\uDE73\uDE78-\uDE7A\uDE80-\uDE82\uDE90-\uDE95])\uFE0F|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDC8F\uDC91\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD0F\uDD18-\uDD1F\uDD26\uDD30-\uDD39\uDD3C-\uDD3E\uDDB5\uDDB6\uDDB8\uDDB9\uDDBB\uDDCD-\uDDCF\uDDD1-\uDDDD])/g;
@@ -3024,7 +2801,7 @@ var __webpack_modules__ = {
3024
2801
  },
3025
2802
  "./src/index.ts" (__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
3026
2803
  var package_namespaceObject = {
3027
- rE: "1.14.0"
2804
+ rE: "1.15.0"
3028
2805
  };
3029
2806
  var brace_expansion = __webpack_require__("../../node_modules/.pnpm/brace-expansion@2.0.1/node_modules/brace-expansion/index.js");
3030
2807
  const MAX_PATTERN_LENGTH = 65536;
@@ -8636,9 +8413,11 @@ Homepage: https://midscenejs.com
8636
8413
  Github: https://github.com/web-infra-dev/midscene
8637
8414
 
8638
8415
  Usage:
8416
+ $0 model siwc <login|refresh> [options]
8639
8417
  $0 [options] <path-to-yaml-script-file-or-directory>
8640
8418
  $0 [options] --files <yaml-file1> <yaml-file2 ...>
8641
- $0 [options] --config <path-to-config-yaml-file>`).options({
8419
+ $0 [options] --config <path-to-config-yaml-file>
8420
+ $0 model verify Check model connectivity and Midscene compatibility`).options({
8642
8421
  files: {
8643
8422
  type: 'array',
8644
8423
  string: true,
@@ -8696,7 +8475,7 @@ Usage:
8696
8475
  type: 'boolean',
8697
8476
  description: 'Whether to dismiss the HarmonyOS keyboard after text input'
8698
8477
  }
8699
- }).version('version', 'Show version number', "1.14.0").help().epilogue(`For complete list of configuration options, please visit:
8478
+ }).version('version', 'Show version number', "1.15.0").help().epilogue(`For complete list of configuration options, please visit:
8700
8479
  • Web options: https://midscenejs.com/automate-with-scripts-in-yaml#the-web-part
8701
8480
  • Android options: https://midscenejs.com/automate-with-scripts-in-yaml#the-android-part
8702
8481
  • HarmonyOS options: https://midscenejs.com/automate-with-scripts-in-yaml#the-harmony-part
@@ -10943,18 +10722,6 @@ Native cases/steps Test projects use the midscene-test command.`).wrap(yargs().t
10943
10722
  globalConfig: pickYamlTargetConfig(options)
10944
10723
  };
10945
10724
  }
10946
- var main = __webpack_require__("../../node_modules/.pnpm/dotenv@16.4.5/node_modules/dotenv/lib/main.js");
10947
- var main_default = /*#__PURE__*/ __webpack_require__.n(main);
10948
- function loadDotenvConfig(options = {}) {
10949
- const dotEnvConfigFile = join(options.cwd ?? process.cwd(), '.env');
10950
- if (!(0, __rspack_external_node_fs_5ea92f0c.existsSync)(dotEnvConfigFile)) return;
10951
- options.log?.(` Env file: ${dotEnvConfigFile}`);
10952
- main_default().config({
10953
- path: dotEnvConfigFile,
10954
- debug: options.dotenvDebug,
10955
- override: options.dotenvOverride
10956
- });
10957
- }
10958
10725
  const warnRetryReport = getDebug('execution-summary', {
10959
10726
  console: true
10960
10727
  });
@@ -12723,172 +12490,431 @@ defineYamlBatchTest(test, testOptions);
12723
12490
  const success = printExecutionSummary(results, summaryPath);
12724
12491
  return success ? exitCode : 1;
12725
12492
  }
12726
- const DEFAULT_OPENAI_BASE_URL = 'https://api.openai.com/v1';
12727
- const MODEL_VERIFY_SEPARATOR = '────────────────────────────────────────';
12728
- const MODEL_COMMAND_USAGE = `Usage:
12729
- midscene model verify
12730
- midscene model eval
12731
- `;
12732
- function assertNoModelVerifyOptions(args) {
12733
- for (const arg of args)throw new Error(`Unknown option for midscene model verify: ${arg}`);
12734
- }
12735
- function shellSingleQuote(value) {
12736
- return `'${value.replace(/'/g, "'\\''")}'`;
12493
+ const defaultCredentialsPath = ()=>join(homedir(), '.midscene', 'siwc.json');
12494
+ async function readCredentialsFile(path) {
12495
+ const content = await readFile(path, 'utf8').catch((error)=>{
12496
+ if ('ENOENT' === error.code) return;
12497
+ throw new Error('Unable to read the local SIWC credentials file.');
12498
+ });
12499
+ if (void 0 === content) return;
12500
+ try {
12501
+ const value = JSON.parse(content);
12502
+ if (!value || 'string' != typeof value.credentials?.ext_agent_host_id || !value.credentials.ext_agent_host_id) throw new Error('Invalid SIWC credentials record.');
12503
+ return value;
12504
+ } catch {
12505
+ throw new Error('Invalid local SIWC credentials file.');
12506
+ }
12737
12507
  }
12738
- function buildChatCompletionsUrl(baseURL) {
12739
- const normalizedBaseURL = (baseURL || DEFAULT_OPENAI_BASE_URL).replace(/\/+$/, '');
12740
- if (normalizedBaseURL.endsWith('/chat/completions')) return normalizedBaseURL;
12741
- return `${normalizedBaseURL}/chat/completions`;
12508
+ async function readRegistration(path) {
12509
+ return (await readCredentialsFile(path))?.credentials;
12742
12510
  }
12743
- function buildCurlCommand(modelConfig) {
12744
- const payload = {
12745
- model: modelConfig.modelName,
12746
- messages: [
12747
- {
12748
- role: 'user',
12749
- content: 'What is 1+1?'
12750
- }
12751
- ]
12511
+ async function saveRegistration(path, value) {
12512
+ const saved = await readCredentialsFile(path);
12513
+ const now = new Date().toISOString();
12514
+ const record = {
12515
+ ...saved,
12516
+ created_at: saved?.created_at ?? now,
12517
+ updated_at: now,
12518
+ credentials: value
12752
12519
  };
12753
- return [
12754
- `curl -X POST ${shellSingleQuote(buildChatCompletionsUrl(modelConfig.openaiBaseURL))} \\`,
12755
- ` -H ${shellSingleQuote(`Authorization: Bearer ${modelConfig.openaiApiKey || ''}`)} \\`,
12756
- ` -H ${shellSingleQuote('Content-Type: application/json')} \\`,
12757
- ` -d ${shellSingleQuote(JSON.stringify(payload, null, 2))}`
12758
- ].join('\n');
12759
- }
12760
- function buildCurlDedupKey(modelConfig) {
12761
- return JSON.stringify({
12762
- baseURL: modelConfig.openaiBaseURL || DEFAULT_OPENAI_BASE_URL,
12763
- apiKey: modelConfig.openaiApiKey || '',
12764
- modelName: modelConfig.modelName
12765
- });
12766
- }
12767
- function buildModelVerifyCurlCommands(configs) {
12768
- const commandMap = new Map();
12769
- for (const item of configs){
12770
- const key = buildCurlDedupKey(item.modelConfig);
12771
- const existing = commandMap.get(key);
12772
- if (existing) {
12773
- existing.intents.push(item.intent);
12774
- continue;
12775
- }
12776
- commandMap.set(key, {
12777
- intents: [
12778
- item.intent
12779
- ],
12780
- curl: buildCurlCommand(item.modelConfig),
12781
- usesDefaultBaseURL: !item.modelConfig.openaiBaseURL
12520
+ const temporary = `${path}.${randomUUID()}.tmp`;
12521
+ try {
12522
+ await writeFile(temporary, `${JSON.stringify(record, null, 2)}\n`, {
12523
+ mode: 384,
12524
+ flag: 'wx'
12525
+ });
12526
+ await rename(temporary, path);
12527
+ } finally{
12528
+ await rm(temporary, {
12529
+ force: true
12782
12530
  });
12783
12531
  }
12784
- return [
12785
- ...commandMap.values()
12786
- ];
12787
12532
  }
12788
- function formatModelVerifyFailureOutput(message, curlCommands) {
12789
- const details = message?.trim() || 'No failure details were generated.';
12790
- const curlSection = curlCommands.map((item)=>{
12791
- const baseUrlNote = item.usesDefaultBaseURL ? ' (base URL not configured; using OpenAI SDK default)' : '';
12792
- return source_default().gray(`# ${item.intents.join(', ')}${baseUrlNote}\n${item.curl}`);
12793
- }).join('\n\n');
12794
- return [
12795
- source_default().red.bold('āŒ Model verify failed with messages:'),
12796
- MODEL_VERIFY_SEPARATOR,
12797
- '',
12798
- details,
12799
- '',
12800
- MODEL_VERIFY_SEPARATOR,
12801
- 'Generated curl requests for basic API connectivity:',
12802
- 'If the error is a basic connectivity issue, use these requests to test the base URL, API key, and model name directly.',
12803
- 'These commands contain your API key. Do not share them publicly.',
12804
- '',
12805
- curlSection
12806
- ].join('\n');
12533
+ async function lockCredentials(path) {
12534
+ await mkdir(external_node_path_dirname(path), {
12535
+ recursive: true,
12536
+ mode: 448
12537
+ });
12538
+ const lockPath = `${path}.lock`;
12539
+ try {
12540
+ await mkdir(lockPath, {
12541
+ mode: 448
12542
+ });
12543
+ } catch (error) {
12544
+ if ('EEXIST' === error.code) throw new Error(`Another SIWC command holds ${lockPath}. If it was interrupted, remove this lock only after confirming that no SIWC command is running.`);
12545
+ throw new Error('Unable to lock the local SIWC credentials file.');
12546
+ }
12547
+ return ()=>rm(lockPath, {
12548
+ recursive: true,
12549
+ force: true
12550
+ });
12807
12551
  }
12808
- function getDefaultDeps(io) {
12809
- return {
12810
- loadDotenv: ()=>{
12811
- loadDotenvConfig({
12812
- dotenvDebug: true,
12813
- dotenvOverride: true,
12814
- log: io.stdout
12552
+ function requireCredentials(value) {
12553
+ if (!value || [
12554
+ 'client_id',
12555
+ 'subject',
12556
+ 'issuer',
12557
+ 'access_token',
12558
+ 'refresh_token',
12559
+ 'id_token'
12560
+ ].some((key)=>'string' != typeof value[key] || !value[key]) || !Array.isArray(value.scopes) || !value.scopes.every((scope)=>'string' == typeof scope) || !Number.isFinite(value.expires_at)) throw new Error('No valid SIWC credentials. Run midscene model siwc login first.');
12561
+ return value;
12562
+ }
12563
+ const ISSUER = 'https://auth.openai.com';
12564
+ const RESOURCE = 'https://api.openai.com/v1';
12565
+ const SCOPES = 'openid profile email offline_access resource.invoke chatgpt.tokens.use.direct';
12566
+ async function loginWithOpenAI(options, dependencies) {
12567
+ const hostId = options.hostId ?? `urn:uuid:${randomUUID()}`;
12568
+ const existingClientId = options.clientId;
12569
+ const state = randomBytes(32).toString('base64url');
12570
+ const nonce = randomBytes(32).toString('base64url');
12571
+ const verifier = randomBytes(32).toString('base64url');
12572
+ const signal = options.signal;
12573
+ signal.throwIfAborted();
12574
+ const server = createServer();
12575
+ const callback = new Promise((resolve, reject)=>{
12576
+ const consumed = {
12577
+ value: false
12578
+ };
12579
+ server.on('request', (request, response)=>{
12580
+ response.setHeader('Content-Type', 'text/plain; charset=utf-8');
12581
+ response.setHeader('Cache-Control', 'no-store');
12582
+ response.setHeader('Referrer-Policy', 'no-referrer');
12583
+ const url = new URL(request.url || '/', 'http://127.0.0.1');
12584
+ if ('GET' !== request.method || '/auth/callback' !== url.pathname) return void response.writeHead(404).end('Not found');
12585
+ if (1 !== url.searchParams.getAll('state').length || url.searchParams.get('state') !== state) return void response.writeHead(400).end('Invalid login state. Use the original authorization link.');
12586
+ if (consumed.value) return void response.writeHead(409).end('Authorization already received.');
12587
+ if (url.searchParams.has('error')) {
12588
+ consumed.value = true;
12589
+ response.writeHead(400).end('Authorization was not granted. Return to the terminal.');
12590
+ reject(new Error('OpenAI authorization was denied or failed.'));
12591
+ return;
12592
+ }
12593
+ const code = url.searchParams.get('code');
12594
+ const clientId = url.searchParams.get('client_id') || existingClientId;
12595
+ if (!code || !clientId || 'dynamic_agent_client' === clientId || existingClientId && clientId !== existingClientId || 1 !== url.searchParams.getAll('code').length || url.searchParams.getAll('client_id').length > 1) return void response.writeHead(400).end('Invalid authorization callback.');
12596
+ consumed.value = true;
12597
+ response.setHeader('Content-Type', 'text/html; charset=utf-8');
12598
+ response.end(`<!doctype html>
12599
+ <html lang="en">
12600
+ <head>
12601
+ <meta charset="utf-8">
12602
+ <meta name="viewport" content="width=device-width, initial-scale=1">
12603
+ <title>Authorization received Ā· Midscene</title>
12604
+ <style>
12605
+ * { box-sizing: border-box; }
12606
+ body { margin: 0; min-height: 100vh; display: grid; place-items: center; padding: 24px; background: #f6f7f9; color: #202124; font-family: system-ui, sans-serif; }
12607
+ main { width: 100%; max-width: 480px; padding: 40px 28px; text-align: center; background: white; border: 1px solid #e5e7eb; border-radius: 20px; box-shadow: 0 8px 32px #00000008; }
12608
+ h1 { margin: 0 0 20px; font-size: 24px; line-height: 1.4; }
12609
+ h1 span { color: #16834a; }
12610
+ p { margin: 8px 0 0; color: #596273; font-size: 15px; line-height: 1.6; }
12611
+ </style>
12612
+ </head>
12613
+ <body>
12614
+ <main>
12615
+ <h1><span aria-hidden="true">āœ“</span> Authorization received</h1>
12616
+ <p>You can close this tab.</p>
12617
+ <p>Check your terminal for the sign-in result and access token.</p>
12618
+ </main>
12619
+ </body>
12620
+ </html>`);
12621
+ resolve({
12622
+ code,
12623
+ clientId
12815
12624
  });
12816
- },
12817
- getModelConfig: (intent)=>globalModelConfigManager.getModelConfig(intent),
12818
- verifyModel: runConnectivityTest
12819
- };
12820
- }
12821
- async function runModelVerifyCommand(args, deps, io) {
12625
+ });
12626
+ server.on('error', reject);
12627
+ });
12628
+ callback.catch(()=>void 0);
12629
+ const abort = ()=>server.emit('error', new Error('OpenAI login cancelled or timed out.'));
12630
+ signal.addEventListener('abort', abort, {
12631
+ once: true
12632
+ });
12822
12633
  try {
12823
- if (args.includes('--help') || args.includes('-h')) {
12824
- io.stdout(MODEL_COMMAND_USAGE);
12825
- return 0;
12826
- }
12827
- assertNoModelVerifyOptions(args);
12828
- io.stdout('Model verify started. This usually takes about 5 seconds.\n');
12829
- deps.loadDotenv();
12830
- io.stdout('');
12831
- const defaultModelConfig = deps.getModelConfig('default');
12832
- const planningModelConfig = deps.getModelConfig('planning');
12833
- const insightModelConfig = deps.getModelConfig('insight');
12834
- const curlCommands = buildModelVerifyCurlCommands([
12835
- {
12836
- intent: 'default',
12837
- modelConfig: defaultModelConfig
12634
+ await new Promise((resolve, reject)=>{
12635
+ server.once('error', reject);
12636
+ server.listen(options.port ?? 0, '127.0.0.1', ()=>{
12637
+ server.removeListener('error', reject);
12638
+ resolve();
12639
+ });
12640
+ });
12641
+ signal.throwIfAborted();
12642
+ const redirectUri = `http://127.0.0.1:${server.address().port}/auth/callback`;
12643
+ const authorization = new URL(`${ISSUER}/api/accounts/authorize`);
12644
+ authorization.search = new URLSearchParams({
12645
+ client_id: existingClientId ?? 'dynamic_agent_client',
12646
+ ...existingClientId ? {} : {
12647
+ agent_name_hint: 'Midscene'
12838
12648
  },
12839
- {
12840
- intent: 'planning',
12841
- modelConfig: planningModelConfig
12649
+ ext_agent_host_id: hostId,
12650
+ response_type: 'code',
12651
+ redirect_uri: redirectUri,
12652
+ scope: SCOPES,
12653
+ resource: RESOURCE,
12654
+ state,
12655
+ nonce,
12656
+ code_challenge_method: 'S256',
12657
+ code_challenge: createHash('sha256').update(verifier).digest('base64url')
12658
+ }).toString();
12659
+ await options.onAuthorization(authorization.toString());
12660
+ const { code, clientId } = await callback;
12661
+ signal.throwIfAborted();
12662
+ const request = dependencies?.fetch ?? fetch;
12663
+ const result = await request(`${ISSUER}/api/accounts/oauth/token`, {
12664
+ method: 'POST',
12665
+ headers: {
12666
+ 'Content-Type': 'application/x-www-form-urlencoded'
12842
12667
  },
12843
- {
12844
- intent: 'insight',
12845
- modelConfig: insightModelConfig
12846
- }
12847
- ]);
12848
- const result = await deps.verifyModel({
12849
- defaultModelConfig,
12850
- planningModelConfig,
12851
- insightModelConfig
12668
+ body: new URLSearchParams({
12669
+ grant_type: 'authorization_code',
12670
+ client_id: clientId,
12671
+ code,
12672
+ code_verifier: verifier,
12673
+ redirect_uri: redirectUri,
12674
+ resource: RESOURCE
12675
+ }),
12676
+ signal,
12677
+ redirect: 'error'
12852
12678
  });
12853
- if (result.passed) {
12854
- io.stdout('āœ… Model verify passed.');
12855
- return 0;
12856
- }
12857
- io.stderr(formatModelVerifyFailureOutput(result.message, curlCommands));
12858
- return 1;
12859
- } catch (error) {
12860
- const message = error instanceof Error ? error.message : String(error);
12861
- io.stderr([
12862
- MODEL_VERIFY_SEPARATOR,
12863
- 'āŒ Model verify failed with messages:',
12864
- '',
12865
- message,
12866
- MODEL_VERIFY_SEPARATOR
12867
- ].join('\n'));
12868
- return 1;
12679
+ if (!result.ok) throw new Error(`OpenAI token exchange failed (HTTP ${result.status}). Run midscene model siwc login again.`);
12680
+ const tokens = await result.json();
12681
+ if (!tokens || 'string' != typeof tokens.access_token || !tokens.access_token || 'string' != typeof tokens.refresh_token || !tokens.refresh_token || 'string' != typeof tokens.id_token || 'string' != typeof tokens.scope || 'number' != typeof tokens.expires_in || !Number.isFinite(tokens.expires_in) || tokens.expires_in <= 0 || 'string' != typeof tokens.token_type || 'bearer' !== tokens.token_type.toLowerCase()) throw new Error('OpenAI returned an invalid token response.');
12682
+ const scopes = tokens.scope.split(/\s+/);
12683
+ if (!scopes.includes('chatgpt.tokens.use.direct')) throw new Error('ChatGPT plan usage was not authorized. Run midscene model siwc login again and grant access.');
12684
+ const keys = dependencies?.keys ?? createRemoteJWKSet(new URL(`${ISSUER}/.well-known/jwks.json`));
12685
+ const { payload } = await jwtVerify(tokens.id_token, keys, {
12686
+ issuer: ISSUER,
12687
+ audience: clientId,
12688
+ algorithms: [
12689
+ 'RS256'
12690
+ ],
12691
+ requiredClaims: [
12692
+ 'sub',
12693
+ 'exp',
12694
+ 'iat',
12695
+ 'nonce'
12696
+ ]
12697
+ });
12698
+ if (payload.nonce !== nonce || !payload.sub) throw new Error('OpenAI login identity or nonce did not match.');
12699
+ signal.throwIfAborted();
12700
+ return {
12701
+ ext_agent_host_id: hostId,
12702
+ client_id: clientId,
12703
+ subject: payload.sub,
12704
+ issuer: ISSUER,
12705
+ ...'string' == typeof payload.email ? {
12706
+ email: payload.email
12707
+ } : {},
12708
+ access_token: tokens.access_token,
12709
+ refresh_token: tokens.refresh_token,
12710
+ id_token: tokens.id_token,
12711
+ scopes,
12712
+ expires_at: Date.now() + 1000 * tokens.expires_in
12713
+ };
12714
+ } finally{
12715
+ signal.removeEventListener('abort', abort);
12716
+ server.closeAllConnections();
12717
+ await new Promise((resolve)=>server.close(()=>resolve()));
12718
+ }
12719
+ }
12720
+ async function refreshOpenAICredentials(saved, signal, dependencies) {
12721
+ const issuer = 'https://auth.openai.com';
12722
+ signal.throwIfAborted();
12723
+ const response = await (dependencies?.fetch ?? fetch)(`${issuer}/api/accounts/oauth/token`, {
12724
+ method: 'POST',
12725
+ headers: {
12726
+ 'Content-Type': 'application/x-www-form-urlencoded'
12727
+ },
12728
+ body: new URLSearchParams({
12729
+ grant_type: 'refresh_token',
12730
+ client_id: saved.client_id,
12731
+ refresh_token: saved.refresh_token,
12732
+ resource: 'https://api.openai.com/v1'
12733
+ }),
12734
+ signal,
12735
+ redirect: 'error'
12736
+ });
12737
+ if (!response.ok) throw new Error('OpenAI refresh failed.');
12738
+ const tokens = await response.json();
12739
+ if (!tokens || 'string' != typeof tokens.access_token || !tokens.access_token || 'string' != typeof tokens.refresh_token || !tokens.refresh_token || 'number' != typeof tokens.expires_in || !Number.isFinite(tokens.expires_in) || tokens.expires_in <= 0 || 'string' != typeof tokens.token_type || 'bearer' !== tokens.token_type.toLowerCase() || 'string' != typeof tokens.scope) throw new Error('OpenAI returned an invalid refresh response.');
12740
+ const scopes = tokens.scope.split(/\s+/);
12741
+ if (!scopes.includes('chatgpt.tokens.use.direct')) throw new Error('ChatGPT plan usage is no longer authorized.');
12742
+ if (void 0 !== tokens.id_token) {
12743
+ if ('string' != typeof tokens.id_token || !tokens.id_token) throw new Error('Invalid refreshed ID token.');
12744
+ const keys = dependencies?.keys ?? createRemoteJWKSet(new URL(`${issuer}/.well-known/jwks.json`));
12745
+ const { payload } = await jwtVerify(tokens.id_token, keys, {
12746
+ issuer,
12747
+ audience: saved.client_id,
12748
+ algorithms: [
12749
+ 'RS256'
12750
+ ],
12751
+ requiredClaims: [
12752
+ 'sub',
12753
+ 'exp',
12754
+ 'iat'
12755
+ ]
12756
+ });
12757
+ if (payload.sub !== saved.subject) throw new Error('Refreshed identity does not match the saved account.');
12869
12758
  }
12759
+ signal.throwIfAborted();
12760
+ return {
12761
+ ...saved,
12762
+ access_token: tokens.access_token,
12763
+ refresh_token: tokens.refresh_token,
12764
+ id_token: tokens.id_token ?? saved.id_token,
12765
+ scopes,
12766
+ expires_at: Date.now() + 1000 * tokens.expires_in
12767
+ };
12870
12768
  }
12871
- async function runModelCommand(rawArgs, deps, io = {
12769
+ const USAGE = `Usage:
12770
+ midscene model siwc login [--no-open] [--port <port>]
12771
+ midscene model siwc refresh
12772
+
12773
+ Sign in with ChatGPT or refresh the saved credentials.
12774
+ Full credentials are saved to ~/.midscene/siwc.json. Only the access token is printed to stdout.
12775
+ Midscene does not collect your credentials.
12776
+
12777
+ Options (login):
12778
+ --no-open Print the authorization URL without opening a browser
12779
+ --port <port> Loopback callback port (default: an available port)
12780
+ --help, -h Show this help
12781
+ `;
12782
+ async function openBrowser(url) {
12783
+ const command = 'darwin' === process.platform ? 'open' : 'win32' === process.platform ? 'rundll32' : 'xdg-open';
12784
+ const args = 'win32' === process.platform ? [
12785
+ 'url.dll,FileProtocolHandler',
12786
+ url
12787
+ ] : [
12788
+ url
12789
+ ];
12790
+ await new Promise((resolve, reject)=>{
12791
+ const child = spawn(command, args, {
12792
+ stdio: 'ignore',
12793
+ shell: false
12794
+ });
12795
+ child.once('error', reject);
12796
+ child.once('spawn', ()=>{
12797
+ child.unref();
12798
+ resolve();
12799
+ });
12800
+ });
12801
+ }
12802
+ async function runSiwcCommand(args, io = {
12872
12803
  stdout: console.log,
12873
12804
  stderr: console.error
12874
- }) {
12875
- const [, action, ...restArgs] = rawArgs;
12876
- const mergedDeps = {
12877
- ...getDefaultDeps(io),
12878
- ...deps
12879
- };
12880
- if (!action || '--help' === action || '-h' === action) {
12881
- io.stdout(MODEL_COMMAND_USAGE);
12805
+ }, credentialsPath = defaultCredentialsPath()) {
12806
+ if (!args.length || args.includes('--help') || args.includes('-h')) {
12807
+ io.stdout(USAGE);
12882
12808
  return 0;
12883
12809
  }
12884
- if ('verify' === action) return runModelVerifyCommand(restArgs, mergedDeps, io);
12885
- if ('eval' === action) {
12886
- io.stderr('midscene model eval is not implemented yet. It is reserved for future model evaluation suites.');
12810
+ const [action, ...options] = args;
12811
+ if ('login' !== action && 'refresh' !== action) {
12812
+ io.stderr(`Unknown SIWC command: ${action}\n${USAGE}`);
12887
12813
  return 1;
12888
12814
  }
12889
- io.stderr(`Unknown midscene model command: ${action}\n\n${MODEL_COMMAND_USAGE}`);
12890
- return 1;
12815
+ const portOptions = {
12816
+ port: 0,
12817
+ noOpen: false
12818
+ };
12819
+ const seen = new Set();
12820
+ for(let index = 0; index < options.length; index++){
12821
+ const option = options[index];
12822
+ if ('login' !== action || seen.has(option) || ![
12823
+ '--port',
12824
+ '--no-open'
12825
+ ].includes(option)) {
12826
+ io.stderr(`Invalid midscene model siwc arguments.\n${USAGE}`);
12827
+ return 1;
12828
+ }
12829
+ seen.add(option);
12830
+ if ('--no-open' === option) portOptions.noOpen = true;
12831
+ else {
12832
+ const value = options[++index];
12833
+ if (!value || !/^\d+$/.test(value) || Number(value) > 65535) {
12834
+ io.stderr('--port must be an integer between 0 and 65535.');
12835
+ return 1;
12836
+ }
12837
+ portOptions.port = Number(value);
12838
+ }
12839
+ }
12840
+ const unlock = await lockCredentials(credentialsPath).catch((error)=>{
12841
+ io.stderr(error.message);
12842
+ });
12843
+ if (!unlock) return 1;
12844
+ const controller = new AbortController();
12845
+ const cancel = ()=>controller.abort();
12846
+ const timer = setTimeout(cancel, 300000);
12847
+ process.once('SIGINT', cancel);
12848
+ process.once('SIGTERM', cancel);
12849
+ try {
12850
+ io.stderr(source_default().cyan.bold(`šŸ”„ Starting OpenAI SIWC ${action}...`));
12851
+ const saved = await readRegistration(credentialsPath);
12852
+ const credentials = await (async ()=>{
12853
+ if ('refresh' === action) return refreshOpenAICredentials(requireCredentials(saved), controller.signal);
12854
+ const registration = saved ?? {
12855
+ ext_agent_host_id: `urn:uuid:${randomUUID()}`
12856
+ };
12857
+ if (!saved) await saveRegistration(credentialsPath, registration);
12858
+ const result = await loginWithOpenAI({
12859
+ port: portOptions.port,
12860
+ hostId: registration.ext_agent_host_id,
12861
+ clientId: registration.client_id,
12862
+ signal: controller.signal,
12863
+ onAuthorization: async (url)=>{
12864
+ io.stderr(`\n${source_default().bold('Continue with ChatGPT:')}\n${url}\n\n${source_default().gray('Waiting for authorization...')}`);
12865
+ if (!portOptions.noOpen) try {
12866
+ await openBrowser(url);
12867
+ } catch {
12868
+ io.stderr('Open the URL above in your browser to continue.');
12869
+ }
12870
+ }
12871
+ });
12872
+ if (registration.subject && result.subject !== registration.subject) throw new Error('Login identity does not match the saved account.');
12873
+ return result;
12874
+ })();
12875
+ await saveRegistration(credentialsPath, credentials);
12876
+ io.stderr('\nšŸ”‘ your Access token:');
12877
+ io.stdout(credentials.access_token);
12878
+ const expiresAt = new Intl.DateTimeFormat('en-GB', {
12879
+ dateStyle: 'medium',
12880
+ timeStyle: 'long'
12881
+ }).format(credentials.expires_at);
12882
+ const remainingMinutes = Math.ceil((credentials.expires_at - Date.now()) / 60000);
12883
+ const expiresIn = new Intl.RelativeTimeFormat('en', {
12884
+ numeric: 'always'
12885
+ }).format(60 === remainingMinutes ? 1 : remainingMinutes, 60 === remainingMinutes ? 'hour' : 'minute');
12886
+ io.stderr(`\nExpires at: ${expiresAt} (${expiresIn})`);
12887
+ io.stderr(`\n${source_default().green(`āœ… SIWC ${action} succeeded.`)}\n\nYou can use this access token as your model API Key in Midscene by setting MIDSCENE_MODEL_API_KEY.\n\nFull credentials are saved locally to:\n${source_default().cyan(credentialsPath)}\n\n${source_default().gray('Midscene does not collect your credentials.')}`);
12888
+ return 0;
12889
+ } catch {
12890
+ io.stderr(source_default().red(controller.signal.aborted ? `OpenAI ${action} cancelled or timed out.` : `OpenAI ${action} failed. Check the local credentials file permissions or run midscene model siwc login to authorize again.`));
12891
+ return 1;
12892
+ } finally{
12893
+ clearTimeout(timer);
12894
+ process.removeListener('SIGINT', cancel);
12895
+ process.removeListener('SIGTERM', cancel);
12896
+ await unlock();
12897
+ }
12891
12898
  }
12899
+ const model_command_runModelCommand = (rawArgs, deps, io = {
12900
+ stdout: console.log,
12901
+ stderr: console.error
12902
+ })=>{
12903
+ const [, action, ...restArgs] = rawArgs;
12904
+ if ('siwc' === action) return runSiwcCommand(restArgs, io);
12905
+ const withSiwcUsage = (message)=>message.replace(' midscene model verify\n', ' midscene model verify\n midscene model siwc <login|refresh> [options]\n');
12906
+ return runModelCommand(rawArgs, {
12907
+ loadDotenv: ()=>loadDotenvConfig({
12908
+ dotenvDebug: true,
12909
+ dotenvOverride: true,
12910
+ log: io.stdout
12911
+ }),
12912
+ ...deps
12913
+ }, {
12914
+ stdout: (message)=>io.stdout(withSiwcUsage(message)),
12915
+ stderr: (message)=>io.stderr(withSiwcUsage(message))
12916
+ }, 'midscene');
12917
+ };
12892
12918
  const probeWorkflowFormat = (file)=>{
12893
12919
  try {
12894
12920
  const content = (0, __rspack_external_node_fs_5ea92f0c.readFileSync)(file, 'utf8').replace(/\$\{[^}\r\n]*\}/g, 'MIDSCENE_ENV');
@@ -12937,7 +12963,7 @@ defineYamlBatchTest(test, testOptions);
12937
12963
  extraCommands: createReportCliCommands()
12938
12964
  });
12939
12965
  if ('model' === firstArg) {
12940
- const exitCode = await runModelCommand(rawArgs);
12966
+ const exitCode = await model_command_runModelCommand(rawArgs);
12941
12967
  process.exit(exitCode);
12942
12968
  return;
12943
12969
  }
@@ -13013,9 +13039,6 @@ defineYamlBatchTest(test, testOptions);
13013
13039
  assert (module) {
13014
13040
  module.exports = __rspack_external_assert;
13015
13041
  },
13016
- crypto (module) {
13017
- module.exports = __rspack_external_crypto;
13018
- },
13019
13042
  fs (module) {
13020
13043
  module.exports = __rspack_external_fs;
13021
13044
  },
@@ -16300,9 +16323,6 @@ defineYamlBatchTest(test, testOptions);
16300
16323
  YError: e1
16301
16324
  };
16302
16325
  module.exports = ue;
16303
- },
16304
- "../../node_modules/.pnpm/dotenv@16.4.5/node_modules/dotenv/package.json" (module) {
16305
- module.exports = JSON.parse('{"name":"dotenv","version":"16.4.5","description":"Loads environment variables from .env file","main":"lib/main.js","types":"lib/main.d.ts","exports":{".":{"types":"./lib/main.d.ts","require":"./lib/main.js","default":"./lib/main.js"},"./config":"./config.js","./config.js":"./config.js","./lib/env-options":"./lib/env-options.js","./lib/env-options.js":"./lib/env-options.js","./lib/cli-options":"./lib/cli-options.js","./lib/cli-options.js":"./lib/cli-options.js","./package.json":"./package.json"},"scripts":{"dts-check":"tsc --project tests/types/tsconfig.json","lint":"standard","lint-readme":"standard-markdown","pretest":"npm run lint && npm run dts-check","test":"tap tests/*.js --100 -Rspec","test:coverage":"tap --coverage-report=lcov","prerelease":"npm test","release":"standard-version"},"repository":{"type":"git","url":"git://github.com/motdotla/dotenv.git"},"funding":"https://dotenvx.com","keywords":["dotenv","env",".env","environment","variables","config","settings"],"readmeFilename":"README.md","license":"BSD-2-Clause","devDependencies":{"@definitelytyped/dtslint":"^0.0.133","@types/node":"^18.11.3","decache":"^4.6.1","sinon":"^14.0.1","standard":"^17.0.0","standard-markdown":"^7.1.0","standard-version":"^9.5.0","tap":"^16.3.0","tar":"^6.1.11","typescript":"^4.8.4"},"engines":{"node":">=12"},"browser":{"fs":false}}');
16306
16326
  }
16307
16327
  };
16308
16328
  var __webpack_module_cache__ = {};