@midscene/cli 1.16.2 → 1.16.6-beta-20261023073257.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/lib/index.js CHANGED
@@ -3342,7 +3342,7 @@ var __webpack_modules__ = {
3342
3342
  return lines.join('\n');
3343
3343
  };
3344
3344
  const printer_formatYamlProgressSnapshot = (message, attempt, totalAttempts)=>totalAttempts > 1 ? `Attempt ${attempt}/${totalAttempts}\n${message}` : message;
3345
- const external_node_util_namespaceObject = require("node:util");
3345
+ var external_node_util_ = __webpack_require__("node:util");
3346
3346
  var external_node_process_ = __webpack_require__("node:process");
3347
3347
  const copyProperty = (to, from, property, ignoreNonConfigurable)=>{
3348
3348
  if ('length' === property || 'prototype' === property) return;
@@ -3683,7 +3683,7 @@ var __webpack_modules__ = {
3683
3683
  for (const content of contents){
3684
3684
  const rows = content.split('\n');
3685
3685
  for (const row of rows){
3686
- const text = (0, external_node_util_namespaceObject.stripVTControlCharacters)(row);
3686
+ const text = (0, external_node_util_.stripVTControlCharacters)(row);
3687
3687
  count += Math.max(1, Math.ceil(text.length / columns));
3688
3688
  }
3689
3689
  }
@@ -4587,9 +4587,7 @@ defineYamlBatchTest(test, testOptions);
4587
4587
  "use strict";
4588
4588
  var core_ = __webpack_require__("@midscene/core");
4589
4589
  const cli_namespaceObject = require("@midscene/shared/cli");
4590
- var package_namespaceObject = {
4591
- rE: "1.16.2"
4592
- };
4590
+ var package_namespaceObject = JSON.parse('{"rE":"1.16.6-beta-20261023073257.0"}');
4593
4591
  var external_node_fs_ = __webpack_require__("node:fs");
4594
4592
  var external_node_fs_namespaceObject = /*#__PURE__*/ __webpack_require__.t(external_node_fs_, 2);
4595
4593
  var external_node_path_ = __webpack_require__("node:path");
@@ -10273,7 +10271,7 @@ Usage:
10273
10271
  type: 'boolean',
10274
10272
  description: 'Whether to dismiss the HarmonyOS keyboard after text input'
10275
10273
  }
10276
- }).version('version', 'Show version number', "1.16.2").help().epilogue(`For complete list of configuration options, please visit:
10274
+ }).version('version', 'Show version number', "1.16.6-beta-20261023073257.0").help().epilogue(`For complete list of configuration options, please visit:
10277
10275
  • Web options: https://midscenejs.com/automate-with-scripts-in-yaml#the-web-part
10278
10276
  • Android options: https://midscenejs.com/automate-with-scripts-in-yaml#the-android-part
10279
10277
  • HarmonyOS options: https://midscenejs.com/automate-with-scripts-in-yaml#the-harmony-part
@@ -12527,22 +12525,36 @@ Native cases/steps Test projects use the midscene-test command.`).wrap(yargs().t
12527
12525
  var framework = __webpack_require__("./src/framework/index.ts");
12528
12526
  const external_node_child_process_namespaceObject = require("node:child_process");
12529
12527
  var external_node_crypto_ = __webpack_require__("node:crypto");
12528
+ const ai_model_namespaceObject = require("@midscene/core/ai-model");
12530
12529
  var chalk_source = __webpack_require__("../../node_modules/.pnpm/chalk@4.1.2/node_modules/chalk/source/index.js");
12531
12530
  var source_default = /*#__PURE__*/ __webpack_require__.n(chalk_source);
12532
12531
  const external_node_os_namespaceObject = require("node:os");
12532
+ var external_node_util_ = __webpack_require__("node:util");
12533
+ function describeSiwcError(error) {
12534
+ return (0, external_node_util_.inspect)(error, {
12535
+ depth: null,
12536
+ colors: false,
12537
+ maxArrayLength: null,
12538
+ maxStringLength: null
12539
+ });
12540
+ }
12533
12541
  const defaultCredentialsPath = ()=>(0, external_node_path_.join)((0, external_node_os_namespaceObject.homedir)(), '.midscene', 'siwc.json');
12534
12542
  async function readCredentialsFile(path) {
12535
12543
  const content = await (0, promises_namespaceObject.readFile)(path, 'utf8').catch((error)=>{
12536
12544
  if ('ENOENT' === error.code) return;
12537
- throw new Error('Unable to read the local SIWC credentials file.');
12545
+ throw new ai_model_namespaceObject.SiwcError('Unable to read the local SIWC credentials file.', {
12546
+ cause: error
12547
+ });
12538
12548
  });
12539
12549
  if (void 0 === content) return;
12540
12550
  try {
12541
12551
  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.');
12552
+ if (!value || 'string' != typeof value.credentials?.ext_agent_host_id || !value.credentials.ext_agent_host_id) throw new ai_model_namespaceObject.SiwcError('Invalid SIWC credentials record.');
12543
12553
  return value;
12544
- } catch {
12545
- throw new Error('Invalid local SIWC credentials file.');
12554
+ } catch (error) {
12555
+ throw new ai_model_namespaceObject.SiwcError('Invalid local SIWC credentials file.', {
12556
+ cause: error
12557
+ });
12546
12558
  }
12547
12559
  }
12548
12560
  async function readRegistration(path) {
@@ -12581,8 +12593,10 @@ Native cases/steps Test projects use the midscene-test command.`).wrap(yargs().t
12581
12593
  mode: 448
12582
12594
  });
12583
12595
  } 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.');
12596
+ if ('EEXIST' === error.code) throw new ai_model_namespaceObject.SiwcError(`Another SIWC command holds ${lockPath}. If it was interrupted, remove this lock only after confirming that no SIWC command is running.`);
12597
+ throw new ai_model_namespaceObject.SiwcError('Unable to lock the local SIWC credentials file.', {
12598
+ cause: error
12599
+ });
12586
12600
  }
12587
12601
  return ()=>(0, promises_namespaceObject.rm)(lockPath, {
12588
12602
  recursive: true,
@@ -12597,20 +12611,14 @@ Native cases/steps Test projects use the midscene-test command.`).wrap(yargs().t
12597
12611
  'access_token',
12598
12612
  'refresh_token',
12599
12613
  '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.');
12614
+ ].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 ai_model_namespaceObject.SiwcError('No valid SIWC credentials. Run midscene model siwc login first.');
12601
12615
  return value;
12602
12616
  }
12603
12617
  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
12618
  async function loginWithOpenAI(options, dependencies) {
12609
12619
  const hostId = options.hostId ?? `urn:uuid:${(0, external_node_crypto_.randomUUID)()}`;
12610
12620
  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');
12621
+ const context = {};
12614
12622
  const signal = options.signal;
12615
12623
  signal.throwIfAborted();
12616
12624
  const server = (0, external_node_http_namespaceObject.createServer)();
@@ -12624,17 +12632,24 @@ Native cases/steps Test projects use the midscene-test command.`).wrap(yargs().t
12624
12632
  response.setHeader('Referrer-Policy', 'no-referrer');
12625
12633
  const url = new URL(request.url || '/', 'http://127.0.0.1');
12626
12634
  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.');
12635
+ if (!context.attempt) return void response.writeHead(400).end('Authorization has not started.');
12628
12636
  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.'));
12637
+ const parsed = (()=>{
12638
+ try {
12639
+ return (0, ai_model_namespaceObject.parseSiwcCallback)(new URL(request.url || '/', context.attempt.redirectUri).toString(), context.attempt);
12640
+ } catch (error) {
12641
+ response.writeHead(400).end('Invalid authorization callback.');
12642
+ if (url.searchParams.has('error')) {
12643
+ consumed.value = true;
12644
+ reject(error);
12645
+ }
12646
+ return;
12647
+ }
12648
+ })();
12649
+ if (!parsed) {
12650
+ if (!response.writableEnded) response.writeHead(400).end('Invalid login state. Use the original authorization link.');
12633
12651
  return;
12634
12652
  }
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
12653
  consumed.value = true;
12639
12654
  response.setHeader('Content-Type', 'text/html; charset=utf-8');
12640
12655
  response.end(`<!doctype html>
@@ -12660,10 +12675,7 @@ Native cases/steps Test projects use the midscene-test command.`).wrap(yargs().t
12660
12675
  </main>
12661
12676
  </body>
12662
12677
  </html>`);
12663
- resolve({
12664
- code,
12665
- clientId
12666
- });
12678
+ resolve(parsed);
12667
12679
  });
12668
12680
  server.on('error', reject);
12669
12681
  });
@@ -12682,132 +12694,22 @@ Native cases/steps Test projects use the midscene-test command.`).wrap(yargs().t
12682
12694
  });
12683
12695
  signal.throwIfAborted();
12684
12696
  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'
12690
- },
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'
12709
- },
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'
12697
+ const { attempt, url } = await (0, ai_model_namespaceObject.createSiwcAuthorization)({
12698
+ hostId,
12699
+ clientId: existingClientId,
12700
+ redirectUri,
12701
+ agentName: 'Midscene'
12720
12702
  });
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
- };
12703
+ context.attempt = attempt;
12704
+ await options.onAuthorization(url);
12705
+ const result = await (0, ai_model_namespaceObject.withSiwcStage)('Authorization callback', ()=>callback);
12706
+ return await (0, ai_model_namespaceObject.exchangeSiwcCode)(attempt, result, signal, dependencies);
12756
12707
  } finally{
12757
12708
  signal.removeEventListener('abort', abort);
12758
12709
  server.closeAllConnections();
12759
12710
  await new Promise((resolve)=>server.close(()=>resolve()));
12760
12711
  }
12761
12712
  }
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.');
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
12713
  const USAGE = `Usage:
12812
12714
  midscene model siwc login [--no-open] [--port <port>]
12813
12715
  midscene model siwc refresh
@@ -12880,7 +12782,7 @@ Options (login):
12880
12782
  }
12881
12783
  }
12882
12784
  const unlock = await lockCredentials(credentialsPath).catch((error)=>{
12883
- io.stderr(error.message);
12785
+ io.stderr(describeSiwcError(error));
12884
12786
  });
12885
12787
  if (!unlock) return 1;
12886
12788
  const controller = new AbortController();
@@ -12890,31 +12792,31 @@ Options (login):
12890
12792
  process.once('SIGTERM', cancel);
12891
12793
  try {
12892
12794
  io.stderr(source_default().cyan.bold(`🔄 Starting OpenAI SIWC ${action}...`));
12893
- const saved = await readRegistration(credentialsPath);
12795
+ const saved = await (0, ai_model_namespaceObject.withSiwcStage)('Read credentials', ()=>readRegistration(credentialsPath));
12894
12796
  const credentials = await (async ()=>{
12895
- if ('refresh' === action) return refreshOpenAICredentials(requireCredentials(saved), controller.signal);
12797
+ if ('refresh' === action) return (0, ai_model_namespaceObject.withSiwcStage)('Refresh', ()=>(0, ai_model_namespaceObject.refreshSiwcCredentials)(requireCredentials(saved), controller.signal));
12896
12798
  const registration = saved ?? {
12897
12799
  ext_agent_host_id: `urn:uuid:${(0, external_node_crypto_.randomUUID)()}`
12898
12800
  };
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.');
12801
+ if (!saved) await (0, ai_model_namespaceObject.withSiwcStage)('Save registration', ()=>saveRegistration(credentialsPath, registration));
12802
+ const result = await (0, ai_model_namespaceObject.withSiwcStage)('Login', ()=>loginWithOpenAI({
12803
+ port: portOptions.port,
12804
+ hostId: registration.ext_agent_host_id,
12805
+ clientId: registration.client_id,
12806
+ signal: controller.signal,
12807
+ onAuthorization: async (url)=>{
12808
+ io.stderr(`\n${source_default().bold('Continue with ChatGPT:')}\n${url}\n\n${source_default().gray('Waiting for authorization...')}`);
12809
+ if (!portOptions.noOpen) try {
12810
+ await openBrowser(url);
12811
+ } catch {
12812
+ io.stderr('Open the URL above in your browser to continue.');
12813
+ }
12911
12814
  }
12912
- }
12913
- });
12914
- if (registration.subject && result.subject !== registration.subject) throw new Error('Login identity does not match the saved account.');
12815
+ }));
12816
+ if (registration.subject && result.subject !== registration.subject) throw new ai_model_namespaceObject.SiwcError('Login identity does not match the saved account.');
12915
12817
  return result;
12916
12818
  })();
12917
- await saveRegistration(credentialsPath, credentials);
12819
+ await (0, ai_model_namespaceObject.withSiwcStage)('Save credentials', ()=>saveRegistration(credentialsPath, credentials));
12918
12820
  io.stderr('\n🔑 your Access token:');
12919
12821
  io.stdout(credentials.access_token);
12920
12822
  const expiresAt = new Intl.DateTimeFormat('en-GB', {
@@ -12928,8 +12830,8 @@ Options (login):
12928
12830
  io.stderr(`\nExpires at: ${expiresAt} (${expiresIn})`);
12929
12831
  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
12832
  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.`));
12833
+ } catch (error) {
12834
+ io.stderr(source_default().red(controller.signal.aborted ? `OpenAI ${action} cancelled or timed out.\n${describeSiwcError(error)}` : `OpenAI ${action} failed.\n${describeSiwcError(error)}`));
12933
12835
  return 1;
12934
12836
  } finally{
12935
12837
  clearTimeout(timer);
@@ -13123,6 +13025,10 @@ Options (login):
13123
13025
  "use strict";
13124
13026
  module.exports = require("node:url");
13125
13027
  },
13028
+ "node:util" (module) {
13029
+ "use strict";
13030
+ module.exports = require("node:util");
13031
+ },
13126
13032
  os (module) {
13127
13033
  "use strict";
13128
13034
  module.exports = require("os");