@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 +418 -398
- package/dist/es/index.mjs.map +1 -1
- package/dist/lib/framework/index.js +6 -2
- package/dist/lib/framework/index.js.map +1 -1
- package/dist/lib/index.js +422 -398
- package/dist/lib/index.js.map +1 -1
- package/package.json +10 -9
package/dist/lib/index.js
CHANGED
|
@@ -2451,232 +2451,6 @@ var __webpack_modules__ = {
|
|
|
2451
2451
|
]
|
|
2452
2452
|
};
|
|
2453
2453
|
},
|
|
2454
|
-
"../../node_modules/.pnpm/dotenv@16.4.5/node_modules/dotenv/lib/main.js" (module, __unused_rspack_exports, __webpack_require__) {
|
|
2455
|
-
const fs = __webpack_require__("fs");
|
|
2456
|
-
const path = __webpack_require__("path");
|
|
2457
|
-
const os = __webpack_require__("os");
|
|
2458
|
-
const crypto = __webpack_require__("crypto");
|
|
2459
|
-
const packageJson = __webpack_require__("../../node_modules/.pnpm/dotenv@16.4.5/node_modules/dotenv/package.json");
|
|
2460
|
-
const version = packageJson.version;
|
|
2461
|
-
const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
|
|
2462
|
-
function parse(src) {
|
|
2463
|
-
const obj = {};
|
|
2464
|
-
let lines = src.toString();
|
|
2465
|
-
lines = lines.replace(/\r\n?/mg, '\n');
|
|
2466
|
-
let match;
|
|
2467
|
-
while(null != (match = LINE.exec(lines))){
|
|
2468
|
-
const key = match[1];
|
|
2469
|
-
let value = match[2] || '';
|
|
2470
|
-
value = value.trim();
|
|
2471
|
-
const maybeQuote = value[0];
|
|
2472
|
-
value = value.replace(/^(['"`])([\s\S]*)\1$/mg, '$2');
|
|
2473
|
-
if ('"' === maybeQuote) {
|
|
2474
|
-
value = value.replace(/\\n/g, '\n');
|
|
2475
|
-
value = value.replace(/\\r/g, '\r');
|
|
2476
|
-
}
|
|
2477
|
-
obj[key] = value;
|
|
2478
|
-
}
|
|
2479
|
-
return obj;
|
|
2480
|
-
}
|
|
2481
|
-
function _parseVault(options) {
|
|
2482
|
-
const vaultPath = _vaultPath(options);
|
|
2483
|
-
const result = DotenvModule.configDotenv({
|
|
2484
|
-
path: vaultPath
|
|
2485
|
-
});
|
|
2486
|
-
if (!result.parsed) {
|
|
2487
|
-
const err = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
|
|
2488
|
-
err.code = 'MISSING_DATA';
|
|
2489
|
-
throw err;
|
|
2490
|
-
}
|
|
2491
|
-
const keys = _dotenvKey(options).split(',');
|
|
2492
|
-
const length = keys.length;
|
|
2493
|
-
let decrypted;
|
|
2494
|
-
for(let i = 0; i < length; i++)try {
|
|
2495
|
-
const key = keys[i].trim();
|
|
2496
|
-
const attrs = _instructions(result, key);
|
|
2497
|
-
decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
|
|
2498
|
-
break;
|
|
2499
|
-
} catch (error) {
|
|
2500
|
-
if (i + 1 >= length) throw error;
|
|
2501
|
-
}
|
|
2502
|
-
return DotenvModule.parse(decrypted);
|
|
2503
|
-
}
|
|
2504
|
-
function _log(message) {
|
|
2505
|
-
console.log(`[dotenv@${version}][INFO] ${message}`);
|
|
2506
|
-
}
|
|
2507
|
-
function _warn(message) {
|
|
2508
|
-
console.log(`[dotenv@${version}][WARN] ${message}`);
|
|
2509
|
-
}
|
|
2510
|
-
function _debug(message) {
|
|
2511
|
-
console.log(`[dotenv@${version}][DEBUG] ${message}`);
|
|
2512
|
-
}
|
|
2513
|
-
function _dotenvKey(options) {
|
|
2514
|
-
if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) return options.DOTENV_KEY;
|
|
2515
|
-
if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) return process.env.DOTENV_KEY;
|
|
2516
|
-
return '';
|
|
2517
|
-
}
|
|
2518
|
-
function _instructions(result, dotenvKey) {
|
|
2519
|
-
let uri;
|
|
2520
|
-
try {
|
|
2521
|
-
uri = new URL(dotenvKey);
|
|
2522
|
-
} catch (error) {
|
|
2523
|
-
if ('ERR_INVALID_URL' === error.code) {
|
|
2524
|
-
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');
|
|
2525
|
-
err.code = 'INVALID_DOTENV_KEY';
|
|
2526
|
-
throw err;
|
|
2527
|
-
}
|
|
2528
|
-
throw error;
|
|
2529
|
-
}
|
|
2530
|
-
const key = uri.password;
|
|
2531
|
-
if (!key) {
|
|
2532
|
-
const err = new Error('INVALID_DOTENV_KEY: Missing key part');
|
|
2533
|
-
err.code = 'INVALID_DOTENV_KEY';
|
|
2534
|
-
throw err;
|
|
2535
|
-
}
|
|
2536
|
-
const environment = uri.searchParams.get('environment');
|
|
2537
|
-
if (!environment) {
|
|
2538
|
-
const err = new Error('INVALID_DOTENV_KEY: Missing environment part');
|
|
2539
|
-
err.code = 'INVALID_DOTENV_KEY';
|
|
2540
|
-
throw err;
|
|
2541
|
-
}
|
|
2542
|
-
const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
|
|
2543
|
-
const ciphertext = result.parsed[environmentKey];
|
|
2544
|
-
if (!ciphertext) {
|
|
2545
|
-
const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
|
|
2546
|
-
err.code = 'NOT_FOUND_DOTENV_ENVIRONMENT';
|
|
2547
|
-
throw err;
|
|
2548
|
-
}
|
|
2549
|
-
return {
|
|
2550
|
-
ciphertext,
|
|
2551
|
-
key
|
|
2552
|
-
};
|
|
2553
|
-
}
|
|
2554
|
-
function _vaultPath(options) {
|
|
2555
|
-
let possibleVaultPath = null;
|
|
2556
|
-
if (options && options.path && options.path.length > 0) if (Array.isArray(options.path)) {
|
|
2557
|
-
for (const filepath of options.path)if (fs.existsSync(filepath)) possibleVaultPath = filepath.endsWith('.vault') ? filepath : `${filepath}.vault`;
|
|
2558
|
-
} else possibleVaultPath = options.path.endsWith('.vault') ? options.path : `${options.path}.vault`;
|
|
2559
|
-
else possibleVaultPath = path.resolve(process.cwd(), '.env.vault');
|
|
2560
|
-
if (fs.existsSync(possibleVaultPath)) return possibleVaultPath;
|
|
2561
|
-
return null;
|
|
2562
|
-
}
|
|
2563
|
-
function _resolveHome(envPath) {
|
|
2564
|
-
return '~' === envPath[0] ? path.join(os.homedir(), envPath.slice(1)) : envPath;
|
|
2565
|
-
}
|
|
2566
|
-
function _configVault(options) {
|
|
2567
|
-
_log('Loading env from encrypted .env.vault');
|
|
2568
|
-
const parsed = DotenvModule._parseVault(options);
|
|
2569
|
-
let processEnv = process.env;
|
|
2570
|
-
if (options && null != options.processEnv) processEnv = options.processEnv;
|
|
2571
|
-
DotenvModule.populate(processEnv, parsed, options);
|
|
2572
|
-
return {
|
|
2573
|
-
parsed
|
|
2574
|
-
};
|
|
2575
|
-
}
|
|
2576
|
-
function configDotenv(options) {
|
|
2577
|
-
const dotenvPath = path.resolve(process.cwd(), '.env');
|
|
2578
|
-
let encoding = 'utf8';
|
|
2579
|
-
const debug = Boolean(options && options.debug);
|
|
2580
|
-
if (options && options.encoding) encoding = options.encoding;
|
|
2581
|
-
else if (debug) _debug('No encoding is specified. UTF-8 is used by default');
|
|
2582
|
-
let optionPaths = [
|
|
2583
|
-
dotenvPath
|
|
2584
|
-
];
|
|
2585
|
-
if (options && options.path) if (Array.isArray(options.path)) {
|
|
2586
|
-
optionPaths = [];
|
|
2587
|
-
for (const filepath of options.path)optionPaths.push(_resolveHome(filepath));
|
|
2588
|
-
} else optionPaths = [
|
|
2589
|
-
_resolveHome(options.path)
|
|
2590
|
-
];
|
|
2591
|
-
let lastError;
|
|
2592
|
-
const parsedAll = {};
|
|
2593
|
-
for (const path of optionPaths)try {
|
|
2594
|
-
const parsed = DotenvModule.parse(fs.readFileSync(path, {
|
|
2595
|
-
encoding
|
|
2596
|
-
}));
|
|
2597
|
-
DotenvModule.populate(parsedAll, parsed, options);
|
|
2598
|
-
} catch (e1) {
|
|
2599
|
-
if (debug) _debug(`Failed to load ${path} ${e1.message}`);
|
|
2600
|
-
lastError = e1;
|
|
2601
|
-
}
|
|
2602
|
-
let processEnv = process.env;
|
|
2603
|
-
if (options && null != options.processEnv) processEnv = options.processEnv;
|
|
2604
|
-
DotenvModule.populate(processEnv, parsedAll, options);
|
|
2605
|
-
if (lastError) return {
|
|
2606
|
-
parsed: parsedAll,
|
|
2607
|
-
error: lastError
|
|
2608
|
-
};
|
|
2609
|
-
return {
|
|
2610
|
-
parsed: parsedAll
|
|
2611
|
-
};
|
|
2612
|
-
}
|
|
2613
|
-
function config(options) {
|
|
2614
|
-
if (0 === _dotenvKey(options).length) return DotenvModule.configDotenv(options);
|
|
2615
|
-
const vaultPath = _vaultPath(options);
|
|
2616
|
-
if (!vaultPath) {
|
|
2617
|
-
_warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`);
|
|
2618
|
-
return DotenvModule.configDotenv(options);
|
|
2619
|
-
}
|
|
2620
|
-
return DotenvModule._configVault(options);
|
|
2621
|
-
}
|
|
2622
|
-
function decrypt(encrypted, keyStr) {
|
|
2623
|
-
const key = Buffer.from(keyStr.slice(-64), 'hex');
|
|
2624
|
-
let ciphertext = Buffer.from(encrypted, 'base64');
|
|
2625
|
-
const nonce = ciphertext.subarray(0, 12);
|
|
2626
|
-
const authTag = ciphertext.subarray(-16);
|
|
2627
|
-
ciphertext = ciphertext.subarray(12, -16);
|
|
2628
|
-
try {
|
|
2629
|
-
const aesgcm = crypto.createDecipheriv('aes-256-gcm', key, nonce);
|
|
2630
|
-
aesgcm.setAuthTag(authTag);
|
|
2631
|
-
return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
|
|
2632
|
-
} catch (error) {
|
|
2633
|
-
const isRange = error instanceof RangeError;
|
|
2634
|
-
const invalidKeyLength = 'Invalid key length' === error.message;
|
|
2635
|
-
const decryptionFailed = 'Unsupported state or unable to authenticate data' === error.message;
|
|
2636
|
-
if (isRange || invalidKeyLength) {
|
|
2637
|
-
const err = new Error('INVALID_DOTENV_KEY: It must be 64 characters long (or more)');
|
|
2638
|
-
err.code = 'INVALID_DOTENV_KEY';
|
|
2639
|
-
throw err;
|
|
2640
|
-
}
|
|
2641
|
-
if (decryptionFailed) {
|
|
2642
|
-
const err = new Error('DECRYPTION_FAILED: Please check your DOTENV_KEY');
|
|
2643
|
-
err.code = 'DECRYPTION_FAILED';
|
|
2644
|
-
throw err;
|
|
2645
|
-
}
|
|
2646
|
-
throw error;
|
|
2647
|
-
}
|
|
2648
|
-
}
|
|
2649
|
-
function populate(processEnv, parsed, options = {}) {
|
|
2650
|
-
const debug = Boolean(options && options.debug);
|
|
2651
|
-
const override = Boolean(options && options.override);
|
|
2652
|
-
if ('object' != typeof parsed) {
|
|
2653
|
-
const err = new Error('OBJECT_REQUIRED: Please check the processEnv argument being passed to populate');
|
|
2654
|
-
err.code = 'OBJECT_REQUIRED';
|
|
2655
|
-
throw err;
|
|
2656
|
-
}
|
|
2657
|
-
for (const key of Object.keys(parsed))if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
|
|
2658
|
-
if (true === override) processEnv[key] = parsed[key];
|
|
2659
|
-
if (debug) true === override ? _debug(`"${key}" is already defined and WAS overwritten`) : _debug(`"${key}" is already defined and was NOT overwritten`);
|
|
2660
|
-
} else processEnv[key] = parsed[key];
|
|
2661
|
-
}
|
|
2662
|
-
const DotenvModule = {
|
|
2663
|
-
configDotenv,
|
|
2664
|
-
_configVault,
|
|
2665
|
-
_parseVault,
|
|
2666
|
-
config,
|
|
2667
|
-
decrypt,
|
|
2668
|
-
parse,
|
|
2669
|
-
populate
|
|
2670
|
-
};
|
|
2671
|
-
module.exports.configDotenv = DotenvModule.configDotenv;
|
|
2672
|
-
module.exports._configVault = DotenvModule._configVault;
|
|
2673
|
-
module.exports._parseVault = DotenvModule._parseVault;
|
|
2674
|
-
module.exports.config = DotenvModule.config;
|
|
2675
|
-
module.exports.decrypt = DotenvModule.decrypt;
|
|
2676
|
-
module.exports.parse = DotenvModule.parse;
|
|
2677
|
-
module.exports.populate = DotenvModule.populate;
|
|
2678
|
-
module.exports = DotenvModule;
|
|
2679
|
-
},
|
|
2680
2454
|
"../../node_modules/.pnpm/emoji-regex@8.0.0/node_modules/emoji-regex/index.js" (module) {
|
|
2681
2455
|
"use strict";
|
|
2682
2456
|
module.exports = function() {
|
|
@@ -3014,7 +2788,7 @@ var __webpack_modules__ = {
|
|
|
3014
2788
|
});
|
|
3015
2789
|
var external_node_fs_ = __webpack_require__("node:fs");
|
|
3016
2790
|
var external_node_path_ = __webpack_require__("node:path");
|
|
3017
|
-
|
|
2791
|
+
var external_node_crypto_ = __webpack_require__("node:crypto");
|
|
3018
2792
|
var core_ = __webpack_require__("@midscene/core");
|
|
3019
2793
|
const common_namespaceObject = require("@midscene/shared/common");
|
|
3020
2794
|
var logger_ = __webpack_require__("@midscene/shared/logger");
|
|
@@ -3171,7 +2945,7 @@ var __webpack_modules__ = {
|
|
|
3171
2945
|
const safeReportNamePart = (value)=>value.replace(/[:*?"<>|# ]/g, '-');
|
|
3172
2946
|
const createRetryReportName = (file)=>{
|
|
3173
2947
|
const fileName = (0, external_node_path_.basename)(file, (0, external_node_path_.extname)(file)) || 'yaml';
|
|
3174
|
-
const fileHash = (0,
|
|
2948
|
+
const fileHash = (0, external_node_crypto_.createHash)('sha1').update((0, external_node_path_.resolve)(file)).digest('hex').slice(0, 8);
|
|
3175
2949
|
return `${safeReportNamePart(fileName)}-${fileHash}-retry-attempts`;
|
|
3176
2950
|
};
|
|
3177
2951
|
const createRetryAttemptReport = (result)=>{
|
|
@@ -4814,7 +4588,7 @@ defineYamlBatchTest(test, testOptions);
|
|
|
4814
4588
|
var core_ = __webpack_require__("@midscene/core");
|
|
4815
4589
|
const cli_namespaceObject = require("@midscene/shared/cli");
|
|
4816
4590
|
var package_namespaceObject = {
|
|
4817
|
-
rE: "1.
|
|
4591
|
+
rE: "1.15.0"
|
|
4818
4592
|
};
|
|
4819
4593
|
var external_node_fs_ = __webpack_require__("node:fs");
|
|
4820
4594
|
var external_node_fs_namespaceObject = /*#__PURE__*/ __webpack_require__.t(external_node_fs_, 2);
|
|
@@ -10437,9 +10211,11 @@ Homepage: https://midscenejs.com
|
|
|
10437
10211
|
Github: https://github.com/web-infra-dev/midscene
|
|
10438
10212
|
|
|
10439
10213
|
Usage:
|
|
10214
|
+
$0 model siwc <login|refresh> [options]
|
|
10440
10215
|
$0 [options] <path-to-yaml-script-file-or-directory>
|
|
10441
10216
|
$0 [options] --files <yaml-file1> <yaml-file2 ...>
|
|
10442
|
-
$0 [options] --config <path-to-config-yaml-file
|
|
10217
|
+
$0 [options] --config <path-to-config-yaml-file>
|
|
10218
|
+
$0 model verify Check model connectivity and Midscene compatibility`).options({
|
|
10443
10219
|
files: {
|
|
10444
10220
|
type: 'array',
|
|
10445
10221
|
string: true,
|
|
@@ -10497,7 +10273,7 @@ Usage:
|
|
|
10497
10273
|
type: 'boolean',
|
|
10498
10274
|
description: 'Whether to dismiss the HarmonyOS keyboard after text input'
|
|
10499
10275
|
}
|
|
10500
|
-
}).version('version', 'Show version number', "1.
|
|
10276
|
+
}).version('version', 'Show version number', "1.15.0").help().epilogue(`For complete list of configuration options, please visit:
|
|
10501
10277
|
⢠Web options: https://midscenejs.com/automate-with-scripts-in-yaml#the-web-part
|
|
10502
10278
|
⢠Android options: https://midscenejs.com/automate-with-scripts-in-yaml#the-android-part
|
|
10503
10279
|
⢠HarmonyOS options: https://midscenejs.com/automate-with-scripts-in-yaml#the-harmony-part
|
|
@@ -12747,188 +12523,440 @@ Native cases/steps Test projects use the midscene-test command.`).wrap(yargs().t
|
|
|
12747
12523
|
globalConfig: pickYamlTargetConfig(options)
|
|
12748
12524
|
};
|
|
12749
12525
|
}
|
|
12750
|
-
|
|
12751
|
-
var main_default = /*#__PURE__*/ __webpack_require__.n(main);
|
|
12752
|
-
function loadDotenvConfig(options = {}) {
|
|
12753
|
-
const dotEnvConfigFile = (0, external_node_path_.join)(options.cwd ?? process.cwd(), '.env');
|
|
12754
|
-
if (!(0, external_node_fs_.existsSync)(dotEnvConfigFile)) return;
|
|
12755
|
-
options.log?.(` Env file: ${dotEnvConfigFile}`);
|
|
12756
|
-
main_default().config({
|
|
12757
|
-
path: dotEnvConfigFile,
|
|
12758
|
-
debug: options.dotenvDebug,
|
|
12759
|
-
override: options.dotenvOverride
|
|
12760
|
-
});
|
|
12761
|
-
}
|
|
12526
|
+
const model_command_namespaceObject = require("@midscene/test/internal/model-command");
|
|
12762
12527
|
var framework = __webpack_require__("./src/framework/index.ts");
|
|
12763
|
-
const
|
|
12528
|
+
const external_node_child_process_namespaceObject = require("node:child_process");
|
|
12529
|
+
var external_node_crypto_ = __webpack_require__("node:crypto");
|
|
12764
12530
|
var chalk_source = __webpack_require__("../../node_modules/.pnpm/chalk@4.1.2/node_modules/chalk/source/index.js");
|
|
12765
12531
|
var source_default = /*#__PURE__*/ __webpack_require__.n(chalk_source);
|
|
12766
|
-
const
|
|
12767
|
-
const
|
|
12768
|
-
|
|
12769
|
-
|
|
12770
|
-
|
|
12771
|
-
|
|
12772
|
-
|
|
12773
|
-
|
|
12774
|
-
|
|
12775
|
-
|
|
12776
|
-
|
|
12532
|
+
const external_node_os_namespaceObject = require("node:os");
|
|
12533
|
+
const defaultCredentialsPath = ()=>(0, external_node_path_.join)((0, external_node_os_namespaceObject.homedir)(), '.midscene', 'siwc.json');
|
|
12534
|
+
async function readCredentialsFile(path) {
|
|
12535
|
+
const content = await (0, promises_namespaceObject.readFile)(path, 'utf8').catch((error)=>{
|
|
12536
|
+
if ('ENOENT' === error.code) return;
|
|
12537
|
+
throw new Error('Unable to read the local SIWC credentials file.');
|
|
12538
|
+
});
|
|
12539
|
+
if (void 0 === content) return;
|
|
12540
|
+
try {
|
|
12541
|
+
const value = JSON.parse(content);
|
|
12542
|
+
if (!value || 'string' != typeof value.credentials?.ext_agent_host_id || !value.credentials.ext_agent_host_id) throw new Error('Invalid SIWC credentials record.');
|
|
12543
|
+
return value;
|
|
12544
|
+
} catch {
|
|
12545
|
+
throw new Error('Invalid local SIWC credentials file.');
|
|
12546
|
+
}
|
|
12777
12547
|
}
|
|
12778
|
-
function
|
|
12779
|
-
|
|
12780
|
-
if (normalizedBaseURL.endsWith('/chat/completions')) return normalizedBaseURL;
|
|
12781
|
-
return `${normalizedBaseURL}/chat/completions`;
|
|
12548
|
+
async function readRegistration(path) {
|
|
12549
|
+
return (await readCredentialsFile(path))?.credentials;
|
|
12782
12550
|
}
|
|
12783
|
-
function
|
|
12784
|
-
const
|
|
12785
|
-
|
|
12786
|
-
|
|
12787
|
-
|
|
12788
|
-
|
|
12789
|
-
|
|
12790
|
-
|
|
12791
|
-
]
|
|
12551
|
+
async function saveRegistration(path, value) {
|
|
12552
|
+
const saved = await readCredentialsFile(path);
|
|
12553
|
+
const now = new Date().toISOString();
|
|
12554
|
+
const record = {
|
|
12555
|
+
...saved,
|
|
12556
|
+
created_at: saved?.created_at ?? now,
|
|
12557
|
+
updated_at: now,
|
|
12558
|
+
credentials: value
|
|
12792
12559
|
};
|
|
12793
|
-
|
|
12794
|
-
|
|
12795
|
-
|
|
12796
|
-
|
|
12797
|
-
|
|
12798
|
-
|
|
12799
|
-
|
|
12800
|
-
|
|
12801
|
-
|
|
12802
|
-
|
|
12803
|
-
apiKey: modelConfig.openaiApiKey || '',
|
|
12804
|
-
modelName: modelConfig.modelName
|
|
12805
|
-
});
|
|
12806
|
-
}
|
|
12807
|
-
function buildModelVerifyCurlCommands(configs) {
|
|
12808
|
-
const commandMap = new Map();
|
|
12809
|
-
for (const item of configs){
|
|
12810
|
-
const key = buildCurlDedupKey(item.modelConfig);
|
|
12811
|
-
const existing = commandMap.get(key);
|
|
12812
|
-
if (existing) {
|
|
12813
|
-
existing.intents.push(item.intent);
|
|
12814
|
-
continue;
|
|
12815
|
-
}
|
|
12816
|
-
commandMap.set(key, {
|
|
12817
|
-
intents: [
|
|
12818
|
-
item.intent
|
|
12819
|
-
],
|
|
12820
|
-
curl: buildCurlCommand(item.modelConfig),
|
|
12821
|
-
usesDefaultBaseURL: !item.modelConfig.openaiBaseURL
|
|
12560
|
+
const temporary = `${path}.${(0, external_node_crypto_.randomUUID)()}.tmp`;
|
|
12561
|
+
try {
|
|
12562
|
+
await (0, promises_namespaceObject.writeFile)(temporary, `${JSON.stringify(record, null, 2)}\n`, {
|
|
12563
|
+
mode: 384,
|
|
12564
|
+
flag: 'wx'
|
|
12565
|
+
});
|
|
12566
|
+
await (0, promises_namespaceObject.rename)(temporary, path);
|
|
12567
|
+
} finally{
|
|
12568
|
+
await (0, promises_namespaceObject.rm)(temporary, {
|
|
12569
|
+
force: true
|
|
12822
12570
|
});
|
|
12823
12571
|
}
|
|
12824
|
-
return [
|
|
12825
|
-
...commandMap.values()
|
|
12826
|
-
];
|
|
12827
12572
|
}
|
|
12828
|
-
function
|
|
12829
|
-
|
|
12830
|
-
|
|
12831
|
-
|
|
12832
|
-
|
|
12833
|
-
}
|
|
12834
|
-
|
|
12835
|
-
|
|
12836
|
-
|
|
12837
|
-
|
|
12838
|
-
|
|
12839
|
-
'',
|
|
12840
|
-
|
|
12841
|
-
|
|
12842
|
-
|
|
12843
|
-
|
|
12844
|
-
|
|
12845
|
-
|
|
12846
|
-
].join('\n');
|
|
12573
|
+
async function lockCredentials(path) {
|
|
12574
|
+
await (0, promises_namespaceObject.mkdir)((0, external_node_path_.dirname)(path), {
|
|
12575
|
+
recursive: true,
|
|
12576
|
+
mode: 448
|
|
12577
|
+
});
|
|
12578
|
+
const lockPath = `${path}.lock`;
|
|
12579
|
+
try {
|
|
12580
|
+
await (0, promises_namespaceObject.mkdir)(lockPath, {
|
|
12581
|
+
mode: 448
|
|
12582
|
+
});
|
|
12583
|
+
} catch (error) {
|
|
12584
|
+
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.`);
|
|
12585
|
+
throw new Error('Unable to lock the local SIWC credentials file.');
|
|
12586
|
+
}
|
|
12587
|
+
return ()=>(0, promises_namespaceObject.rm)(lockPath, {
|
|
12588
|
+
recursive: true,
|
|
12589
|
+
force: true
|
|
12590
|
+
});
|
|
12847
12591
|
}
|
|
12848
|
-
function
|
|
12849
|
-
|
|
12850
|
-
|
|
12851
|
-
|
|
12852
|
-
|
|
12853
|
-
|
|
12854
|
-
|
|
12592
|
+
function requireCredentials(value) {
|
|
12593
|
+
if (!value || [
|
|
12594
|
+
'client_id',
|
|
12595
|
+
'subject',
|
|
12596
|
+
'issuer',
|
|
12597
|
+
'access_token',
|
|
12598
|
+
'refresh_token',
|
|
12599
|
+
'id_token'
|
|
12600
|
+
].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.');
|
|
12601
|
+
return value;
|
|
12602
|
+
}
|
|
12603
|
+
const external_node_http_namespaceObject = require("node:http");
|
|
12604
|
+
const external_jose_namespaceObject = require("jose");
|
|
12605
|
+
const ISSUER = 'https://auth.openai.com';
|
|
12606
|
+
const RESOURCE = 'https://api.openai.com/v1';
|
|
12607
|
+
const SCOPES = 'openid profile email offline_access resource.invoke chatgpt.tokens.use.direct';
|
|
12608
|
+
async function loginWithOpenAI(options, dependencies) {
|
|
12609
|
+
const hostId = options.hostId ?? `urn:uuid:${(0, external_node_crypto_.randomUUID)()}`;
|
|
12610
|
+
const existingClientId = options.clientId;
|
|
12611
|
+
const state = (0, external_node_crypto_.randomBytes)(32).toString('base64url');
|
|
12612
|
+
const nonce = (0, external_node_crypto_.randomBytes)(32).toString('base64url');
|
|
12613
|
+
const verifier = (0, external_node_crypto_.randomBytes)(32).toString('base64url');
|
|
12614
|
+
const signal = options.signal;
|
|
12615
|
+
signal.throwIfAborted();
|
|
12616
|
+
const server = (0, external_node_http_namespaceObject.createServer)();
|
|
12617
|
+
const callback = new Promise((resolve, reject)=>{
|
|
12618
|
+
const consumed = {
|
|
12619
|
+
value: false
|
|
12620
|
+
};
|
|
12621
|
+
server.on('request', (request, response)=>{
|
|
12622
|
+
response.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
12623
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
12624
|
+
response.setHeader('Referrer-Policy', 'no-referrer');
|
|
12625
|
+
const url = new URL(request.url || '/', 'http://127.0.0.1');
|
|
12626
|
+
if ('GET' !== request.method || '/auth/callback' !== url.pathname) return void response.writeHead(404).end('Not found');
|
|
12627
|
+
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.');
|
|
12628
|
+
if (consumed.value) return void response.writeHead(409).end('Authorization already received.');
|
|
12629
|
+
if (url.searchParams.has('error')) {
|
|
12630
|
+
consumed.value = true;
|
|
12631
|
+
response.writeHead(400).end('Authorization was not granted. Return to the terminal.');
|
|
12632
|
+
reject(new Error('OpenAI authorization was denied or failed.'));
|
|
12633
|
+
return;
|
|
12634
|
+
}
|
|
12635
|
+
const code = url.searchParams.get('code');
|
|
12636
|
+
const clientId = url.searchParams.get('client_id') || existingClientId;
|
|
12637
|
+
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.');
|
|
12638
|
+
consumed.value = true;
|
|
12639
|
+
response.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
12640
|
+
response.end(`<!doctype html>
|
|
12641
|
+
<html lang="en">
|
|
12642
|
+
<head>
|
|
12643
|
+
<meta charset="utf-8">
|
|
12644
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
12645
|
+
<title>Authorization received Ā· Midscene</title>
|
|
12646
|
+
<style>
|
|
12647
|
+
* { box-sizing: border-box; }
|
|
12648
|
+
body { margin: 0; min-height: 100vh; display: grid; place-items: center; padding: 24px; background: #f6f7f9; color: #202124; font-family: system-ui, sans-serif; }
|
|
12649
|
+
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; }
|
|
12650
|
+
h1 { margin: 0 0 20px; font-size: 24px; line-height: 1.4; }
|
|
12651
|
+
h1 span { color: #16834a; }
|
|
12652
|
+
p { margin: 8px 0 0; color: #596273; font-size: 15px; line-height: 1.6; }
|
|
12653
|
+
</style>
|
|
12654
|
+
</head>
|
|
12655
|
+
<body>
|
|
12656
|
+
<main>
|
|
12657
|
+
<h1><span aria-hidden="true">ā</span> Authorization received</h1>
|
|
12658
|
+
<p>You can close this tab.</p>
|
|
12659
|
+
<p>Check your terminal for the sign-in result and access token.</p>
|
|
12660
|
+
</main>
|
|
12661
|
+
</body>
|
|
12662
|
+
</html>`);
|
|
12663
|
+
resolve({
|
|
12664
|
+
code,
|
|
12665
|
+
clientId
|
|
12855
12666
|
});
|
|
12856
|
-
}
|
|
12857
|
-
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
|
|
12861
|
-
|
|
12667
|
+
});
|
|
12668
|
+
server.on('error', reject);
|
|
12669
|
+
});
|
|
12670
|
+
callback.catch(()=>void 0);
|
|
12671
|
+
const abort = ()=>server.emit('error', new Error('OpenAI login cancelled or timed out.'));
|
|
12672
|
+
signal.addEventListener('abort', abort, {
|
|
12673
|
+
once: true
|
|
12674
|
+
});
|
|
12862
12675
|
try {
|
|
12863
|
-
|
|
12864
|
-
|
|
12865
|
-
|
|
12866
|
-
|
|
12867
|
-
|
|
12868
|
-
|
|
12869
|
-
|
|
12870
|
-
|
|
12871
|
-
const
|
|
12872
|
-
const
|
|
12873
|
-
|
|
12874
|
-
|
|
12875
|
-
{
|
|
12876
|
-
|
|
12877
|
-
modelConfig: defaultModelConfig
|
|
12676
|
+
await new Promise((resolve, reject)=>{
|
|
12677
|
+
server.once('error', reject);
|
|
12678
|
+
server.listen(options.port ?? 0, '127.0.0.1', ()=>{
|
|
12679
|
+
server.removeListener('error', reject);
|
|
12680
|
+
resolve();
|
|
12681
|
+
});
|
|
12682
|
+
});
|
|
12683
|
+
signal.throwIfAborted();
|
|
12684
|
+
const redirectUri = `http://127.0.0.1:${server.address().port}/auth/callback`;
|
|
12685
|
+
const authorization = new URL(`${ISSUER}/api/accounts/authorize`);
|
|
12686
|
+
authorization.search = new URLSearchParams({
|
|
12687
|
+
client_id: existingClientId ?? 'dynamic_agent_client',
|
|
12688
|
+
...existingClientId ? {} : {
|
|
12689
|
+
agent_name_hint: 'Midscene'
|
|
12878
12690
|
},
|
|
12879
|
-
|
|
12880
|
-
|
|
12881
|
-
|
|
12691
|
+
ext_agent_host_id: hostId,
|
|
12692
|
+
response_type: 'code',
|
|
12693
|
+
redirect_uri: redirectUri,
|
|
12694
|
+
scope: SCOPES,
|
|
12695
|
+
resource: RESOURCE,
|
|
12696
|
+
state,
|
|
12697
|
+
nonce,
|
|
12698
|
+
code_challenge_method: 'S256',
|
|
12699
|
+
code_challenge: (0, external_node_crypto_.createHash)('sha256').update(verifier).digest('base64url')
|
|
12700
|
+
}).toString();
|
|
12701
|
+
await options.onAuthorization(authorization.toString());
|
|
12702
|
+
const { code, clientId } = await callback;
|
|
12703
|
+
signal.throwIfAborted();
|
|
12704
|
+
const request = dependencies?.fetch ?? fetch;
|
|
12705
|
+
const result = await request(`${ISSUER}/api/accounts/oauth/token`, {
|
|
12706
|
+
method: 'POST',
|
|
12707
|
+
headers: {
|
|
12708
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
12882
12709
|
},
|
|
12883
|
-
{
|
|
12884
|
-
|
|
12885
|
-
|
|
12886
|
-
|
|
12887
|
-
|
|
12888
|
-
|
|
12889
|
-
|
|
12890
|
-
|
|
12891
|
-
|
|
12710
|
+
body: new URLSearchParams({
|
|
12711
|
+
grant_type: 'authorization_code',
|
|
12712
|
+
client_id: clientId,
|
|
12713
|
+
code,
|
|
12714
|
+
code_verifier: verifier,
|
|
12715
|
+
redirect_uri: redirectUri,
|
|
12716
|
+
resource: RESOURCE
|
|
12717
|
+
}),
|
|
12718
|
+
signal,
|
|
12719
|
+
redirect: 'error'
|
|
12892
12720
|
});
|
|
12893
|
-
if (result.
|
|
12894
|
-
|
|
12895
|
-
|
|
12896
|
-
|
|
12897
|
-
|
|
12898
|
-
|
|
12899
|
-
|
|
12900
|
-
|
|
12901
|
-
|
|
12902
|
-
|
|
12903
|
-
|
|
12904
|
-
|
|
12905
|
-
|
|
12906
|
-
|
|
12907
|
-
|
|
12908
|
-
|
|
12721
|
+
if (!result.ok) throw new Error(`OpenAI token exchange failed (HTTP ${result.status}). Run midscene model siwc login again.`);
|
|
12722
|
+
const tokens = await result.json();
|
|
12723
|
+
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.');
|
|
12724
|
+
const scopes = tokens.scope.split(/\s+/);
|
|
12725
|
+
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.');
|
|
12726
|
+
const keys = dependencies?.keys ?? (0, external_jose_namespaceObject.createRemoteJWKSet)(new URL(`${ISSUER}/.well-known/jwks.json`));
|
|
12727
|
+
const { payload } = await (0, external_jose_namespaceObject.jwtVerify)(tokens.id_token, keys, {
|
|
12728
|
+
issuer: ISSUER,
|
|
12729
|
+
audience: clientId,
|
|
12730
|
+
algorithms: [
|
|
12731
|
+
'RS256'
|
|
12732
|
+
],
|
|
12733
|
+
requiredClaims: [
|
|
12734
|
+
'sub',
|
|
12735
|
+
'exp',
|
|
12736
|
+
'iat',
|
|
12737
|
+
'nonce'
|
|
12738
|
+
]
|
|
12739
|
+
});
|
|
12740
|
+
if (payload.nonce !== nonce || !payload.sub) throw new Error('OpenAI login identity or nonce did not match.');
|
|
12741
|
+
signal.throwIfAborted();
|
|
12742
|
+
return {
|
|
12743
|
+
ext_agent_host_id: hostId,
|
|
12744
|
+
client_id: clientId,
|
|
12745
|
+
subject: payload.sub,
|
|
12746
|
+
issuer: ISSUER,
|
|
12747
|
+
...'string' == typeof payload.email ? {
|
|
12748
|
+
email: payload.email
|
|
12749
|
+
} : {},
|
|
12750
|
+
access_token: tokens.access_token,
|
|
12751
|
+
refresh_token: tokens.refresh_token,
|
|
12752
|
+
id_token: tokens.id_token,
|
|
12753
|
+
scopes,
|
|
12754
|
+
expires_at: Date.now() + 1000 * tokens.expires_in
|
|
12755
|
+
};
|
|
12756
|
+
} finally{
|
|
12757
|
+
signal.removeEventListener('abort', abort);
|
|
12758
|
+
server.closeAllConnections();
|
|
12759
|
+
await new Promise((resolve)=>server.close(()=>resolve()));
|
|
12760
|
+
}
|
|
12761
|
+
}
|
|
12762
|
+
async function refreshOpenAICredentials(saved, signal, dependencies) {
|
|
12763
|
+
const issuer = 'https://auth.openai.com';
|
|
12764
|
+
signal.throwIfAborted();
|
|
12765
|
+
const response = await (dependencies?.fetch ?? fetch)(`${issuer}/api/accounts/oauth/token`, {
|
|
12766
|
+
method: 'POST',
|
|
12767
|
+
headers: {
|
|
12768
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
12769
|
+
},
|
|
12770
|
+
body: new URLSearchParams({
|
|
12771
|
+
grant_type: 'refresh_token',
|
|
12772
|
+
client_id: saved.client_id,
|
|
12773
|
+
refresh_token: saved.refresh_token,
|
|
12774
|
+
resource: 'https://api.openai.com/v1'
|
|
12775
|
+
}),
|
|
12776
|
+
signal,
|
|
12777
|
+
redirect: 'error'
|
|
12778
|
+
});
|
|
12779
|
+
if (!response.ok) throw new Error('OpenAI refresh failed.');
|
|
12780
|
+
const tokens = await response.json();
|
|
12781
|
+
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.');
|
|
12782
|
+
const scopes = tokens.scope.split(/\s+/);
|
|
12783
|
+
if (!scopes.includes('chatgpt.tokens.use.direct')) throw new Error('ChatGPT plan usage is no longer authorized.');
|
|
12784
|
+
if (void 0 !== tokens.id_token) {
|
|
12785
|
+
if ('string' != typeof tokens.id_token || !tokens.id_token) throw new Error('Invalid refreshed ID token.');
|
|
12786
|
+
const keys = dependencies?.keys ?? (0, external_jose_namespaceObject.createRemoteJWKSet)(new URL(`${issuer}/.well-known/jwks.json`));
|
|
12787
|
+
const { payload } = await (0, external_jose_namespaceObject.jwtVerify)(tokens.id_token, keys, {
|
|
12788
|
+
issuer,
|
|
12789
|
+
audience: saved.client_id,
|
|
12790
|
+
algorithms: [
|
|
12791
|
+
'RS256'
|
|
12792
|
+
],
|
|
12793
|
+
requiredClaims: [
|
|
12794
|
+
'sub',
|
|
12795
|
+
'exp',
|
|
12796
|
+
'iat'
|
|
12797
|
+
]
|
|
12798
|
+
});
|
|
12799
|
+
if (payload.sub !== saved.subject) throw new Error('Refreshed identity does not match the saved account.');
|
|
12909
12800
|
}
|
|
12801
|
+
signal.throwIfAborted();
|
|
12802
|
+
return {
|
|
12803
|
+
...saved,
|
|
12804
|
+
access_token: tokens.access_token,
|
|
12805
|
+
refresh_token: tokens.refresh_token,
|
|
12806
|
+
id_token: tokens.id_token ?? saved.id_token,
|
|
12807
|
+
scopes,
|
|
12808
|
+
expires_at: Date.now() + 1000 * tokens.expires_in
|
|
12809
|
+
};
|
|
12810
|
+
}
|
|
12811
|
+
const USAGE = `Usage:
|
|
12812
|
+
midscene model siwc login [--no-open] [--port <port>]
|
|
12813
|
+
midscene model siwc refresh
|
|
12814
|
+
|
|
12815
|
+
Sign in with ChatGPT or refresh the saved credentials.
|
|
12816
|
+
Full credentials are saved to ~/.midscene/siwc.json. Only the access token is printed to stdout.
|
|
12817
|
+
Midscene does not collect your credentials.
|
|
12818
|
+
|
|
12819
|
+
Options (login):
|
|
12820
|
+
--no-open Print the authorization URL without opening a browser
|
|
12821
|
+
--port <port> Loopback callback port (default: an available port)
|
|
12822
|
+
--help, -h Show this help
|
|
12823
|
+
`;
|
|
12824
|
+
async function openBrowser(url) {
|
|
12825
|
+
const command = 'darwin' === process.platform ? 'open' : 'win32' === process.platform ? 'rundll32' : 'xdg-open';
|
|
12826
|
+
const args = 'win32' === process.platform ? [
|
|
12827
|
+
'url.dll,FileProtocolHandler',
|
|
12828
|
+
url
|
|
12829
|
+
] : [
|
|
12830
|
+
url
|
|
12831
|
+
];
|
|
12832
|
+
await new Promise((resolve, reject)=>{
|
|
12833
|
+
const child = (0, external_node_child_process_namespaceObject.spawn)(command, args, {
|
|
12834
|
+
stdio: 'ignore',
|
|
12835
|
+
shell: false
|
|
12836
|
+
});
|
|
12837
|
+
child.once('error', reject);
|
|
12838
|
+
child.once('spawn', ()=>{
|
|
12839
|
+
child.unref();
|
|
12840
|
+
resolve();
|
|
12841
|
+
});
|
|
12842
|
+
});
|
|
12910
12843
|
}
|
|
12911
|
-
async function
|
|
12844
|
+
async function runSiwcCommand(args, io = {
|
|
12912
12845
|
stdout: console.log,
|
|
12913
12846
|
stderr: console.error
|
|
12914
|
-
}) {
|
|
12915
|
-
|
|
12916
|
-
|
|
12917
|
-
...getDefaultDeps(io),
|
|
12918
|
-
...deps
|
|
12919
|
-
};
|
|
12920
|
-
if (!action || '--help' === action || '-h' === action) {
|
|
12921
|
-
io.stdout(MODEL_COMMAND_USAGE);
|
|
12847
|
+
}, credentialsPath = defaultCredentialsPath()) {
|
|
12848
|
+
if (!args.length || args.includes('--help') || args.includes('-h')) {
|
|
12849
|
+
io.stdout(USAGE);
|
|
12922
12850
|
return 0;
|
|
12923
12851
|
}
|
|
12924
|
-
|
|
12925
|
-
if ('
|
|
12926
|
-
io.stderr(
|
|
12852
|
+
const [action, ...options] = args;
|
|
12853
|
+
if ('login' !== action && 'refresh' !== action) {
|
|
12854
|
+
io.stderr(`Unknown SIWC command: ${action}\n${USAGE}`);
|
|
12927
12855
|
return 1;
|
|
12928
12856
|
}
|
|
12929
|
-
|
|
12930
|
-
|
|
12857
|
+
const portOptions = {
|
|
12858
|
+
port: 0,
|
|
12859
|
+
noOpen: false
|
|
12860
|
+
};
|
|
12861
|
+
const seen = new Set();
|
|
12862
|
+
for(let index = 0; index < options.length; index++){
|
|
12863
|
+
const option = options[index];
|
|
12864
|
+
if ('login' !== action || seen.has(option) || ![
|
|
12865
|
+
'--port',
|
|
12866
|
+
'--no-open'
|
|
12867
|
+
].includes(option)) {
|
|
12868
|
+
io.stderr(`Invalid midscene model siwc arguments.\n${USAGE}`);
|
|
12869
|
+
return 1;
|
|
12870
|
+
}
|
|
12871
|
+
seen.add(option);
|
|
12872
|
+
if ('--no-open' === option) portOptions.noOpen = true;
|
|
12873
|
+
else {
|
|
12874
|
+
const value = options[++index];
|
|
12875
|
+
if (!value || !/^\d+$/.test(value) || Number(value) > 65535) {
|
|
12876
|
+
io.stderr('--port must be an integer between 0 and 65535.');
|
|
12877
|
+
return 1;
|
|
12878
|
+
}
|
|
12879
|
+
portOptions.port = Number(value);
|
|
12880
|
+
}
|
|
12881
|
+
}
|
|
12882
|
+
const unlock = await lockCredentials(credentialsPath).catch((error)=>{
|
|
12883
|
+
io.stderr(error.message);
|
|
12884
|
+
});
|
|
12885
|
+
if (!unlock) return 1;
|
|
12886
|
+
const controller = new AbortController();
|
|
12887
|
+
const cancel = ()=>controller.abort();
|
|
12888
|
+
const timer = setTimeout(cancel, 300000);
|
|
12889
|
+
process.once('SIGINT', cancel);
|
|
12890
|
+
process.once('SIGTERM', cancel);
|
|
12891
|
+
try {
|
|
12892
|
+
io.stderr(source_default().cyan.bold(`š Starting OpenAI SIWC ${action}...`));
|
|
12893
|
+
const saved = await readRegistration(credentialsPath);
|
|
12894
|
+
const credentials = await (async ()=>{
|
|
12895
|
+
if ('refresh' === action) return refreshOpenAICredentials(requireCredentials(saved), controller.signal);
|
|
12896
|
+
const registration = saved ?? {
|
|
12897
|
+
ext_agent_host_id: `urn:uuid:${(0, external_node_crypto_.randomUUID)()}`
|
|
12898
|
+
};
|
|
12899
|
+
if (!saved) await saveRegistration(credentialsPath, registration);
|
|
12900
|
+
const result = await loginWithOpenAI({
|
|
12901
|
+
port: portOptions.port,
|
|
12902
|
+
hostId: registration.ext_agent_host_id,
|
|
12903
|
+
clientId: registration.client_id,
|
|
12904
|
+
signal: controller.signal,
|
|
12905
|
+
onAuthorization: async (url)=>{
|
|
12906
|
+
io.stderr(`\n${source_default().bold('Continue with ChatGPT:')}\n${url}\n\n${source_default().gray('Waiting for authorization...')}`);
|
|
12907
|
+
if (!portOptions.noOpen) try {
|
|
12908
|
+
await openBrowser(url);
|
|
12909
|
+
} catch {
|
|
12910
|
+
io.stderr('Open the URL above in your browser to continue.');
|
|
12911
|
+
}
|
|
12912
|
+
}
|
|
12913
|
+
});
|
|
12914
|
+
if (registration.subject && result.subject !== registration.subject) throw new Error('Login identity does not match the saved account.');
|
|
12915
|
+
return result;
|
|
12916
|
+
})();
|
|
12917
|
+
await saveRegistration(credentialsPath, credentials);
|
|
12918
|
+
io.stderr('\nš your Access token:');
|
|
12919
|
+
io.stdout(credentials.access_token);
|
|
12920
|
+
const expiresAt = new Intl.DateTimeFormat('en-GB', {
|
|
12921
|
+
dateStyle: 'medium',
|
|
12922
|
+
timeStyle: 'long'
|
|
12923
|
+
}).format(credentials.expires_at);
|
|
12924
|
+
const remainingMinutes = Math.ceil((credentials.expires_at - Date.now()) / 60000);
|
|
12925
|
+
const expiresIn = new Intl.RelativeTimeFormat('en', {
|
|
12926
|
+
numeric: 'always'
|
|
12927
|
+
}).format(60 === remainingMinutes ? 1 : remainingMinutes, 60 === remainingMinutes ? 'hour' : 'minute');
|
|
12928
|
+
io.stderr(`\nExpires at: ${expiresAt} (${expiresIn})`);
|
|
12929
|
+
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.')}`);
|
|
12930
|
+
return 0;
|
|
12931
|
+
} catch {
|
|
12932
|
+
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.`));
|
|
12933
|
+
return 1;
|
|
12934
|
+
} finally{
|
|
12935
|
+
clearTimeout(timer);
|
|
12936
|
+
process.removeListener('SIGINT', cancel);
|
|
12937
|
+
process.removeListener('SIGTERM', cancel);
|
|
12938
|
+
await unlock();
|
|
12939
|
+
}
|
|
12931
12940
|
}
|
|
12941
|
+
const runModelCommand = (rawArgs, deps, io = {
|
|
12942
|
+
stdout: console.log,
|
|
12943
|
+
stderr: console.error
|
|
12944
|
+
})=>{
|
|
12945
|
+
const [, action, ...restArgs] = rawArgs;
|
|
12946
|
+
if ('siwc' === action) return runSiwcCommand(restArgs, io);
|
|
12947
|
+
const withSiwcUsage = (message)=>message.replace(' midscene model verify\n', ' midscene model verify\n midscene model siwc <login|refresh> [options]\n');
|
|
12948
|
+
return (0, model_command_namespaceObject.runModelCommand)(rawArgs, {
|
|
12949
|
+
loadDotenv: ()=>(0, model_command_namespaceObject.loadDotenvConfig)({
|
|
12950
|
+
dotenvDebug: true,
|
|
12951
|
+
dotenvOverride: true,
|
|
12952
|
+
log: io.stdout
|
|
12953
|
+
}),
|
|
12954
|
+
...deps
|
|
12955
|
+
}, {
|
|
12956
|
+
stdout: (message)=>io.stdout(withSiwcUsage(message)),
|
|
12957
|
+
stderr: (message)=>io.stderr(withSiwcUsage(message))
|
|
12958
|
+
}, 'midscene');
|
|
12959
|
+
};
|
|
12932
12960
|
const test_runner_namespaceObject = require("@midscene/core/internal/test-runner");
|
|
12933
12961
|
const probeWorkflowFormat = (file)=>{
|
|
12934
12962
|
try {
|
|
@@ -13036,7 +13064,7 @@ Native cases/steps Test projects use the midscene-test command.`).wrap(yargs().t
|
|
|
13036
13064
|
] : [],
|
|
13037
13065
|
...config.files
|
|
13038
13066
|
]);
|
|
13039
|
-
loadDotenvConfig({
|
|
13067
|
+
(0, model_command_namespaceObject.loadDotenvConfig)({
|
|
13040
13068
|
dotenvDebug: config.dotenvDebug,
|
|
13041
13069
|
dotenvOverride: config.dotenvOverride,
|
|
13042
13070
|
log: console.log
|
|
@@ -13067,10 +13095,6 @@ Native cases/steps Test projects use the midscene-test command.`).wrap(yargs().t
|
|
|
13067
13095
|
"use strict";
|
|
13068
13096
|
module.exports = require("assert");
|
|
13069
13097
|
},
|
|
13070
|
-
crypto (module) {
|
|
13071
|
-
"use strict";
|
|
13072
|
-
module.exports = require("crypto");
|
|
13073
|
-
},
|
|
13074
13098
|
fs (module) {
|
|
13075
13099
|
"use strict";
|
|
13076
13100
|
module.exports = require("fs");
|
|
@@ -13079,6 +13103,10 @@ Native cases/steps Test projects use the midscene-test command.`).wrap(yargs().t
|
|
|
13079
13103
|
"use strict";
|
|
13080
13104
|
module.exports = require("lodash.merge");
|
|
13081
13105
|
},
|
|
13106
|
+
"node:crypto" (module) {
|
|
13107
|
+
"use strict";
|
|
13108
|
+
module.exports = require("node:crypto");
|
|
13109
|
+
},
|
|
13082
13110
|
"node:fs" (module) {
|
|
13083
13111
|
"use strict";
|
|
13084
13112
|
module.exports = require("node:fs");
|
|
@@ -16384,10 +16412,6 @@ Native cases/steps Test projects use the midscene-test command.`).wrap(yargs().t
|
|
|
16384
16412
|
YError: e1
|
|
16385
16413
|
};
|
|
16386
16414
|
module.exports = ue;
|
|
16387
|
-
},
|
|
16388
|
-
"../../node_modules/.pnpm/dotenv@16.4.5/node_modules/dotenv/package.json" (module) {
|
|
16389
|
-
"use strict";
|
|
16390
|
-
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}}');
|
|
16391
16415
|
}
|
|
16392
16416
|
};
|
|
16393
16417
|
var __webpack_module_cache__ = {};
|