@qawolf/cli 1.24.0 → 1.25.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/cli.js CHANGED
@@ -8390,11 +8390,82 @@ function extractMissingPackage(text) {
8390
8390
  return specifier;
8391
8391
  }
8392
8392
 
8393
+ // src/core/pluralize.ts
8394
+ function pluralize(count, singular, plural = `${singular}s`) {
8395
+ return `${String(count)} ${count === 1 ? singular : plural}`;
8396
+ }
8397
+
8393
8398
  // src/core/formatSeconds.ts
8394
8399
  function formatSeconds(ms) {
8395
8400
  return `${Math.round(ms / 1000)}s`;
8396
8401
  }
8397
8402
 
8403
+ // src/core/messages/authErrors.ts
8404
+ var authErrorMessages = {
8405
+ identity: {
8406
+ invalidOrUnauthorized: "API key is invalid or unauthorized",
8407
+ unexpectedFormat: "Could not verify API key: unexpected response format",
8408
+ couldNotVerify: (detail, status) => `Could not verify API key: ${detail || `HTTP ${status}`}`,
8409
+ couldNotVerifyNetwork: (cause) => `Could not verify API key: ${cause}`,
8410
+ timedOut: (timeoutMs) => `Could not verify API key: the QA Wolf API did not answer within ${formatSeconds(timeoutMs)}.`
8411
+ },
8412
+ request: {
8413
+ rejected401: (noun) => `QA Wolf API rejected the${noun ? ` ${noun}` : ""} request (HTTP 401). Check your API key.`,
8414
+ rejected402: (noun) => `QA Wolf API refused the${noun ? ` ${noun}` : ""} request (HTTP 402): billing prevented it.`,
8415
+ rejected403: (noun) => `QA Wolf API rejected the${noun ? ` ${noun}` : ""} request (HTTP 403). Check that your API key has access to this environment.`,
8416
+ notFound404: (noun) => `QA Wolf API could not find ${noun ? `${noun} for that environment` : "that environment"} (HTTP 404). Check the --env value.`,
8417
+ failedWithStatus: (status, noun) => `QA Wolf API${noun ? ` ${noun}` : ""} request failed (HTTP ${status}).`,
8418
+ networkUnreachable: (baseUrl, noun) => `Could not reach the QA Wolf API at ${baseUrl}${noun ? ` to fetch ${noun}` : ""}. Check your network connection and QAWOLF_HOST_URL.`,
8419
+ timedOut: (timeoutMs, noun) => `The QA Wolf API${noun ? ` ${noun}` : ""} request timed out after ${formatSeconds(timeoutMs)}. The work may still be finishing on the platform.`,
8420
+ unexpectedResponse: (noun) => `Unexpected${noun ? ` ${noun}` : ""} response from the QA Wolf API.`
8421
+ },
8422
+ workos: {
8423
+ unexpectedResponse: "WorkOS returned an unexpected response",
8424
+ unexpectedResponseWithStatus: (status) => `WorkOS returned an unexpected response (HTTP ${status})`,
8425
+ unreachable: (detail) => `Could not reach WorkOS: ${detail}`,
8426
+ redirected: "WorkOS answered with a redirect, which the CLI does not follow for a sign-in request",
8427
+ noClientForSession: "This session names no WorkOS client"
8428
+ },
8429
+ bundle: {
8430
+ linkExpired: "The flow bundle download link has expired. Please run `qawolf flows pull` again to refresh.",
8431
+ failedWithStatus: (status) => `Could not download the flow bundle (HTTP ${status}).`,
8432
+ networkUnreachable: "Could not reach the flow bundle storage. Check your network connection and try again.",
8433
+ timedOut: (timeoutMs) => `Downloading the flow bundle stalled — no data arrived for ${formatSeconds(timeoutMs)}. Please try again.`,
8434
+ malformed: "The flow bundle download was malformed. Please run `qawolf flows pull` again."
8435
+ }
8436
+ };
8437
+
8438
+ // src/core/messages/whoami.ts
8439
+ var whoamiMessages = {
8440
+ source: (source) => `Source: ${source}`,
8441
+ authFailed: (source, error) => `Authentication failed (source: ${source}): ${error}`,
8442
+ authenticatedAs: (teamName, source) => `Authenticated as ${teamName} (source: ${source})`,
8443
+ teamNote: (input) => [
8444
+ `Workspace: ${input.team.name}`,
8445
+ `ID: ${input.team.id}`,
8446
+ input.team.slug ? `Slug: ${input.team.slug}` : undefined,
8447
+ input.teamUrl ? `URL: ${input.teamUrl}` : undefined,
8448
+ `Source: ${input.source}`
8449
+ ].filter((line) => Boolean(line)).join(`
8450
+ `),
8451
+ organizationNote: (input) => [
8452
+ `Organization: ${input.organization.name}`,
8453
+ `ID: ${input.organization.id}`,
8454
+ `Source: ${input.source}`
8455
+ ].join(`
8456
+ `),
8457
+ activeWorkspace: (workspaceId, found) => found ? `${found.workspace} (${found.organization})` : `${workspaceId} — no longer in reach; run 'qawolf auth switch'`,
8458
+ userNote: (input) => [
8459
+ `User: ${input.user.email}`,
8460
+ `ID: ${input.user.id}`,
8461
+ `Organization: ${input.organization.name}`,
8462
+ input.activeWorkspace ? `Workspace: ${input.activeWorkspace}` : undefined,
8463
+ input.organizations.length > 0 ? `Workspaces: ${input.organizations.map((o) => `${o.name}: ${o.workspaces.map((w) => w.name).join(", ")}`).join(" | ")}` : undefined,
8464
+ `Source: ${input.source}`
8465
+ ].filter((line) => Boolean(line)).join(`
8466
+ `)
8467
+ };
8468
+
8398
8469
  // src/core/messages/auth.ts
8399
8470
  var authMessages = {
8400
8471
  title: "QA Wolf Authentication",
@@ -8412,7 +8483,47 @@ var authMessages = {
8412
8483
  whoamiFailed: "Not authenticated",
8413
8484
  login: {
8414
8485
  nonInteractive: "auth login requires an interactive terminal. Set the QAWOLF_API_KEY environment variable for CI authentication.",
8415
- reAuthPrompt: "You are already authenticated. Re-authenticate?"
8486
+ reAuthPrompt: "You are already authenticated. Re-authenticate?",
8487
+ chooseMethod: "How do you want to sign in?",
8488
+ methodBrowser: "Browser",
8489
+ methodBrowserHint: "Sign in with your QA Wolf account",
8490
+ methodApiKey: "API key",
8491
+ methodApiKeyHint: "Paste a team key — needed for flows pull and flows run",
8492
+ apiKeyPrecedence: {
8493
+ env: "QAWOLF_API_KEY is set, and an API key takes precedence over a browser session. Commands continue to use that key until you unset the variable.",
8494
+ stored: "A stored API key takes precedence over a browser session. Commands continue to use that key until you run 'qawolf auth logout' and sign in again."
8495
+ }
8496
+ },
8497
+ device: {
8498
+ unavailable: "This QA Wolf deployment does not offer browser sign-in. Run 'qawolf auth login' again and choose 'API key'.",
8499
+ configUnreachable: "Could not ask this QA Wolf deployment whether it offers browser sign-in. Check your connection, then try again.",
8500
+ confirmCode: (userCode) => `Your code is ${userCode}`,
8501
+ visitUrl: (url) => `Confirm it at ${url}`,
8502
+ visitUrlPlain: (url) => `Or go to ${url} and enter the code`,
8503
+ openFailed: (url) => `Could not open a browser automatically. Open ${url} yourself to continue.`,
8504
+ waiting: "Waiting for you to finish in the browser",
8505
+ signedIn: (email) => `Signed in as ${email}.`,
8506
+ failed: {
8507
+ "access-denied": "The sign-in request was rejected.",
8508
+ expired: "The sign-in request expired. Run 'qawolf auth login' to retry.",
8509
+ timeout: "The sign-in request timed out. Run 'qawolf auth login' to retry.",
8510
+ network: "Could not reach WorkOS to complete sign-in.",
8511
+ unavailable: "Could not start browser sign-in.",
8512
+ cancelled: "Sign-in cancelled."
8513
+ }
8514
+ },
8515
+ workspace: {
8516
+ chooseOrganization: "Which organization do you want to work in?",
8517
+ choose: "Which workspace do you want to use?",
8518
+ workspaceCount: (count) => pluralize(count, "workspace"),
8519
+ working: (organization, workspace) => `Working in ${workspace} (${organization}).`,
8520
+ none: "This account reaches no organizations yet.",
8521
+ cancelled: "Workspace not changed.",
8522
+ notSignedIn: "Workspace switching needs a browser sign-in. Run 'qawolf auth login' and choose Browser.",
8523
+ sessionExpired: "Your session could not be renewed. Run 'qawolf auth login' to sign in again.",
8524
+ nonInteractive: "auth switch needs an interactive terminal, or set QAWOLF_WORKSPACE to name a workspace.",
8525
+ sessionChanged: "The stored sign-in changed while you made your choice. Run the command again.",
8526
+ saveFailed: (detail) => `Could not save the workspace choice: ${detail}`
8416
8527
  },
8417
8528
  logout: {
8418
8529
  title: "Log Out",
@@ -8424,58 +8535,8 @@ var authMessages = {
8424
8535
  success: "Logged out successfully.",
8425
8536
  cancelled: "Logout cancelled."
8426
8537
  },
8427
- errors: {
8428
- identity: {
8429
- invalidOrUnauthorized: "API key is invalid or unauthorized",
8430
- unexpectedFormat: "Could not verify API key: unexpected response format",
8431
- couldNotVerify: (detail, status) => `Could not verify API key: ${detail || `HTTP ${status}`}`,
8432
- couldNotVerifyNetwork: (cause) => `Could not verify API key: ${cause}`,
8433
- timedOut: (timeoutMs) => `Could not verify API key: the QA Wolf API did not answer within ${formatSeconds(timeoutMs)}.`
8434
- },
8435
- request: {
8436
- rejected401: (noun) => `QA Wolf API rejected the${noun ? ` ${noun}` : ""} request (HTTP 401). Check your API key.`,
8437
- rejected402: (noun) => `QA Wolf API refused the${noun ? ` ${noun}` : ""} request (HTTP 402): billing prevented it.`,
8438
- rejected403: (noun) => `QA Wolf API rejected the${noun ? ` ${noun}` : ""} request (HTTP 403). Check that your API key has access to this environment.`,
8439
- notFound404: (noun) => `QA Wolf API could not find ${noun ? `${noun} for that environment` : "that environment"} (HTTP 404). Check the --env value.`,
8440
- failedWithStatus: (status, noun) => `QA Wolf API${noun ? ` ${noun}` : ""} request failed (HTTP ${status}).`,
8441
- networkUnreachable: (baseUrl, noun) => `Could not reach the QA Wolf API at ${baseUrl}${noun ? ` to fetch ${noun}` : ""}. Check your network connection and QAWOLF_HOST_URL.`,
8442
- timedOut: (timeoutMs, noun) => `The QA Wolf API${noun ? ` ${noun}` : ""} request timed out after ${formatSeconds(timeoutMs)}. The work may still be finishing on the platform.`,
8443
- unexpectedResponse: (noun) => `Unexpected${noun ? ` ${noun}` : ""} response from the QA Wolf API.`
8444
- },
8445
- bundle: {
8446
- linkExpired: "The flow bundle download link has expired. Please run `qawolf flows pull` again to refresh.",
8447
- failedWithStatus: (status) => `Could not download the flow bundle (HTTP ${status}).`,
8448
- networkUnreachable: "Could not reach the flow bundle storage. Check your network connection and try again.",
8449
- timedOut: (timeoutMs) => `Downloading the flow bundle stalled — no data arrived for ${formatSeconds(timeoutMs)}. Please try again.`,
8450
- malformed: "The flow bundle download was malformed. Please run `qawolf flows pull` again."
8451
- }
8452
- },
8453
- whoami: {
8454
- source: (source) => `Source: ${source}`,
8455
- authFailed: (source, error) => `Authentication failed (source: ${source}): ${error}`,
8456
- authenticatedAs: (teamName, source) => `Authenticated as ${teamName} (source: ${source})`,
8457
- teamNote: (input) => [
8458
- `Team: ${input.team.name}`,
8459
- `ID: ${input.team.id}`,
8460
- input.team.slug ? `Slug: ${input.team.slug}` : undefined,
8461
- input.teamUrl ? `URL: ${input.teamUrl}` : undefined,
8462
- `Source: ${input.source}`
8463
- ].filter((line) => Boolean(line)).join(`
8464
- `),
8465
- organizationNote: (input) => [
8466
- `Organization: ${input.organization.name}`,
8467
- `ID: ${input.organization.id}`,
8468
- `Source: ${input.source}`
8469
- ].join(`
8470
- `),
8471
- userNote: (input) => [
8472
- `User: ${input.user.email}`,
8473
- `ID: ${input.user.id}`,
8474
- `Organization: ${input.organization.name}`,
8475
- `Source: ${input.source}`
8476
- ].join(`
8477
- `)
8478
- }
8538
+ errors: authErrorMessages,
8539
+ whoami: whoamiMessages
8479
8540
  };
8480
8541
  // src/core/messages/toolNotFound.ts
8481
8542
  var installRemedy = "Run `qawolf install` to install the runtime dependencies.";
@@ -8568,11 +8629,6 @@ var environmentsMessages = {
8568
8629
  usingPulledEnv: (env) => `Could not reach the platform to resolve environment ${env}; using the copy pulled into .qawolf/${env}.`,
8569
8630
  tooManyPages: (pages) => `Stopped listing environments after ${pages} pages. Pass --env explicitly.`
8570
8631
  };
8571
- // src/core/pluralize.ts
8572
- function pluralize(count, singular, plural = `${singular}s`) {
8573
- return `${String(count)} ${count === 1 ? singular : plural}`;
8574
- }
8575
-
8576
8632
  // src/core/messages/flows.ts
8577
8633
  var flowsMessages = {
8578
8634
  title: "Flows",
@@ -9161,43 +9217,30 @@ function resolveHostUrl(env) {
9161
9217
  }
9162
9218
 
9163
9219
  // src/domains/auth/resolve.ts
9164
- import { Entry as Entry3 } from "@napi-rs/keyring";
9165
-
9166
- // src/domains/auth/store/delete.ts
9167
- import { join as join2 } from "node:path";
9168
- import { Entry } from "@napi-rs/keyring";
9169
-
9170
- // src/domains/auth/store/constants.ts
9171
- var service = "qawolf-cli";
9172
- var account = "api-key";
9173
- var credentialsFile = "credentials.json";
9220
+ import { Entry as Entry5 } from "@napi-rs/keyring";
9174
9221
 
9175
- // src/domains/auth/store/delete.ts
9176
- function deleteFromKeychain() {
9177
- try {
9178
- new Entry(service, account).deletePassword();
9179
- return "deleted";
9180
- } catch {
9181
- return "unavailable";
9182
- }
9183
- }
9184
- async function deleteFromFile(configDir, fs) {
9222
+ // src/core/deviceAuth/tokenExpiry.ts
9223
+ function readAccessTokenExpiry(accessToken) {
9224
+ const segments = accessToken.split(".");
9225
+ if (segments.length !== 3)
9226
+ return;
9227
+ const [, payload] = segments;
9228
+ if (!payload)
9229
+ return;
9230
+ let claims;
9185
9231
  try {
9186
- await fs.unlink(join2(configDir, credentialsFile));
9187
- return "deleted";
9232
+ claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
9188
9233
  } catch {
9189
- return "not-found";
9234
+ return;
9190
9235
  }
9236
+ if (typeof claims !== "object" || claims === null)
9237
+ return;
9238
+ const exp = claims.exp;
9239
+ if (typeof exp !== "number" || !Number.isFinite(exp))
9240
+ return;
9241
+ const expiresAtMs = exp * 1000;
9242
+ return Number.isFinite(expiresAtMs) ? expiresAtMs : undefined;
9191
9243
  }
9192
- async function deleteApiKey(configDir, fs) {
9193
- const [keychain, file] = await Promise.all([
9194
- Promise.resolve(deleteFromKeychain()),
9195
- deleteFromFile(configDir, fs)
9196
- ]);
9197
- return { keychain, file };
9198
- }
9199
- // src/domains/auth/store/load.ts
9200
- import { join as join3 } from "node:path";
9201
9244
 
9202
9245
  // node_modules/zod/v4/core/core.js
9203
9246
  var _a;
@@ -14424,10 +14467,229 @@ function date4(params) {
14424
14467
 
14425
14468
  // node_modules/zod/v4/classic/external.js
14426
14469
  config(en_default());
14470
+ // src/shell/workos/types.ts
14471
+ var defaultWorkosBaseUrl = "https://api.workos.com";
14472
+ var defaultIntervalSec = 5;
14473
+ var deviceAuthorizationBody = object({
14474
+ device_code: string2().min(1),
14475
+ user_code: string2().min(1),
14476
+ verification_uri: string2().min(1),
14477
+ verification_uri_complete: string2().min(1).optional(),
14478
+ expires_in: number2().int().positive(),
14479
+ interval: number2().int().positive().optional()
14480
+ });
14481
+ var deviceTokenBody = object({
14482
+ access_token: string2().min(1),
14483
+ refresh_token: string2().min(1),
14484
+ user: object({ email: string2().min(1) }),
14485
+ organization_id: string2().min(1).optional()
14486
+ });
14487
+ var oauthErrorBody = object({
14488
+ error: string2().min(1),
14489
+ error_description: string2().min(1).optional()
14490
+ });
14491
+ var authenticationErrorBody = object({
14492
+ code: string2().min(1),
14493
+ message: string2().min(1).optional()
14494
+ });
14495
+
14496
+ // src/shell/workos/send.ts
14497
+ var timeoutMs = 15000;
14498
+ var unexpectedResponse = authErrorMessages.workos.unexpectedResponse;
14499
+ function isTransientStatus(status) {
14500
+ return status >= 500 || status === 429 || status === 408;
14501
+ }
14502
+ async function sendWorkosRequest(url, init, fetchFn) {
14503
+ let response;
14504
+ try {
14505
+ response = await fetchFn(url, {
14506
+ method: "POST",
14507
+ headers: init.headers,
14508
+ body: init.body,
14509
+ redirect: "manual",
14510
+ signal: AbortSignal.timeout(timeoutMs)
14511
+ });
14512
+ } catch (err) {
14513
+ return {
14514
+ kind: "failure",
14515
+ detail: authErrorMessages.workos.unreachable(errorMessage(err)),
14516
+ retryable: true
14517
+ };
14518
+ }
14519
+ if (response.status >= 300 && response.status < 400) {
14520
+ return {
14521
+ kind: "failure",
14522
+ detail: authErrorMessages.workos.redirected,
14523
+ retryable: false
14524
+ };
14525
+ }
14526
+ let json;
14527
+ try {
14528
+ json = await response.json();
14529
+ } catch {
14530
+ return {
14531
+ kind: "failure",
14532
+ detail: authErrorMessages.workos.unexpectedResponseWithStatus(response.status),
14533
+ retryable: response.ok || isTransientStatus(response.status)
14534
+ };
14535
+ }
14536
+ if (response.ok)
14537
+ return { kind: "json", json };
14538
+ if (isTransientStatus(response.status)) {
14539
+ return {
14540
+ kind: "failure",
14541
+ detail: authErrorMessages.workos.unexpectedResponseWithStatus(response.status),
14542
+ retryable: true
14543
+ };
14544
+ }
14545
+ const parsed = oauthErrorBody.safeParse(json);
14546
+ if (parsed.success) {
14547
+ return {
14548
+ kind: "oauth-error",
14549
+ code: parsed.data.error,
14550
+ description: parsed.data.error_description
14551
+ };
14552
+ }
14553
+ const authError = authenticationErrorBody.safeParse(json);
14554
+ if (authError.success) {
14555
+ return {
14556
+ kind: "oauth-error",
14557
+ code: authError.data.code,
14558
+ description: authError.data.message
14559
+ };
14560
+ }
14561
+ return {
14562
+ kind: "failure",
14563
+ detail: authErrorMessages.workos.unexpectedResponseWithStatus(response.status),
14564
+ retryable: false
14565
+ };
14566
+ }
14567
+
14568
+ // src/shell/workos/refreshAccessToken.ts
14569
+ async function refreshAccessToken(refreshToken, organizationId, deps) {
14570
+ const params = new URLSearchParams({
14571
+ grant_type: "refresh_token",
14572
+ refresh_token: refreshToken,
14573
+ client_id: deps.clientId
14574
+ });
14575
+ if (organizationId)
14576
+ params.set("organization_id", organizationId);
14577
+ const outcome = await sendWorkosRequest(`${deps.baseUrl}/user_management/authenticate`, {
14578
+ headers: { "content-type": "application/x-www-form-urlencoded" },
14579
+ body: params.toString()
14580
+ }, deps.fetch);
14581
+ if (outcome.kind === "failure") {
14582
+ return { ok: false, error: outcome.detail, retryable: outcome.retryable };
14583
+ }
14584
+ if (outcome.kind === "oauth-error") {
14585
+ return {
14586
+ ok: false,
14587
+ error: outcome.description ?? outcome.code,
14588
+ retryable: false
14589
+ };
14590
+ }
14591
+ const parsed = deviceTokenBody.safeParse(outcome.json);
14592
+ if (!parsed.success) {
14593
+ return { ok: false, error: unexpectedResponse, retryable: false };
14594
+ }
14595
+ return {
14596
+ ok: true,
14597
+ value: {
14598
+ accessToken: parsed.data.access_token,
14599
+ refreshToken: parsed.data.refresh_token,
14600
+ expiresAt: readAccessTokenExpiry(parsed.data.access_token),
14601
+ email: parsed.data.user.email,
14602
+ organizationId: parsed.data.organization_id
14603
+ }
14604
+ };
14605
+ }
14606
+
14607
+ // src/shell/workos/config.ts
14608
+ function resolveWorkosConfig(publishedClientId) {
14609
+ const clientId = publishedClientId?.trim();
14610
+ if (!clientId)
14611
+ return { configured: false };
14612
+ return { configured: true, clientId, baseUrl: defaultWorkosBaseUrl };
14613
+ }
14614
+
14615
+ // src/domains/auth/store/delete.ts
14616
+ import { join as join2 } from "node:path";
14617
+ import { Entry } from "@napi-rs/keyring";
14618
+
14619
+ // src/domains/auth/store/constants.ts
14620
+ var service = "qawolf-cli";
14621
+ var account = "api-key";
14622
+ var credentialsFile = "credentials.json";
14623
+ var tokensAccount = "oauth-tokens";
14624
+ var tokensFile = "tokens.json";
14625
+
14626
+ // src/domains/auth/store/delete.ts
14627
+ function deleteFromKeychain() {
14628
+ try {
14629
+ new Entry(service, account).deletePassword();
14630
+ return "deleted";
14631
+ } catch {
14632
+ return "unavailable";
14633
+ }
14634
+ }
14635
+ async function deleteFromFile(configDir, fs) {
14636
+ try {
14637
+ await fs.unlink(join2(configDir, credentialsFile));
14638
+ return "deleted";
14639
+ } catch (err) {
14640
+ if (isNoEntError(err))
14641
+ return "not-found";
14642
+ throw err;
14643
+ }
14644
+ }
14645
+ async function deleteApiKey(configDir, fs) {
14646
+ const [keychain, file] = await Promise.all([
14647
+ Promise.resolve(deleteFromKeychain()),
14648
+ deleteFromFile(configDir, fs)
14649
+ ]);
14650
+ return { keychain, file };
14651
+ }
14652
+ // src/domains/auth/store/hasStoredCredentials.ts
14653
+ import { join as join3 } from "node:path";
14654
+ import { Entry as Entry2 } from "@napi-rs/keyring";
14655
+ async function fileExists(path, fs) {
14656
+ try {
14657
+ await fs.readFile(path);
14658
+ return true;
14659
+ } catch (err) {
14660
+ return !isNoEntError(err);
14661
+ }
14662
+ }
14663
+ function keychainHolds(entryAccount) {
14664
+ try {
14665
+ return Boolean(new Entry2(service, entryAccount).getPassword());
14666
+ } catch {
14667
+ return false;
14668
+ }
14669
+ }
14670
+ async function hasStoredCredentials(configDir, fs) {
14671
+ const [apiKeyFile, tokenFile] = await Promise.all([
14672
+ fileExists(join3(configDir, credentialsFile), fs),
14673
+ fileExists(join3(configDir, tokensFile), fs)
14674
+ ]);
14675
+ return apiKeyFile || tokenFile || keychainHolds(account) || keychainHolds(tokensAccount);
14676
+ }
14677
+ // src/domains/auth/store/load.ts
14678
+ import { join as join4 } from "node:path";
14679
+
14427
14680
  // src/domains/auth/store/types.ts
14428
14681
  var credentialsFileSchema = object({
14429
14682
  apiKey: string2().min(1)
14430
14683
  });
14684
+ var oauthTokensSchema = object({
14685
+ accessToken: string2().min(1),
14686
+ refreshToken: string2().min(1),
14687
+ expiresAt: number2().int().optional(),
14688
+ email: string2().min(1),
14689
+ organizationId: string2().min(1).optional(),
14690
+ workspaceId: string2().min(1).optional(),
14691
+ clientId: string2().min(1).optional()
14692
+ });
14431
14693
 
14432
14694
  // src/domains/auth/store/load.ts
14433
14695
  async function loadApiKey(configDir, deps) {
@@ -14441,7 +14703,7 @@ async function loadApiKey(configDir, deps) {
14441
14703
  errors.keychain = errorMessage(err);
14442
14704
  }
14443
14705
  try {
14444
- const content = await deps.fs.readFile(join3(configDir, credentialsFile));
14706
+ const content = await deps.fs.readFile(join4(configDir, credentialsFile));
14445
14707
  const parsed = credentialsFileSchema.safeParse(JSON.parse(content));
14446
14708
  if (parsed.success) {
14447
14709
  return { found: true, key: parsed.data.apiKey, source: "file" };
@@ -14453,16 +14715,16 @@ async function loadApiKey(configDir, deps) {
14453
14715
  return { found: false, errors };
14454
14716
  }
14455
14717
  // src/domains/auth/store/save.ts
14456
- import { join as join4 } from "node:path";
14457
- import { Entry as Entry2 } from "@napi-rs/keyring";
14718
+ import { join as join5 } from "node:path";
14719
+ import { Entry as Entry3 } from "@napi-rs/keyring";
14458
14720
  async function saveToFile(configDir, key, fs) {
14459
14721
  const payload = { apiKey: key };
14460
14722
  await fs.mkdir(configDir, { recursive: true, mode: 448 });
14461
- await fs.writeFile(join4(configDir, credentialsFile), JSON.stringify(payload, undefined, 2), { mode: 384 });
14723
+ await fs.writeFile(join5(configDir, credentialsFile), JSON.stringify(payload, undefined, 2), { mode: 384 });
14462
14724
  }
14463
14725
  async function saveApiKey(configDir, key, fs) {
14464
14726
  try {
14465
- const entry = new Entry2(service, account);
14727
+ const entry = new Entry3(service, account);
14466
14728
  entry.setPassword(key);
14467
14729
  return { stored: "keychain" };
14468
14730
  } catch (err) {
@@ -14470,10 +14732,159 @@ async function saveApiKey(configDir, key, fs) {
14470
14732
  return { stored: "file", keychainError: errorMessage(err) };
14471
14733
  }
14472
14734
  }
14735
+ // src/domains/auth/store/loadTokens.ts
14736
+ import { join as join6 } from "node:path";
14737
+ var invalidPayload = "Invalid stored token format";
14738
+ function parseTokens(raw) {
14739
+ const parsed = oauthTokensSchema.safeParse(JSON.parse(raw));
14740
+ if (!parsed.success)
14741
+ return;
14742
+ return {
14743
+ accessToken: parsed.data.accessToken,
14744
+ refreshToken: parsed.data.refreshToken,
14745
+ expiresAt: parsed.data.expiresAt,
14746
+ email: parsed.data.email,
14747
+ organizationId: parsed.data.organizationId,
14748
+ workspaceId: parsed.data.workspaceId,
14749
+ clientId: parsed.data.clientId
14750
+ };
14751
+ }
14752
+ async function loadTokens(configDir, deps) {
14753
+ const errors = {};
14754
+ try {
14755
+ const raw = new deps.EntryClass(service, tokensAccount).getPassword();
14756
+ if (raw) {
14757
+ const tokens = parseTokens(raw);
14758
+ if (tokens)
14759
+ return { found: true, tokens, source: "keychain" };
14760
+ errors.keychain = invalidPayload;
14761
+ }
14762
+ } catch (err) {
14763
+ errors.keychain = errorMessage(err);
14764
+ }
14765
+ try {
14766
+ const raw = await deps.fs.readFile(join6(configDir, tokensFile));
14767
+ const tokens = parseTokens(raw);
14768
+ if (tokens)
14769
+ return { found: true, tokens, source: "file" };
14770
+ errors.file = invalidPayload;
14771
+ } catch (err) {
14772
+ errors.file = errorMessage(err);
14773
+ }
14774
+ return { found: false, errors };
14775
+ }
14776
+
14777
+ // src/domains/auth/store/saveTokens.ts
14778
+ import { randomUUID } from "node:crypto";
14779
+ import { join as join7 } from "node:path";
14780
+ import { Entry as Entry4 } from "@napi-rs/keyring";
14781
+ async function saveToFile2(configDir, tokens, fs) {
14782
+ await fs.mkdir(configDir, { recursive: true, mode: 448 });
14783
+ const target = join7(configDir, tokensFile);
14784
+ const staging = `${target}.${randomUUID()}.tmp`;
14785
+ try {
14786
+ await fs.writeFile(staging, JSON.stringify(tokens, undefined, 2), {
14787
+ mode: 384
14788
+ });
14789
+ await fs.rename(staging, target);
14790
+ } catch (err) {
14791
+ await fs.unlink(staging).catch(() => {});
14792
+ throw err;
14793
+ }
14794
+ }
14795
+ async function saveTokens(configDir, tokens, fs) {
14796
+ try {
14797
+ new Entry4(service, tokensAccount).setPassword(JSON.stringify(tokens));
14798
+ return { stored: "keychain" };
14799
+ } catch (err) {
14800
+ try {
14801
+ new Entry4(service, tokensAccount).deletePassword();
14802
+ } catch {}
14803
+ await saveToFile2(configDir, tokens, fs);
14804
+ return { stored: "file", keychainError: errorMessage(err) };
14805
+ }
14806
+ }
14807
+
14808
+ // src/domains/auth/resolveOauthToken.ts
14809
+ var expiryMarginMs = 30000;
14810
+ async function resolveOauthToken(configDir, deps) {
14811
+ const stored = await deps.loadTokens(configDir);
14812
+ if (!stored.found)
14813
+ return;
14814
+ const { tokens } = stored;
14815
+ const expiresAt = tokens.expiresAt;
14816
+ const isFresh = expiresAt !== undefined && expiresAt - expiryMarginMs > deps.now();
14817
+ if (isFresh) {
14818
+ return {
14819
+ key: tokens.accessToken,
14820
+ email: tokens.email,
14821
+ workspaceId: tokens.workspaceId
14822
+ };
14823
+ }
14824
+ const refreshed = await deps.refreshTokens({
14825
+ refreshToken: tokens.refreshToken,
14826
+ organizationId: tokens.organizationId,
14827
+ clientId: tokens.clientId
14828
+ });
14829
+ if (!refreshed.ok) {
14830
+ if (refreshed.retryable && expiresAt !== undefined && expiresAt > deps.now()) {
14831
+ return {
14832
+ key: tokens.accessToken,
14833
+ email: tokens.email,
14834
+ workspaceId: tokens.workspaceId
14835
+ };
14836
+ }
14837
+ const current = await deps.loadTokens(configDir);
14838
+ if (current.found && current.tokens.refreshToken !== tokens.refreshToken && current.tokens.email === tokens.email && current.tokens.organizationId === tokens.organizationId && current.tokens.clientId === tokens.clientId && current.tokens.expiresAt !== undefined && current.tokens.expiresAt > deps.now()) {
14839
+ return {
14840
+ key: current.tokens.accessToken,
14841
+ email: current.tokens.email,
14842
+ workspaceId: current.tokens.workspaceId
14843
+ };
14844
+ }
14845
+ return;
14846
+ }
14847
+ try {
14848
+ await deps.saveTokens(configDir, {
14849
+ ...refreshed.value,
14850
+ workspaceId: tokens.workspaceId,
14851
+ clientId: tokens.clientId
14852
+ });
14853
+ } catch {}
14854
+ return {
14855
+ key: refreshed.value.accessToken,
14856
+ email: refreshed.value.email,
14857
+ workspaceId: tokens.workspaceId
14858
+ };
14859
+ }
14860
+
14473
14861
  // src/domains/auth/resolve.ts
14862
+ function makeOauthDeps(fs) {
14863
+ return {
14864
+ loadTokens: (configDir) => loadTokens(configDir, { EntryClass: Entry5, fs }),
14865
+ refreshTokens: async ({ refreshToken, organizationId, clientId }) => {
14866
+ const config = resolveWorkosConfig(clientId);
14867
+ if (!config.configured) {
14868
+ return {
14869
+ ok: false,
14870
+ error: authErrorMessages.workos.noClientForSession,
14871
+ retryable: false
14872
+ };
14873
+ }
14874
+ return refreshAccessToken(refreshToken, organizationId, {
14875
+ fetch: globalThis.fetch,
14876
+ baseUrl: config.baseUrl,
14877
+ clientId: config.clientId
14878
+ });
14879
+ },
14880
+ saveTokens: (configDir, tokens) => saveTokens(configDir, tokens, fs),
14881
+ now: () => Date.now()
14882
+ };
14883
+ }
14474
14884
  function makeDefaultDeps(fs) {
14475
14885
  return {
14476
- loadApiKey: (configDir) => loadApiKey(configDir, { EntryClass: Entry3, fs }),
14886
+ loadApiKey: (configDir) => loadApiKey(configDir, { EntryClass: Entry5, fs }),
14887
+ resolveOauth: (configDir) => resolveOauthToken(configDir, makeOauthDeps(fs)),
14477
14888
  env: process.env
14478
14889
  };
14479
14890
  }
@@ -14481,11 +14892,19 @@ async function resolveApiKey(configDir, fs, deps) {
14481
14892
  const resolvedDeps = deps ?? makeDefaultDeps(fs);
14482
14893
  const envKey = resolvedDeps.env["QAWOLF_API_KEY"];
14483
14894
  if (envKey?.trim()) {
14484
- return { key: envKey.trim(), source: "env" };
14895
+ return { key: envKey.trim(), source: "env", workspaceId: undefined };
14485
14896
  }
14486
14897
  const stored = await resolvedDeps.loadApiKey(configDir);
14487
14898
  if (stored.found) {
14488
- return { key: stored.key, source: stored.source };
14899
+ return { key: stored.key, source: stored.source, workspaceId: undefined };
14900
+ }
14901
+ const oauth = await resolvedDeps.resolveOauth(configDir);
14902
+ if (oauth) {
14903
+ return {
14904
+ key: oauth.key,
14905
+ source: "browser",
14906
+ workspaceId: oauth.workspaceId
14907
+ };
14489
14908
  }
14490
14909
  return;
14491
14910
  }
@@ -14507,9 +14926,9 @@ async function validateApiKey(deps) {
14507
14926
  // src/shell/logger.ts
14508
14927
  var import_pino = __toESM(require_pino(), 1);
14509
14928
  import { mkdirSync as mkdirSync2, openSync } from "node:fs";
14510
- import { dirname, join as join5 } from "node:path";
14929
+ import { dirname, join as join8 } from "node:path";
14511
14930
  function defaultLogPath() {
14512
- return join5(envPaths("qawolf", { suffix: "" }).log, "cli.log");
14931
+ return join8(envPaths("qawolf", { suffix: "" }).log, "cli.log");
14513
14932
  }
14514
14933
  function resolveStderrLevel(env, verbose) {
14515
14934
  if (verbose)
@@ -14577,14 +14996,184 @@ function createLoggingSystem(opts) {
14577
14996
  };
14578
14997
  }
14579
14998
 
14580
- // src/shell/platform/parseErrorBody.ts
14581
- var defaultMaxLength = 1024;
14582
- function asRecord(value) {
14583
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
14584
- }
14585
- function asString(value) {
14586
- return typeof value === "string" ? value : undefined;
14587
- }
14999
+ // src/core/publicApi/flagKind.ts
15000
+ function flagKind(schema) {
15001
+ if (schema.type === "string")
15002
+ return { ok: true, kind: "string" };
15003
+ if (schema.type === "number" || schema.type === "integer") {
15004
+ return { ok: true, kind: "number" };
15005
+ }
15006
+ if (schema.type === "boolean")
15007
+ return { ok: true, kind: "boolean" };
15008
+ if (schema.type === "array") {
15009
+ if (schema.items?.type !== "string") {
15010
+ return {
15011
+ ok: false,
15012
+ reason: "only string arrays can be expressed as flags"
15013
+ };
15014
+ }
15015
+ return { ok: true, kind: "string-array" };
15016
+ }
15017
+ if (schema.type === "object") {
15018
+ if (schema.properties) {
15019
+ return {
15020
+ ok: false,
15021
+ reason: "nested objects cannot be expressed as flags"
15022
+ };
15023
+ }
15024
+ if (typeof schema.additionalProperties === "object" && schema.additionalProperties.type === "string") {
15025
+ return { ok: true, kind: "key-value-record" };
15026
+ }
15027
+ return { ok: false, reason: "nested objects cannot be expressed as flags" };
15028
+ }
15029
+ return {
15030
+ ok: false,
15031
+ reason: `type ${schema.type ?? "unknown"} cannot be expressed as a flag`
15032
+ };
15033
+ }
15034
+
15035
+ // src/core/publicApi/objectShape.ts
15036
+ var asObjectShape = (schema) => schema.type === "object" && schema.properties ? { properties: schema.properties, required: new Set(schema.required) } : undefined;
15037
+ function mergeIntersection(members) {
15038
+ const shape = { properties: {}, required: new Set };
15039
+ for (const member of members) {
15040
+ const memberShape = asObjectShape(member);
15041
+ if (!memberShape) {
15042
+ return {
15043
+ ok: false,
15044
+ field: "",
15045
+ reason: "intersection members must be object schemas"
15046
+ };
15047
+ }
15048
+ for (const [field, fieldSchema] of Object.entries(memberShape.properties)) {
15049
+ if (field in shape.properties) {
15050
+ return {
15051
+ ok: false,
15052
+ field,
15053
+ reason: "field appears in multiple intersection members"
15054
+ };
15055
+ }
15056
+ shape.properties[field] = fieldSchema;
15057
+ if (memberShape.required.has(field))
15058
+ shape.required.add(field);
15059
+ }
15060
+ }
15061
+ return { ok: true, shape };
15062
+ }
15063
+ function mergeUnion(branches) {
15064
+ const shapes = [];
15065
+ for (const branch of branches) {
15066
+ const branchShape = asObjectShape(branch);
15067
+ if (!branchShape) {
15068
+ return {
15069
+ ok: false,
15070
+ field: "",
15071
+ reason: "union branches must be object schemas"
15072
+ };
15073
+ }
15074
+ shapes.push(branchShape);
15075
+ }
15076
+ const shape = { properties: {}, required: new Set };
15077
+ for (const branchShape of shapes) {
15078
+ for (const [field, fieldSchema] of Object.entries(branchShape.properties)) {
15079
+ const existing = shape.properties[field];
15080
+ if (!existing) {
15081
+ shape.properties[field] = { ...fieldSchema };
15082
+ continue;
15083
+ }
15084
+ const kinds = [flagKind(existing), flagKind(fieldSchema)];
15085
+ if (kinds[0]?.ok && kinds[1]?.ok) {
15086
+ if (kinds[0].kind !== kinds[1].kind) {
15087
+ return {
15088
+ ok: false,
15089
+ field,
15090
+ reason: "field has conflicting types across union branches"
15091
+ };
15092
+ }
15093
+ } else if (JSON.stringify({ ...existing, description: undefined }) !== JSON.stringify({ ...fieldSchema, description: undefined })) {
15094
+ return {
15095
+ ok: false,
15096
+ field,
15097
+ reason: "field has conflicting object schemas across union branches"
15098
+ };
15099
+ }
15100
+ if (!existing.description && fieldSchema.description) {
15101
+ existing.description = fieldSchema.description;
15102
+ }
15103
+ }
15104
+ }
15105
+ const discriminator = Object.keys(shape.properties).find((field) => {
15106
+ const values = shapes.map((branch) => branch.properties[field]?.const);
15107
+ return values.every((value) => typeof value === "string") && new Set(values).size === shapes.length && shapes.every((branch) => branch.required.has(field));
15108
+ });
15109
+ for (const [field, fieldSchema] of Object.entries(shape.properties)) {
15110
+ const occurrences = shapes.filter((branch) => (field in branch.properties));
15111
+ if (occurrences.length === shapes.length && occurrences.every((branch) => branch.required.has(field))) {
15112
+ shape.required.add(field);
15113
+ }
15114
+ if (field === discriminator) {
15115
+ const values = shapes.map((branch) => branch.properties[field]?.const);
15116
+ fieldSchema.description = `One of: ${values.join(", ")}`;
15117
+ }
15118
+ }
15119
+ return { ok: true, shape };
15120
+ }
15121
+ function toObjectShape(schema) {
15122
+ if (schema.allOf)
15123
+ return mergeIntersection(schema.allOf);
15124
+ const branches = schema.oneOf ?? schema.anyOf;
15125
+ if (branches)
15126
+ return mergeUnion(branches);
15127
+ const shape = asObjectShape(schema);
15128
+ if (shape)
15129
+ return { ok: true, shape };
15130
+ return {
15131
+ ok: false,
15132
+ field: "",
15133
+ reason: "contract input must be an object schema"
15134
+ };
15135
+ }
15136
+
15137
+ // src/shell/platform/applyWorkspaceId.ts
15138
+ var acceptsWorkspace = new WeakMap;
15139
+ function takesWorkspaceId(inputSchema) {
15140
+ const cached = acceptsWorkspace.get(inputSchema);
15141
+ if (cached !== undefined)
15142
+ return cached;
15143
+ let accepted = false;
15144
+ try {
15145
+ const jsonSchema = toJSONSchema(inputSchema, {
15146
+ io: "input"
15147
+ });
15148
+ const shape = toObjectShape(jsonSchema);
15149
+ accepted = shape.ok && "workspaceId" in shape.shape.properties;
15150
+ } catch {
15151
+ accepted = false;
15152
+ }
15153
+ acceptsWorkspace.set(inputSchema, accepted);
15154
+ return accepted;
15155
+ }
15156
+ function applyWorkspaceId(inputSchema, input, workspaceId) {
15157
+ if (!workspaceId)
15158
+ return input;
15159
+ if (typeof input !== "object" || input === null)
15160
+ return input;
15161
+ const existing = input.workspaceId;
15162
+ if (existing !== undefined)
15163
+ return input;
15164
+ if (!takesWorkspaceId(inputSchema))
15165
+ return input;
15166
+ return { ...input, workspaceId };
15167
+ }
15168
+
15169
+ // src/shell/platform/parseErrorBody.ts
15170
+ var defaultMaxLength = 1024;
15171
+ function asRecord(value) {
15172
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
15173
+ }
15174
+ function asString(value) {
15175
+ return typeof value === "string" ? value : undefined;
15176
+ }
14588
15177
  function extractMessage(parsed) {
14589
15178
  const root = asRecord(parsed);
14590
15179
  if (!root)
@@ -14743,7 +15332,7 @@ function callPublicApi(trpc, contract, input, options) {
14743
15332
  }
14744
15333
  function makeCallPublicApiMethod(trpc, deps, readBackoffMs) {
14745
15334
  return async (contract, input, options) => requestWithRetry({
14746
- call: () => callPublicApi(trpc, contract, input, options),
15335
+ call: () => callPublicApi(trpc, contract, applyWorkspaceId(contract.input, input, deps.workspaceId), options),
14747
15336
  backoffMs: contract.kind === "read" ? readBackoffMs : [],
14748
15337
  describe: (err) => describeRequestError(err, deps.baseUrl, contract.name),
14749
15338
  sleep: deps.sleep
@@ -15646,7 +16235,7 @@ function summarize(error) {
15646
16235
  // src/shell/platform/downloadBundle.ts
15647
16236
  import { randomBytes } from "node:crypto";
15648
16237
  import { tmpdir as tmpdir2 } from "node:os";
15649
- import { join as join6 } from "node:path";
16238
+ import { join as join9 } from "node:path";
15650
16239
 
15651
16240
  // src/shell/platform/fetchSignedUrl.ts
15652
16241
  var defaultStallTimeoutMs = 30000;
@@ -15734,7 +16323,7 @@ async function fetchSignedUrl(args, deps = { fetch: globalThis.fetch }) {
15734
16323
 
15735
16324
  // src/shell/platform/downloadBundle.ts
15736
16325
  async function downloadBundle(signedUrl, deps) {
15737
- const tmpArchive = join6(tmpdir2(), `qawolf-pull-${randomBytes(8).toString("hex")}.tar.gz`);
16326
+ const tmpArchive = join9(tmpdir2(), `qawolf-pull-${randomBytes(8).toString("hex")}.tar.gz`);
15738
16327
  const result = await fetchSignedUrl({ url: signedUrl, dest: tmpArchive }, { fetch: deps.fetch, fs: deps.fs });
15739
16328
  if (!result.ok) {
15740
16329
  await deps.fs.unlink(tmpArchive).catch(() => {});
@@ -15838,6 +16427,10 @@ var identityResponse = union([
15838
16427
  user: never().optional()
15839
16428
  }).transform(({ organization }) => ({ organization }))
15840
16429
  ]);
16430
+ // node_modules/@qawolf/api-contracts/dist/v1/identity/organizations.js
16431
+ var identityOrganizationsResponse = object({
16432
+ organizations: array(identityOrganization)
16433
+ });
15841
16434
  // node_modules/@qawolf/api-contracts/dist/v1/ids.js
15842
16435
  var defaultIdSchemas = {
15843
16436
  aiTask: string2().min(1).describe("The id of the AI task."),
@@ -17537,19 +18130,70 @@ var makeContractsV1 = (ids) => {
17537
18130
  };
17538
18131
  var publicContractsV1 = makeContractsV1(defaultIdSchemas);
17539
18132
 
18133
+ // src/shell/platform/organizations.ts
18134
+ function parseOrganizationsResponse(json) {
18135
+ const parsed = identityOrganizationsResponse.safeParse(json);
18136
+ return parsed.success ? parsed.data.organizations : undefined;
18137
+ }
18138
+
18139
+ // src/shell/platform/getAccessibleOrganizations.ts
18140
+ var timeoutMs2 = 1e4;
18141
+ async function getAccessibleOrganizations(apiKey, deps) {
18142
+ const url = `${deps.baseUrl}/api/v0/identity/organizations`;
18143
+ let response;
18144
+ try {
18145
+ response = await deps.fetch(url, {
18146
+ headers: { Authorization: `Bearer ${apiKey}` },
18147
+ signal: AbortSignal.timeout(timeoutMs2)
18148
+ });
18149
+ } catch (error) {
18150
+ if (isTimeoutError(error)) {
18151
+ return { ok: false, error: { kind: "timeout", timeoutMs: timeoutMs2 } };
18152
+ }
18153
+ return { ok: false, error: { kind: "network", cause: toError(error) } };
18154
+ }
18155
+ if (!response.ok) {
18156
+ const body = await response.text().catch(() => "");
18157
+ return {
18158
+ ok: false,
18159
+ error: { kind: "http", status: response.status, body }
18160
+ };
18161
+ }
18162
+ let json;
18163
+ try {
18164
+ json = await response.json();
18165
+ } catch (error) {
18166
+ if (isTimeoutError(error)) {
18167
+ return { ok: false, error: { kind: "timeout", timeoutMs: timeoutMs2 } };
18168
+ }
18169
+ json = undefined;
18170
+ }
18171
+ const organizations = parseOrganizationsResponse(json);
18172
+ if (!organizations) {
18173
+ return {
18174
+ ok: false,
18175
+ error: {
18176
+ kind: "parse",
18177
+ cause: Error("organizations response did not match the contract")
18178
+ }
18179
+ };
18180
+ }
18181
+ return { ok: true, data: organizations };
18182
+ }
18183
+
17540
18184
  // src/shell/platform/getIdentity.ts
17541
- var timeoutMs = 1e4;
18185
+ var timeoutMs3 = 1e4;
17542
18186
  async function getIdentity(apiKey, deps) {
17543
18187
  const url = `${deps.baseUrl}/api/v0/identity`;
17544
18188
  let response;
17545
18189
  try {
17546
18190
  response = await deps.fetch(url, {
17547
18191
  headers: { Authorization: `Bearer ${apiKey}` },
17548
- signal: AbortSignal.timeout(timeoutMs)
18192
+ signal: AbortSignal.timeout(timeoutMs3)
17549
18193
  });
17550
18194
  } catch (error) {
17551
18195
  if (isTimeoutError(error)) {
17552
- return { ok: false, error: { kind: "timeout", timeoutMs } };
18196
+ return { ok: false, error: { kind: "timeout", timeoutMs: timeoutMs3 } };
17553
18197
  }
17554
18198
  return {
17555
18199
  ok: false,
@@ -17568,7 +18212,7 @@ async function getIdentity(apiKey, deps) {
17568
18212
  json = await response.json();
17569
18213
  } catch (error) {
17570
18214
  if (isTimeoutError(error)) {
17571
- return { ok: false, error: { kind: "timeout", timeoutMs } };
18215
+ return { ok: false, error: { kind: "timeout", timeoutMs: timeoutMs3 } };
17572
18216
  }
17573
18217
  json = undefined;
17574
18218
  }
@@ -17579,6 +18223,24 @@ async function getIdentity(apiKey, deps) {
17579
18223
  return { ok: true, data: parsed.data };
17580
18224
  }
17581
18225
 
18226
+ // src/shell/platform/identityMethods.ts
18227
+ function createIdentityMethods(apiKey, deps, backoffMs) {
18228
+ return {
18229
+ getIdentity: () => requestWithRetry({
18230
+ call: () => getIdentity(apiKey, deps),
18231
+ backoffMs,
18232
+ describe: describeIdentityError,
18233
+ sleep: deps.sleep
18234
+ }),
18235
+ getAccessibleOrganizations: () => requestWithRetry({
18236
+ call: () => getAccessibleOrganizations(apiKey, deps),
18237
+ backoffMs,
18238
+ describe: describeIdentityError,
18239
+ sleep: deps.sleep
18240
+ })
18241
+ };
18242
+ }
18243
+
17582
18244
  // src/shell/platform/types.ts
17583
18245
  var environmentListItemSchema = object({
17584
18246
  envId: string2(),
@@ -17706,7 +18368,7 @@ function isUnsafeSegment(segment) {
17706
18368
 
17707
18369
  // src/shell/platform/teamStorageAssetSnapshot.ts
17708
18370
  import { randomBytes as randomBytes2 } from "node:crypto";
17709
- import { join as join7, sep as sep2 } from "node:path";
18371
+ import { join as join10, sep as sep2 } from "node:path";
17710
18372
  function mintAssetSnapshotPath(assetsAbs, label) {
17711
18373
  return `${assetsAbs}.${label}-${randomBytes2(8).toString("hex")}`;
17712
18374
  }
@@ -17756,9 +18418,9 @@ function expectedEntries(filePaths) {
17756
18418
  return entries;
17757
18419
  }
17758
18420
  async function collectEntries(root, relDir, out, fs) {
17759
- const current = relDir ? join7(root, relDir) : root;
18421
+ const current = relDir ? join10(root, relDir) : root;
17760
18422
  for (const entry of await fs.readdirWithTypes(current)) {
17761
- const relPath = relDir ? join7(relDir, entry.name) : entry.name;
18423
+ const relPath = relDir ? join10(relDir, entry.name) : entry.name;
17762
18424
  out.add(relPath);
17763
18425
  if (entry.isDirectory()) {
17764
18426
  await collectEntries(root, relPath, out, fs);
@@ -17772,7 +18434,7 @@ function setsEqual(left, right) {
17772
18434
  }
17773
18435
 
17774
18436
  // src/shell/platform/teamStorageAssetReuse.ts
17775
- import { join as join8 } from "node:path";
18437
+ import { join as join11 } from "node:path";
17776
18438
  async function reusableAssetPaths(assetsAbs, safeFiles, manifest, fs) {
17777
18439
  const reusable = new Set;
17778
18440
  for (const { file, relativePath } of safeFiles) {
@@ -17788,20 +18450,20 @@ async function canReuseAsset(assetsAbs, relativePath, file, manifest, fs) {
17788
18450
  const previous = manifest.get(relativePath);
17789
18451
  if (previous?.etag !== file.etag)
17790
18452
  return false;
17791
- return fs.pathExists(join8(assetsAbs, relativePath));
18453
+ return fs.pathExists(join11(assetsAbs, relativePath));
17792
18454
  }
17793
18455
 
17794
18456
  // src/shell/platform/writeAssetSnapshot.ts
17795
- import { dirname as dirname3, join as join9 } from "node:path";
18457
+ import { dirname as dirname3, join as join12 } from "node:path";
17796
18458
  async function writeAssetSnapshot(args) {
17797
18459
  let downloadedCount = 0;
17798
18460
  const total = args.safeFiles.filter(({ relativePath }) => !args.reusable.has(relativePath)).length;
17799
18461
  await args.deps.fs.mkdir(args.tmpAssets, { recursive: true });
17800
18462
  for (const { file, relativePath } of args.safeFiles) {
17801
- const dest = join9(args.tmpAssets, relativePath);
18463
+ const dest = join12(args.tmpAssets, relativePath);
17802
18464
  await args.deps.fs.mkdir(dirname3(dest), { recursive: true });
17803
18465
  if (args.reusable.has(relativePath)) {
17804
- await args.deps.fs.copyFile(join9(args.assetsAbs, relativePath), dest);
18466
+ await args.deps.fs.copyFile(join12(args.assetsAbs, relativePath), dest);
17805
18467
  continue;
17806
18468
  }
17807
18469
  args.deps.onProgress?.({ current: downloadedCount + 1, total });
@@ -17878,14 +18540,7 @@ function createPlatformClient(apiKey, deps) {
17878
18540
  return { ok: true, value: { signedUrl: result.value.url } };
17879
18541
  }
17880
18542
  return {
17881
- async getIdentity() {
17882
- return requestWithRetry({
17883
- call: () => getIdentity(apiKey, deps),
17884
- backoffMs: requestBackoffMs2,
17885
- describe: describeIdentityError,
17886
- sleep: deps.sleep
17887
- });
17888
- },
18543
+ ...createIdentityMethods(apiKey, deps, requestBackoffMs2),
17889
18544
  getFlowsBundleUrl: getFlowsBundleUrlImpl,
17890
18545
  callPublicApi: makeCallPublicApiMethod(trpc, deps, requestBackoffMs2),
17891
18546
  async getEnvVars(envId) {
@@ -18584,6 +19239,86 @@ class V {
18584
19239
  }
18585
19240
  }
18586
19241
  }
19242
+ function p$1(l, e) {
19243
+ if (l === undefined || e.length === 0)
19244
+ return 0;
19245
+ const i = e.findIndex((s) => s.value === l);
19246
+ return i !== -1 ? i : 0;
19247
+ }
19248
+ function g(l, e) {
19249
+ return (e.label ?? String(e.value)).toLowerCase().includes(l.toLowerCase());
19250
+ }
19251
+ function m2(l, e) {
19252
+ if (e)
19253
+ return l ? e : e[0];
19254
+ }
19255
+ var T$1 = class T extends V {
19256
+ filteredOptions;
19257
+ multiple;
19258
+ isNavigating = false;
19259
+ selectedValues = [];
19260
+ focusedValue;
19261
+ #e = 0;
19262
+ #s = "";
19263
+ #t;
19264
+ #i;
19265
+ #n;
19266
+ get cursor() {
19267
+ return this.#e;
19268
+ }
19269
+ get userInputWithCursor() {
19270
+ if (!this.userInput)
19271
+ return styleText(["inverse", "hidden"], "_");
19272
+ if (this._cursor >= this.userInput.length)
19273
+ return `${this.userInput}█`;
19274
+ const e = this.userInput.slice(0, this._cursor), [t, ...i] = this.userInput.slice(this._cursor);
19275
+ return `${e}${styleText("inverse", t)}${i.join("")}`;
19276
+ }
19277
+ get options() {
19278
+ return typeof this.#i == "function" ? this.#i() : this.#i;
19279
+ }
19280
+ constructor(e) {
19281
+ super(e), this.#i = e.options, this.#n = e.placeholder;
19282
+ const t = this.options;
19283
+ this.filteredOptions = [...t], this.multiple = e.multiple === true, this.#t = typeof e.options == "function" ? e.filter : e.filter ?? g;
19284
+ let i;
19285
+ if (e.initialValue && Array.isArray(e.initialValue) ? this.multiple ? i = e.initialValue : i = e.initialValue.slice(0, 1) : !this.multiple && this.options.length > 0 && (i = [this.options[0].value]), i)
19286
+ for (const s of i) {
19287
+ const n = t.findIndex((o) => o.value === s);
19288
+ n !== -1 && (this.toggleSelected(s), this.#e = n);
19289
+ }
19290
+ this.focusedValue = this.options[this.#e]?.value, this.on("key", (s, n) => this.#l(s, n)), this.on("userInput", (s) => this.#u(s));
19291
+ }
19292
+ _isActionKey(e, t) {
19293
+ return e === "\t" || this.multiple && this.isNavigating && t.name === "space" && e !== undefined && e !== "";
19294
+ }
19295
+ #l(e, t) {
19296
+ const i = t.name === "up", s = t.name === "down", n = t.name === "return", o = this.userInput === "" || this.userInput === "\t", u = this.#n, h = this.options, f = u !== undefined && u !== "" && h.some((r) => !r.disabled && (this.#t ? this.#t(u, r) : true));
19297
+ if (t.name === "tab" && o && f) {
19298
+ this.userInput === "\t" && this._clearUserInput(), this._setUserInput(u, true), this.isNavigating = false;
19299
+ return;
19300
+ }
19301
+ i || s ? (this.#e = findCursor(this.#e, i ? -1 : 1, this.filteredOptions), this.focusedValue = this.filteredOptions[this.#e]?.value, this.multiple || (this.selectedValues = [this.focusedValue]), this.isNavigating = true) : n ? this.value = m2(this.multiple, this.selectedValues) : this.multiple ? this.focusedValue !== undefined && (t.name === "tab" || this.isNavigating && t.name === "space") ? this.toggleSelected(this.focusedValue) : this.isNavigating = false : (this.focusedValue && (this.selectedValues = [this.focusedValue]), this.isNavigating = false);
19302
+ }
19303
+ deselectAll() {
19304
+ this.selectedValues = [];
19305
+ }
19306
+ toggleSelected(e) {
19307
+ this.filteredOptions.length !== 0 && (this.multiple ? this.selectedValues.includes(e) ? this.selectedValues = this.selectedValues.filter((t) => t !== e) : this.selectedValues = [...this.selectedValues, e] : this.selectedValues = [e]);
19308
+ }
19309
+ #u(e) {
19310
+ if (e !== this.#s) {
19311
+ this.#s = e;
19312
+ const t = this.options;
19313
+ e && this.#t ? this.filteredOptions = t.filter((n) => this.#t?.(e, n)) : this.filteredOptions = [...t];
19314
+ const i = p$1(this.focusedValue, this.filteredOptions);
19315
+ this.#e = findCursor(i, 0, this.filteredOptions);
19316
+ const s = this.filteredOptions[this.#e];
19317
+ s && !s.disabled ? this.focusedValue = s.value : this.focusedValue = undefined, this.multiple || (this.focusedValue !== undefined ? this.toggleSelected(this.focusedValue) : this.deselectAll());
19318
+ }
19319
+ }
19320
+ };
19321
+
18587
19322
  class r extends V {
18588
19323
  get cursor() {
18589
19324
  return this.value ? 0 : 1;
@@ -18770,6 +19505,89 @@ var limitOptions = ({
18770
19505
  b.push(n);
18771
19506
  return u && b.push(C), b;
18772
19507
  };
19508
+ function P(t) {
19509
+ return t.label ?? String(t.value ?? "");
19510
+ }
19511
+ function E(t, c) {
19512
+ if (!t)
19513
+ return true;
19514
+ const n = (c.label ?? String(c.value ?? "")).toLowerCase(), i = (c.hint ?? "").toLowerCase(), l = String(c.value).toLowerCase(), o = t.toLowerCase();
19515
+ return n.includes(o) || i.includes(o) || l.includes(o);
19516
+ }
19517
+ function N(t, c) {
19518
+ const n = [];
19519
+ for (const i of c)
19520
+ t.includes(i.value) && n.push(i);
19521
+ return n;
19522
+ }
19523
+ var autocomplete = (t) => new T$1({
19524
+ options: t.options,
19525
+ initialValue: t.initialValue ? [t.initialValue] : undefined,
19526
+ initialUserInput: t.initialUserInput,
19527
+ placeholder: t.placeholder,
19528
+ filter: t.filter ?? ((n, i) => E(n, i)),
19529
+ signal: t.signal,
19530
+ input: t.input,
19531
+ output: t.output,
19532
+ validate: t.validate,
19533
+ render() {
19534
+ const n = t.withGuide ?? settings.withGuide, i = n ? [`${styleText2("gray", S_BAR)}`, `${symbol(this.state)} ${t.message}`] : [`${symbol(this.state)} ${t.message}`], l = this.userInput, o = this.options, m = t.placeholder, p = l === "" && m !== undefined, $ = (r, s) => {
19535
+ const a = P(r), u = r.hint && r.value === this.focusedValue ? styleText2("dim", ` (${r.hint})`) : "";
19536
+ switch (s) {
19537
+ case "active":
19538
+ return `${styleText2("green", S_RADIO_ACTIVE)} ${a}${u}`;
19539
+ case "inactive":
19540
+ return `${styleText2("dim", S_RADIO_INACTIVE)} ${styleText2("dim", a)}`;
19541
+ case "disabled":
19542
+ return `${styleText2("gray", S_RADIO_INACTIVE)} ${styleText2(["strikethrough", "gray"], a)}`;
19543
+ }
19544
+ };
19545
+ switch (this.state) {
19546
+ case "submit": {
19547
+ const r = N(this.selectedValues, o), s = r.length > 0 ? ` ${styleText2("dim", r.map(P).join(", "))}` : "", a = n ? styleText2("gray", S_BAR) : "";
19548
+ return `${i.join(`
19549
+ `)}
19550
+ ${a}${s}`;
19551
+ }
19552
+ case "cancel": {
19553
+ const r = l ? ` ${styleText2(["strikethrough", "dim"], l)}` : "", s = n ? styleText2("gray", S_BAR) : "";
19554
+ return `${i.join(`
19555
+ `)}
19556
+ ${s}${r}`;
19557
+ }
19558
+ default: {
19559
+ const r = this.state === "error" ? "yellow" : "cyan", s = n ? `${styleText2(r, S_BAR)} ` : "", a = n ? styleText2(r, S_BAR_END) : "";
19560
+ let u = "";
19561
+ if (this.isNavigating || p) {
19562
+ const d = p ? m : l;
19563
+ u = d !== "" ? ` ${styleText2("dim", d)}` : "";
19564
+ } else
19565
+ u = ` ${this.userInputWithCursor}`;
19566
+ const V = this.filteredOptions.length !== o.length ? styleText2("dim", ` (${this.filteredOptions.length} match${this.filteredOptions.length === 1 ? "" : "es"})`) : "", y = this.filteredOptions.length === 0 && l ? [`${s}${styleText2("yellow", "No matches found")}`] : [], b = this.state === "error" ? [`${s}${styleText2("yellow", this.error)}`] : [];
19567
+ n && i.push(`${s.trimEnd()}`), i.push(`${s}${styleText2("dim", "Search:")}${u}${V}`, ...y, ...b);
19568
+ const v = [
19569
+ `${styleText2("dim", "↑/↓")} to select`,
19570
+ `${styleText2("dim", "Enter:")} confirm`,
19571
+ `${styleText2("dim", "Type:")} to search`
19572
+ ], g = [`${s}${v.join(" • ")}`, a], O = this.filteredOptions.length === 0 ? [] : limitOptions({
19573
+ cursor: this.cursor,
19574
+ options: this.filteredOptions,
19575
+ columnPadding: n ? 3 : 0,
19576
+ rowPadding: i.length + g.length,
19577
+ style: (d, f) => $(d, d.disabled ? "disabled" : f ? "active" : "inactive"),
19578
+ maxItems: t.maxItems,
19579
+ output: t.output
19580
+ });
19581
+ return [
19582
+ ...i,
19583
+ ...O.map((d) => `${s}${d}`),
19584
+ ...g
19585
+ ].join(`
19586
+ `);
19587
+ }
19588
+ }
19589
+ }
19590
+ }).prompt();
18773
19591
  var confirm = (i) => {
18774
19592
  const a = i.active ?? "Yes", s = i.inactive ?? "No";
18775
19593
  return new r({
@@ -19200,6 +20018,7 @@ function createStyledClack() {
19200
20018
  cancel,
19201
20019
  confirm,
19202
20020
  password,
20021
+ autocomplete,
19203
20022
  select,
19204
20023
  isCancel,
19205
20024
  spinner,
@@ -19459,13 +20278,52 @@ function createPassword({
19459
20278
  };
19460
20279
  }
19461
20280
 
19462
- // src/shell/ui/renderers/select.ts
19463
- function createSelect({ mode, clack }) {
19464
- return async (message, options) => {
19465
- assertHumanMode(mode, "select");
19466
- const value = await clack.select({ message, options: [...options] });
19467
- if (clack.isCancel(value))
19468
- return { ok: false };
20281
+ // src/core/textSearch.ts
20282
+ function normalizeForSearch(value) {
20283
+ return value.toLowerCase().replace(/[-_\s]+/g, " ").trim();
20284
+ }
20285
+ function matchesSearchTerm(search, fields) {
20286
+ const needle = normalizeForSearch(search);
20287
+ if (!needle)
20288
+ return true;
20289
+ return fields.some((field) => field !== undefined && normalizeForSearch(field).includes(needle));
20290
+ }
20291
+ function createSearchIndex(items, fieldsOf) {
20292
+ const indexed = new Map;
20293
+ for (const item of items) {
20294
+ indexed.set(item, fieldsOf(item).flatMap((field) => field === undefined ? [] : [normalizeForSearch(field)]));
20295
+ }
20296
+ let lastSearch;
20297
+ let needle = "";
20298
+ return (search, item) => {
20299
+ if (search !== lastSearch) {
20300
+ lastSearch = search;
20301
+ needle = normalizeForSearch(search);
20302
+ }
20303
+ if (!needle)
20304
+ return true;
20305
+ const fields = indexed.get(item);
20306
+ return fields ? fields.some((field) => field.includes(needle)) : matchesSearchTerm(search, fieldsOf(item));
20307
+ };
20308
+ }
20309
+
20310
+ // src/shell/ui/renderers/select.ts
20311
+ var searchThreshold = 8;
20312
+ var searchPlaceholder = "Type to filter";
20313
+ function searchableFields(option) {
20314
+ return [option.label, option.hint, option.value];
20315
+ }
20316
+ function createSelect({ mode, clack }) {
20317
+ return async (message, options) => {
20318
+ assertHumanMode(mode, "select");
20319
+ const value = options.length > searchThreshold ? await clack.autocomplete({
20320
+ message,
20321
+ options: [...options],
20322
+ placeholder: searchPlaceholder,
20323
+ filter: createSearchIndex([...options], searchableFields)
20324
+ }) : await clack.select({ message, options: [...options] });
20325
+ if (clack.isCancel(value))
20326
+ return { ok: false };
19469
20327
  return { ok: true, value };
19470
20328
  };
19471
20329
  }
@@ -19511,12 +20369,12 @@ function createVerboseContext(outputMode, isVerbose) {
19511
20369
 
19512
20370
  // src/shell/npmRegistry.ts
19513
20371
  var registryUrl = "https://registry.npmjs.org";
19514
- var timeoutMs2 = 3000;
20372
+ var timeoutMs4 = 3000;
19515
20373
  async function fetchLatestVersion(packageName, deps = {}) {
19516
20374
  const fetchFn = deps.fetchFn ?? globalThis.fetch;
19517
20375
  try {
19518
20376
  const response = await fetchFn(`${registryUrl}/${packageName}/latest`, {
19519
- signal: AbortSignal.timeout(timeoutMs2)
20377
+ signal: AbortSignal.timeout(timeoutMs4)
19520
20378
  });
19521
20379
  if (!response.ok)
19522
20380
  return;
@@ -19531,7 +20389,7 @@ async function fetchLatestVersion(packageName, deps = {}) {
19531
20389
  }
19532
20390
 
19533
20391
  // src/domains/updateCheck/updateCheck.ts
19534
- import { join as join10 } from "node:path";
20392
+ import { join as join13 } from "node:path";
19535
20393
 
19536
20394
  // src/core/version.ts
19537
20395
  function parseRelease(version) {
@@ -19577,7 +20435,7 @@ function startUpdateCheck(deps) {
19577
20435
  return;
19578
20436
  if (!isNewerVersion(deps.currentVersion, latest))
19579
20437
  return;
19580
- const noticePath = join10(deps.configDir, noticeFile);
20438
+ const noticePath = join13(deps.configDir, noticeFile);
19581
20439
  try {
19582
20440
  const notified = (await deps.fs.readFile(noticePath)).trim();
19583
20441
  if (notified === latest)
@@ -19599,7 +20457,7 @@ function startUpdateCheck(deps) {
19599
20457
  // package.json
19600
20458
  var package_default = {
19601
20459
  name: "@qawolf/cli",
19602
- version: "1.24.0",
20460
+ version: "1.25.0",
19603
20461
  description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
19604
20462
  keywords: [
19605
20463
  "automation",
@@ -19789,7 +20647,12 @@ function withAuthContext(signals, fn, deps = {}) {
19789
20647
  await updateNotifier.notifyIfOutdated();
19790
20648
  return;
19791
20649
  }
19792
- const platformClient = (deps.createPlatform ?? createPlatformClient)(resolved.key, { baseUrl: apiBaseUrl, fetch: globalThis.fetch, logger: ctx.log("trpc") });
20650
+ const platformClient = (deps.createPlatform ?? createPlatformClient)(resolved.key, {
20651
+ baseUrl: apiBaseUrl,
20652
+ fetch: globalThis.fetch,
20653
+ logger: ctx.log("trpc"),
20654
+ workspaceId: resolved.workspaceId
20655
+ });
19793
20656
  try {
19794
20657
  const result = await fn({
19795
20658
  ...ctx,
@@ -19810,28 +20673,15 @@ function withAuthContext(signals, fn, deps = {}) {
19810
20673
  };
19811
20674
  }
19812
20675
 
19813
- // src/commands/auth/login.ts
19814
- async function handleLogin(ctx) {
19815
- if (ctx.ui.mode !== "human") {
19816
- ctx.ui.error(authMessages.login.nonInteractive);
19817
- return { error: "non-interactive" };
19818
- }
19819
- const existing = await resolveApiKey(ctx.configDir, ctx.fs);
19820
- if (existing) {
19821
- const reauth = await ctx.ui.confirm(authMessages.login.reAuthPrompt);
19822
- if (!reauth.ok || !reauth.value) {
19823
- ctx.ui.info(authMessages.alreadyConfigured);
19824
- return;
19825
- }
19826
- }
19827
- ctx.ui.gap();
19828
- ctx.ui.intro(authMessages.title);
20676
+ // src/commands/auth/loginApiKey.ts
20677
+ async function loginWithApiKey(ctx) {
19829
20678
  const result = await ctx.ui.password(authMessages.promptApiKey, "Set QAWOLF_API_KEY to authenticate in non-interactive environments.");
19830
20679
  if (!result.ok) {
19831
20680
  ctx.ui.cancel(authMessages.cancelled);
19832
20681
  return;
19833
20682
  }
19834
- if (!result.value.trim()) {
20683
+ const apiKey = result.value.trim();
20684
+ if (!apiKey) {
19835
20685
  ctx.ui.cancel(authMessages.cancelled);
19836
20686
  return;
19837
20687
  }
@@ -19840,7 +20690,7 @@ async function handleLogin(ctx) {
19840
20690
  message: authMessages.verifying,
19841
20691
  task: async () => {
19842
20692
  const v = await validateApiKey({
19843
- platformClient: createPlatformClient(result.value, {
20693
+ platformClient: createPlatformClient(apiKey, {
19844
20694
  baseUrl: ctx.apiBaseUrl,
19845
20695
  fetch: globalThis.fetch
19846
20696
  })
@@ -19851,7 +20701,7 @@ async function handleLogin(ctx) {
19851
20701
  },
19852
20702
  {
19853
20703
  message: authMessages.storing,
19854
- task: async () => saveApiKey(ctx.configDir, result.value, ctx.fs)
20704
+ task: async () => saveApiKey(ctx.configDir, apiKey, ctx.fs)
19855
20705
  }
19856
20706
  ], ([, saveResult]) => {
19857
20707
  return saveResult.stored === "file" ? authMessages.storedFile : authMessages.storedKeychain;
@@ -19859,14 +20709,632 @@ async function handleLogin(ctx) {
19859
20709
  ctx.ui.outro(authMessages.outroSuccess);
19860
20710
  }
19861
20711
 
20712
+ // src/core/deviceAuth/pollState.ts
20713
+ var slowDownIncrementMs = 5000;
20714
+ var unreachableBackoffFactor = 2;
20715
+ function capToLifetime(delayMs, state, nowMs) {
20716
+ return Math.min(delayMs, Math.max(1, state.deadlineMs - nowMs));
20717
+ }
20718
+ function nextPollStep(state, response, nowMs) {
20719
+ if (response.kind === "tokens") {
20720
+ return { action: "done", tokens: response.tokens };
20721
+ }
20722
+ if (nowMs > state.deadlineMs) {
20723
+ return { action: "fail", reason: "timeout", detail: undefined };
20724
+ }
20725
+ switch (response.kind) {
20726
+ case "pending":
20727
+ return {
20728
+ action: "poll",
20729
+ delayMs: capToLifetime(state.intervalMs, state, nowMs),
20730
+ state
20731
+ };
20732
+ case "slow-down": {
20733
+ const slowed = {
20734
+ intervalMs: state.intervalMs + slowDownIncrementMs,
20735
+ deadlineMs: state.deadlineMs
20736
+ };
20737
+ return {
20738
+ action: "poll",
20739
+ delayMs: capToLifetime(slowed.intervalMs, slowed, nowMs),
20740
+ state: slowed
20741
+ };
20742
+ }
20743
+ case "denied":
20744
+ return { action: "fail", reason: "access-denied", detail: undefined };
20745
+ case "expired":
20746
+ return { action: "fail", reason: "expired", detail: undefined };
20747
+ case "unreachable": {
20748
+ const backedOff = {
20749
+ intervalMs: state.intervalMs * unreachableBackoffFactor,
20750
+ deadlineMs: state.deadlineMs
20751
+ };
20752
+ return {
20753
+ action: "poll",
20754
+ delayMs: capToLifetime(backedOff.intervalMs, backedOff, nowMs),
20755
+ state: backedOff
20756
+ };
20757
+ }
20758
+ case "error":
20759
+ return { action: "fail", reason: "network", detail: response.detail };
20760
+ }
20761
+ }
20762
+
20763
+ // src/domains/auth/deviceLogin.ts
20764
+ var cancelled = {
20765
+ ok: false,
20766
+ reason: "cancelled",
20767
+ detail: undefined
20768
+ };
20769
+ async function deviceLogin(deps) {
20770
+ if (deps.isCancelled())
20771
+ return cancelled;
20772
+ const authorization = await deps.requestAuthorization();
20773
+ if (!authorization.ok) {
20774
+ return { ok: false, reason: "unavailable", detail: authorization.error };
20775
+ }
20776
+ await deps.onPrompt(authorization.value);
20777
+ let state = {
20778
+ intervalMs: authorization.value.intervalSec * 1000,
20779
+ deadlineMs: deps.now() + authorization.value.expiresInSec * 1000
20780
+ };
20781
+ for (;; ) {
20782
+ if (deps.isCancelled())
20783
+ return cancelled;
20784
+ const response = await deps.pollToken(authorization.value.deviceCode);
20785
+ const step = nextPollStep(state, response, deps.now());
20786
+ if (step.action === "done")
20787
+ return { ok: true, tokens: step.tokens };
20788
+ if (step.action === "fail") {
20789
+ return { ok: false, reason: step.reason, detail: step.detail };
20790
+ }
20791
+ state = step.state;
20792
+ await deps.sleep(step.delayMs);
20793
+ }
20794
+ }
20795
+
20796
+ // src/shell/openBrowser.ts
20797
+ var launchTimeoutMs = 2000;
20798
+ function launcher(url, platform) {
20799
+ if (platform === "darwin")
20800
+ return { cmd: "open", args: [url] };
20801
+ if (platform === "win32") {
20802
+ return { cmd: "rundll32", args: ["url.dll,FileProtocolHandler", url] };
20803
+ }
20804
+ return { cmd: "xdg-open", args: [url] };
20805
+ }
20806
+ async function openBrowser(url, deps) {
20807
+ let parsed;
20808
+ try {
20809
+ parsed = new URL(url);
20810
+ } catch {
20811
+ return false;
20812
+ }
20813
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
20814
+ return false;
20815
+ }
20816
+ const { cmd, args } = launcher(url, deps.platform);
20817
+ const launched = deps.spawn(cmd, args, { platform: deps.platform }).then((result) => result.exitCode === 0).catch(() => false);
20818
+ return Promise.race([launched, deps.sleep(launchTimeoutMs).then(() => true)]);
20819
+ }
20820
+
20821
+ // src/shell/spawn.ts
20822
+ import { spawn } from "node:child_process";
20823
+
20824
+ // src/core/cmdEscape.ts
20825
+ var metaChars = /([()\][%!^"`<>&|;, *?])/g;
20826
+ function escapeCommand(command) {
20827
+ return command.replace(metaChars, "^$1");
20828
+ }
20829
+ function escapeArgument(arg) {
20830
+ const escaped = arg.replace(/(?=(\\+?)?)\1"/g, "$1$1\\\"").replace(/(?=(\\+?)?)\1$/, "$1$1");
20831
+ return `"${escaped}"`.replace(metaChars, "^$1").replace(metaChars, "^$1");
20832
+ }
20833
+
20834
+ // src/shell/spawn.ts
20835
+ function buildSpawnCommand(cmd, args, platform, env) {
20836
+ const options = {};
20837
+ if (env)
20838
+ options.env = env;
20839
+ if (platform !== "win32" || !/\.(cmd|bat)$/i.test(cmd)) {
20840
+ return { cmd, args, options };
20841
+ }
20842
+ const line = [escapeCommand(cmd), ...args.map(escapeArgument)].join(" ");
20843
+ return {
20844
+ cmd: process.env["ComSpec"] ?? "cmd.exe",
20845
+ args: ["/d", "/s", "/c", `"${line}"`],
20846
+ options: { ...options, windowsVerbatimArguments: true }
20847
+ };
20848
+ }
20849
+ var defaultSpawn = (cmd, args, opts) => new Promise((resolve) => {
20850
+ const env = opts.env ? { ...process.env, ...opts.env } : undefined;
20851
+ const built = buildSpawnCommand(cmd, args, opts.platform, env);
20852
+ const child = spawn(built.cmd, built.args, built.options);
20853
+ let stdout = "";
20854
+ let stderr = "";
20855
+ child.stdout?.on("data", (chunk) => {
20856
+ stdout += String(chunk);
20857
+ });
20858
+ child.stderr?.on("data", (chunk) => {
20859
+ stderr += String(chunk);
20860
+ });
20861
+ if (opts?.stdin !== undefined) {
20862
+ child.stdin?.write(opts.stdin);
20863
+ child.stdin?.end();
20864
+ }
20865
+ child.on("error", (err) => resolve({ exitCode: -1, stdout, stderr: stderr || err.message }));
20866
+ child.on("close", (code) => resolve({ exitCode: code ?? -1, stdout, stderr }));
20867
+ });
20868
+
20869
+ // src/shell/platform/getAuthConfig.ts
20870
+ var timeoutMs5 = 1e4;
20871
+ var authConfigBody = object({
20872
+ workOsClientId: string2().trim().min(1)
20873
+ });
20874
+ async function getAuthConfig(deps) {
20875
+ let response;
20876
+ try {
20877
+ response = await deps.fetch(`${deps.baseUrl}/api/v0/auth/config`, {
20878
+ signal: AbortSignal.timeout(timeoutMs5)
20879
+ });
20880
+ } catch (err) {
20881
+ return { kind: "unreachable", detail: errorMessage(err) };
20882
+ }
20883
+ if (response.status === 404)
20884
+ return { kind: "unconfigured" };
20885
+ if (!response.ok) {
20886
+ return { kind: "unreachable", detail: `HTTP ${response.status}` };
20887
+ }
20888
+ let json;
20889
+ try {
20890
+ json = await response.json();
20891
+ } catch (err) {
20892
+ return { kind: "unreachable", detail: errorMessage(err) };
20893
+ }
20894
+ const parsed = authConfigBody.safeParse(json);
20895
+ return parsed.success ? { kind: "configured", clientId: parsed.data.workOsClientId } : { kind: "unconfigured" };
20896
+ }
20897
+
20898
+ // src/shell/workos/pollDeviceToken.ts
20899
+ var deviceCodeGrantType = "urn:ietf:params:oauth:grant-type:device_code";
20900
+ async function pollDeviceToken(deviceCode, deps) {
20901
+ const outcome = await sendWorkosRequest(`${deps.baseUrl}/user_management/authenticate`, {
20902
+ headers: { "content-type": "application/x-www-form-urlencoded" },
20903
+ body: new URLSearchParams({
20904
+ grant_type: deviceCodeGrantType,
20905
+ device_code: deviceCode,
20906
+ client_id: deps.clientId
20907
+ }).toString()
20908
+ }, deps.fetch);
20909
+ if (outcome.kind === "failure") {
20910
+ return outcome.retryable ? { kind: "unreachable", detail: outcome.detail } : { kind: "error", detail: outcome.detail };
20911
+ }
20912
+ if (outcome.kind === "oauth-error") {
20913
+ switch (outcome.code) {
20914
+ case "authorization_pending":
20915
+ return { kind: "pending" };
20916
+ case "slow_down":
20917
+ return { kind: "slow-down" };
20918
+ case "access_denied":
20919
+ return { kind: "denied" };
20920
+ case "expired_token":
20921
+ case "invalid_grant":
20922
+ return { kind: "expired" };
20923
+ default:
20924
+ return {
20925
+ kind: "error",
20926
+ detail: outcome.description ?? outcome.code
20927
+ };
20928
+ }
20929
+ }
20930
+ const parsed = deviceTokenBody.safeParse(outcome.json);
20931
+ if (!parsed.success) {
20932
+ return { kind: "error", detail: unexpectedResponse };
20933
+ }
20934
+ return {
20935
+ kind: "tokens",
20936
+ tokens: {
20937
+ accessToken: parsed.data.access_token,
20938
+ refreshToken: parsed.data.refresh_token,
20939
+ expiresAt: readAccessTokenExpiry(parsed.data.access_token),
20940
+ email: parsed.data.user.email,
20941
+ organizationId: parsed.data.organization_id
20942
+ }
20943
+ };
20944
+ }
20945
+
20946
+ // src/shell/workos/requestDeviceAuthorization.ts
20947
+ async function requestDeviceAuthorization(deps) {
20948
+ const outcome = await sendWorkosRequest(`${deps.baseUrl}/user_management/authorize/device`, {
20949
+ headers: { "content-type": "application/x-www-form-urlencoded" },
20950
+ body: new URLSearchParams({ client_id: deps.clientId }).toString()
20951
+ }, deps.fetch);
20952
+ if (outcome.kind === "failure") {
20953
+ return { ok: false, error: outcome.detail, retryable: outcome.retryable };
20954
+ }
20955
+ if (outcome.kind === "oauth-error") {
20956
+ return {
20957
+ ok: false,
20958
+ error: outcome.description ?? outcome.code,
20959
+ retryable: false
20960
+ };
20961
+ }
20962
+ const parsed = deviceAuthorizationBody.safeParse(outcome.json);
20963
+ if (!parsed.success) {
20964
+ return { ok: false, error: unexpectedResponse, retryable: false };
20965
+ }
20966
+ return {
20967
+ ok: true,
20968
+ value: {
20969
+ deviceCode: parsed.data.device_code,
20970
+ userCode: parsed.data.user_code,
20971
+ verificationUri: parsed.data.verification_uri,
20972
+ verificationUriComplete: parsed.data.verification_uri_complete,
20973
+ expiresInSec: parsed.data.expires_in,
20974
+ intervalSec: parsed.data.interval ?? defaultIntervalSec
20975
+ }
20976
+ };
20977
+ }
20978
+
20979
+ // src/domains/auth/nameMatching.ts
20980
+ function matches(candidate, wanted) {
20981
+ return candidate.toLowerCase() === wanted.trim().toLowerCase();
20982
+ }
20983
+ function findOrganization(organizations, wanted) {
20984
+ return organizations.find((organization) => matches(organization.name, wanted) || matches(organization.id, wanted) || matches(organization.workOsOrganizationId, wanted));
20985
+ }
20986
+ function findWorkspace(workspaces, wanted) {
20987
+ return workspaces.find((workspace) => matches(workspace.name, wanted) || matches(workspace.id, wanted) || workspace.slug !== undefined && matches(workspace.slug, wanted));
20988
+ }
20989
+ function findWorkspaceAcross(organizations, wanted) {
20990
+ const hits = organizations.flatMap((organization) => {
20991
+ const workspace = findWorkspace(organization.workspaces, wanted);
20992
+ return workspace ? [{ organization, workspace }] : [];
20993
+ });
20994
+ const [only] = hits;
20995
+ if (!only)
20996
+ return { kind: "none" };
20997
+ if (hits.length > 1) {
20998
+ return {
20999
+ kind: "ambiguous",
21000
+ organizations: hits.map((h) => h.organization)
21001
+ };
21002
+ }
21003
+ return {
21004
+ kind: "found",
21005
+ organization: only.organization,
21006
+ workspace: only.workspace
21007
+ };
21008
+ }
21009
+ function nameList(items) {
21010
+ return items.map((item) => item.slug ?? item.name).join(", ");
21011
+ }
21012
+
21013
+ // src/domains/auth/selectWorkspace.ts
21014
+ async function selectWorkspace(deps) {
21015
+ const { organizations } = deps;
21016
+ if (organizations.length === 0)
21017
+ return { outcome: "none" };
21018
+ let organization;
21019
+ if (deps.preferredOrganization) {
21020
+ organization = findOrganization(organizations, deps.preferredOrganization);
21021
+ if (!organization) {
21022
+ return {
21023
+ outcome: "failed",
21024
+ error: `No organization matches '${deps.preferredOrganization}'. Available: ${nameList(organizations)}.`
21025
+ };
21026
+ }
21027
+ } else if (organizations.length === 1) {
21028
+ organization = organizations[0];
21029
+ } else if (deps.preferredWorkspace) {
21030
+ const match = findWorkspaceAcross(organizations, deps.preferredWorkspace);
21031
+ if (match.kind === "none") {
21032
+ return {
21033
+ outcome: "failed",
21034
+ error: `No workspace matches '${deps.preferredWorkspace}'. Available: ${nameList(organizations.flatMap((o) => o.workspaces))}.`
21035
+ };
21036
+ }
21037
+ if (match.kind === "ambiguous") {
21038
+ return {
21039
+ outcome: "failed",
21040
+ error: `More than one organization has a workspace matching '${deps.preferredWorkspace}': ${nameList(match.organizations)}. Set QAWOLF_ORGANIZATION to choose between them.`
21041
+ };
21042
+ }
21043
+ organization = match.organization;
21044
+ } else {
21045
+ organization = await deps.chooseOrganization(organizations);
21046
+ if (!organization)
21047
+ return { outcome: "cancelled" };
21048
+ }
21049
+ if (!organization)
21050
+ throw Error("organization was not settled");
21051
+ const { workspaces } = organization;
21052
+ if (workspaces.length === 0) {
21053
+ return {
21054
+ outcome: "failed",
21055
+ error: `Organization '${organization.name}' has no workspaces.`
21056
+ };
21057
+ }
21058
+ let workspace;
21059
+ if (deps.preferredWorkspace) {
21060
+ workspace = findWorkspace(workspaces, deps.preferredWorkspace);
21061
+ if (!workspace) {
21062
+ return {
21063
+ outcome: "failed",
21064
+ error: `No workspace matches '${deps.preferredWorkspace}' in ${organization.name}. Available: ${nameList(workspaces)}.`
21065
+ };
21066
+ }
21067
+ } else if (workspaces.length === 1) {
21068
+ workspace = workspaces[0];
21069
+ } else {
21070
+ workspace = await deps.chooseWorkspace(workspaces);
21071
+ if (!workspace)
21072
+ return { outcome: "cancelled" };
21073
+ }
21074
+ if (!workspace)
21075
+ throw Error("workspace was not settled");
21076
+ try {
21077
+ await deps.saveWorkspace(workspace.id);
21078
+ } catch (err) {
21079
+ return {
21080
+ outcome: "failed",
21081
+ error: authMessages.workspace.saveFailed(errorMessage(err))
21082
+ };
21083
+ }
21084
+ return { outcome: "selected", organization, workspace };
21085
+ }
21086
+
21087
+ // src/domains/auth/store/saveWorkspaceId.ts
21088
+ import { Entry as Entry6 } from "@napi-rs/keyring";
21089
+ function makeSaveWorkspaceIdDeps(configDir, fs) {
21090
+ return {
21091
+ loadTokens: () => loadTokens(configDir, { EntryClass: Entry6, fs }),
21092
+ saveTokens: (session) => saveTokens(configDir, session, fs)
21093
+ };
21094
+ }
21095
+ async function saveWorkspaceId(session, workspaceId, deps) {
21096
+ const current = await deps.loadTokens();
21097
+ if (!current.found || !isSameSession(current.tokens, session)) {
21098
+ throw Error(authMessages.workspace.sessionChanged);
21099
+ }
21100
+ return deps.saveTokens({ ...current.tokens, workspaceId });
21101
+ }
21102
+ function isSameSession(stored, captured) {
21103
+ return stored.email === captured.email && stored.organizationId === captured.organizationId;
21104
+ }
21105
+
21106
+ // src/commands/auth/chooseWorkspace.ts
21107
+ async function chooseWorkspace(ctx, args) {
21108
+ const client = createPlatformClient(args.session.accessToken, {
21109
+ baseUrl: ctx.apiBaseUrl,
21110
+ fetch: globalThis.fetch,
21111
+ logger: ctx.log("trpc")
21112
+ });
21113
+ const [identity, discovered] = await Promise.all([
21114
+ client.getIdentity(),
21115
+ client.getAccessibleOrganizations()
21116
+ ]);
21117
+ let organizations;
21118
+ if (discovered.ok) {
21119
+ organizations = discovered.value;
21120
+ } else if (identity.ok) {
21121
+ organizations = "user" in identity.value ? identity.value.organizations : [];
21122
+ } else {
21123
+ return { outcome: "failed", error: identity.error };
21124
+ }
21125
+ return selectWorkspace({
21126
+ organizations,
21127
+ preferredOrganization: args.env["QAWOLF_ORGANIZATION"]?.trim(),
21128
+ preferredWorkspace: args.env["QAWOLF_WORKSPACE"]?.trim(),
21129
+ chooseOrganization: async (organizations) => {
21130
+ if (ctx.ui.mode !== "human")
21131
+ return;
21132
+ const picked = await ctx.ui.select(authMessages.workspace.chooseOrganization, organizations.map((organization) => ({
21133
+ value: organization.workOsOrganizationId,
21134
+ label: organization.name,
21135
+ hint: authMessages.workspace.workspaceCount(organization.workspaces.length)
21136
+ })));
21137
+ if (!picked.ok)
21138
+ return;
21139
+ return organizations.find((organization) => organization.workOsOrganizationId === picked.value);
21140
+ },
21141
+ chooseWorkspace: async (workspaces) => {
21142
+ if (ctx.ui.mode !== "human")
21143
+ return;
21144
+ const picked = await ctx.ui.select(authMessages.workspace.choose, workspaces.map((workspace) => ({
21145
+ value: workspace.id,
21146
+ label: workspace.name,
21147
+ ...workspace.slug ? { hint: workspace.slug } : {}
21148
+ })));
21149
+ if (!picked.ok)
21150
+ return;
21151
+ return workspaces.find((workspace) => workspace.id === picked.value);
21152
+ },
21153
+ saveWorkspace: (workspaceId) => saveWorkspaceId(args.session, workspaceId, makeSaveWorkspaceIdDeps(ctx.configDir, ctx.fs))
21154
+ });
21155
+ }
21156
+ function reportWorkspace(ctx, result) {
21157
+ switch (result.outcome) {
21158
+ case "selected":
21159
+ ctx.ui.info(authMessages.workspace.working(result.organization.name, result.workspace.name));
21160
+ return;
21161
+ case "none":
21162
+ ctx.ui.info(authMessages.workspace.none);
21163
+ return;
21164
+ case "cancelled":
21165
+ if (ctx.ui.mode !== "human") {
21166
+ return { error: authMessages.workspace.nonInteractive };
21167
+ }
21168
+ ctx.ui.info(authMessages.workspace.cancelled);
21169
+ return;
21170
+ case "failed":
21171
+ return { error: result.error };
21172
+ }
21173
+ }
21174
+
21175
+ // src/commands/auth/loginDevice.ts
21176
+ async function loginWithDevice(ctx, deps = {}) {
21177
+ const authConfig = await getAuthConfig({
21178
+ baseUrl: ctx.apiBaseUrl,
21179
+ fetch: globalThis.fetch
21180
+ });
21181
+ if (authConfig.kind === "unreachable") {
21182
+ ctx.log("auth").debug(`auth config unreachable: ${authConfig.detail}`);
21183
+ return {
21184
+ error: authMessages.device.configUnreachable,
21185
+ errorBody: authConfig.detail
21186
+ };
21187
+ }
21188
+ const config = resolveWorkosConfig(authConfig.kind === "configured" ? authConfig.clientId : undefined);
21189
+ if (!config.configured) {
21190
+ return { error: authMessages.device.unavailable };
21191
+ }
21192
+ const workos = {
21193
+ fetch: globalThis.fetch,
21194
+ baseUrl: config.baseUrl,
21195
+ clientId: config.clientId
21196
+ };
21197
+ let cancelled = false;
21198
+ const unregister = ctx.signals.register(() => {
21199
+ cancelled = true;
21200
+ });
21201
+ const showCode = async (authorization) => {
21202
+ const url = authorization.verificationUriComplete ?? authorization.verificationUri;
21203
+ ctx.ui.note([
21204
+ authMessages.device.confirmCode(authorization.userCode),
21205
+ authMessages.device.visitUrl(url),
21206
+ url === authorization.verificationUri ? undefined : authMessages.device.visitUrlPlain(authorization.verificationUri)
21207
+ ].filter((line) => Boolean(line)).join(`
21208
+ `), authMessages.title);
21209
+ const opened = await openBrowser(url, {
21210
+ sleep,
21211
+ spawn: defaultSpawn,
21212
+ platform: deps.platform ?? process.platform
21213
+ });
21214
+ if (!opened)
21215
+ ctx.ui.info(authMessages.device.openFailed(url));
21216
+ ctx.ui.step(authMessages.device.waiting);
21217
+ };
21218
+ try {
21219
+ const result = await deviceLogin({
21220
+ requestAuthorization: () => requestDeviceAuthorization(workos),
21221
+ pollToken: (deviceCode) => pollDeviceToken(deviceCode, workos),
21222
+ onPrompt: showCode,
21223
+ sleep,
21224
+ now: () => Date.now(),
21225
+ isCancelled: () => cancelled
21226
+ });
21227
+ if (!result.ok) {
21228
+ return {
21229
+ error: authMessages.device.failed[result.reason],
21230
+ ...result.detail ? { errorBody: result.detail } : {}
21231
+ };
21232
+ }
21233
+ const session = {
21234
+ ...result.tokens,
21235
+ workspaceId: undefined,
21236
+ clientId: config.clientId
21237
+ };
21238
+ await saveTokens(ctx.configDir, session, ctx.fs);
21239
+ const workspace = await chooseWorkspace(ctx, {
21240
+ session,
21241
+ env: deps.env ?? process.env
21242
+ });
21243
+ const failure = reportWorkspace(ctx, workspace);
21244
+ if (failure)
21245
+ return failure;
21246
+ ctx.ui.outro(authMessages.device.signedIn(result.tokens.email));
21247
+ return;
21248
+ } finally {
21249
+ unregister();
21250
+ }
21251
+ }
21252
+
21253
+ // src/commands/auth/login.ts
21254
+ var browserMethod = "browser";
21255
+ async function handleLogin(ctx, deps = {}) {
21256
+ if (ctx.ui.mode !== "human") {
21257
+ ctx.ui.error(authMessages.login.nonInteractive);
21258
+ return { error: "non-interactive" };
21259
+ }
21260
+ const resolveApiKey2 = deps.resolveApiKey ?? resolveApiKey;
21261
+ const existing = await resolveApiKey2(ctx.configDir, ctx.fs);
21262
+ if (existing) {
21263
+ const reauth = await ctx.ui.confirm(authMessages.login.reAuthPrompt);
21264
+ if (!reauth.ok || !reauth.value) {
21265
+ ctx.ui.info(authMessages.alreadyConfigured);
21266
+ return;
21267
+ }
21268
+ }
21269
+ ctx.ui.gap();
21270
+ ctx.ui.intro(authMessages.title);
21271
+ const method = await ctx.ui.select(authMessages.login.chooseMethod, [
21272
+ {
21273
+ value: browserMethod,
21274
+ label: authMessages.login.methodBrowser,
21275
+ hint: authMessages.login.methodBrowserHint
21276
+ },
21277
+ {
21278
+ value: "api-key",
21279
+ label: authMessages.login.methodApiKey,
21280
+ hint: authMessages.login.methodApiKeyHint
21281
+ }
21282
+ ]);
21283
+ if (!method.ok) {
21284
+ ctx.ui.cancel(authMessages.cancelled);
21285
+ return;
21286
+ }
21287
+ if (method.value !== browserMethod) {
21288
+ return (deps.loginWithApiKey ?? loginWithApiKey)(ctx);
21289
+ }
21290
+ if (existing && existing.source !== "browser") {
21291
+ ctx.ui.warn(existing.source === "env" ? authMessages.login.apiKeyPrecedence.env : authMessages.login.apiKeyPrecedence.stored);
21292
+ }
21293
+ return (deps.loginWithDevice ?? loginWithDevice)(ctx);
21294
+ }
21295
+
21296
+ // src/domains/auth/store/deleteTokens.ts
21297
+ import { join as join14 } from "node:path";
21298
+ import { Entry as Entry7 } from "@napi-rs/keyring";
21299
+ function deleteFromKeychain2() {
21300
+ try {
21301
+ new Entry7(service, tokensAccount).deletePassword();
21302
+ return "deleted";
21303
+ } catch {
21304
+ return "unavailable";
21305
+ }
21306
+ }
21307
+ async function deleteFromFile2(configDir, fs) {
21308
+ try {
21309
+ await fs.unlink(join14(configDir, tokensFile));
21310
+ return "deleted";
21311
+ } catch (err) {
21312
+ if (isNoEntError(err))
21313
+ return "not-found";
21314
+ throw err;
21315
+ }
21316
+ }
21317
+ async function deleteTokens(configDir, fs) {
21318
+ const [keychain, file] = await Promise.all([
21319
+ Promise.resolve(deleteFromKeychain2()),
21320
+ deleteFromFile2(configDir, fs)
21321
+ ]);
21322
+ return { keychain, file };
21323
+ }
21324
+
19862
21325
  // src/commands/auth/logout.ts
19863
- async function handleLogout(ctx) {
19864
- const resolved = await resolveApiKey(ctx.configDir, ctx.fs);
19865
- if (!resolved) {
21326
+ async function handleLogout(ctx, deps = {}) {
21327
+ const hasStoredCredentials2 = deps.hasStoredCredentials ?? hasStoredCredentials;
21328
+ const deleteApiKey2 = deps.deleteApiKey ?? deleteApiKey;
21329
+ const deleteTokens2 = deps.deleteTokens ?? deleteTokens;
21330
+ const env = deps.env ?? process.env;
21331
+ const envKey = env["QAWOLF_API_KEY"]?.trim();
21332
+ const stored = await hasStoredCredentials2(ctx.configDir, ctx.fs);
21333
+ if (!envKey && !stored) {
19866
21334
  ctx.ui.info(authMessages.logout.notAuthenticated);
19867
21335
  return;
19868
21336
  }
19869
- if (resolved.source === "env") {
21337
+ if (envKey) {
19870
21338
  ctx.ui.warn(authMessages.logout.envVarWarning);
19871
21339
  }
19872
21340
  if (ctx.ui.mode === "human") {
@@ -19881,7 +21349,12 @@ async function handleLogout(ctx) {
19881
21349
  await ctx.ui.withProgress([
19882
21350
  {
19883
21351
  message: authMessages.logout.deleting,
19884
- task: () => deleteApiKey(ctx.configDir, ctx.fs)
21352
+ task: async () => {
21353
+ await Promise.all([
21354
+ deleteApiKey2(ctx.configDir, ctx.fs),
21355
+ deleteTokens2(ctx.configDir, ctx.fs)
21356
+ ]);
21357
+ }
19885
21358
  }
19886
21359
  ], () => authMessages.logout.credentialsRemoved);
19887
21360
  if (ctx.ui.mode === "human") {
@@ -19891,6 +21364,76 @@ async function handleLogout(ctx) {
19891
21364
  }
19892
21365
  }
19893
21366
 
21367
+ // src/domains/auth/refreshStoredSession.ts
21368
+ import { Entry as Entry8 } from "@napi-rs/keyring";
21369
+ function makeDefaultDeps2(fs) {
21370
+ return {
21371
+ loadTokens: (configDir) => loadTokens(configDir, { EntryClass: Entry8, fs }),
21372
+ resolveOauth: (configDir) => resolveOauthToken(configDir, makeOauthDeps(fs))
21373
+ };
21374
+ }
21375
+ async function refreshStoredSession(configDir, fs, deps) {
21376
+ const resolvedDeps = deps ?? makeDefaultDeps2(fs);
21377
+ const before = await resolvedDeps.loadTokens(configDir);
21378
+ if (!before.found)
21379
+ return { kind: "not-signed-in" };
21380
+ const renewed = await resolvedDeps.resolveOauth(configDir);
21381
+ if (!renewed)
21382
+ return { kind: "refresh-failed" };
21383
+ const after = await resolvedDeps.loadTokens(configDir);
21384
+ if (!after.found)
21385
+ return { kind: "refresh-failed" };
21386
+ if (after.tokens.accessToken !== renewed.key) {
21387
+ return { kind: "refresh-failed" };
21388
+ }
21389
+ return { kind: "session", session: after.tokens };
21390
+ }
21391
+
21392
+ // src/commands/auth/switchWorkspace.ts
21393
+ async function handleSwitchWorkspace(ctx, deps = {}) {
21394
+ const env = deps.env ?? process.env;
21395
+ const refreshStoredSession2 = deps.refreshStoredSession ?? refreshStoredSession;
21396
+ const preferred = env["QAWOLF_WORKSPACE"]?.trim() || env["QAWOLF_ORGANIZATION"]?.trim();
21397
+ if (ctx.ui.mode !== "human" && !preferred) {
21398
+ return { error: authMessages.workspace.nonInteractive };
21399
+ }
21400
+ const stored = await refreshStoredSession2(ctx.configDir, ctx.fs);
21401
+ if (stored.kind === "not-signed-in") {
21402
+ return { error: authMessages.workspace.notSignedIn };
21403
+ }
21404
+ if (stored.kind === "refresh-failed") {
21405
+ return { error: authMessages.workspace.sessionExpired };
21406
+ }
21407
+ ctx.ui.gap();
21408
+ ctx.ui.intro(authMessages.title);
21409
+ const result = await chooseWorkspace(ctx, {
21410
+ session: stored.session,
21411
+ env
21412
+ });
21413
+ const failure = reportWorkspace(ctx, result);
21414
+ if (failure)
21415
+ return failure;
21416
+ ctx.ui.outro(authMessages.outroReady);
21417
+ return;
21418
+ }
21419
+
21420
+ // src/commands/auth/whoamiTeam.ts
21421
+ function reportTeamIdentity(ctx, value, source) {
21422
+ const { team } = value;
21423
+ const teamUrl = team.slug ? new URL("/" + encodeURIComponent(team.slug), ctx.apiBaseUrl).toString() : undefined;
21424
+ if (ctx.ui.mode === "human") {
21425
+ ctx.ui.note(authMessages.whoami.teamNote({ team, teamUrl, source }), authMessages.whoamiAuthenticated);
21426
+ ctx.ui.outro(authMessages.outroReady);
21427
+ } else {
21428
+ ctx.ui.output({
21429
+ authenticated: true,
21430
+ source,
21431
+ team,
21432
+ teamUrl
21433
+ }, authMessages.whoami.authenticatedAs(team.name, source));
21434
+ }
21435
+ }
21436
+
19894
21437
  // src/commands/auth/whoami.ts
19895
21438
  async function handleWhoami(ctx, deps = {}) {
19896
21439
  ctx.ui.gap();
@@ -19932,11 +21475,19 @@ async function handleWhoami(ctx, deps = {}) {
19932
21475
  const { value } = identity;
19933
21476
  if ("user" in value) {
19934
21477
  const { organization, user } = value;
21478
+ const found = value.organizations.flatMap((candidate) => candidate.workspaces.map((workspace) => ({
21479
+ organization: candidate.name,
21480
+ workspace: workspace.name,
21481
+ id: workspace.id
21482
+ }))).find((entry) => entry.id === resolved.workspaceId);
21483
+ const activeWorkspace = resolved.workspaceId ? authMessages.whoami.activeWorkspace(resolved.workspaceId, found) : undefined;
19935
21484
  if (ctx.ui.mode === "human") {
19936
21485
  ctx.ui.note(authMessages.whoami.userNote({
19937
21486
  organization,
19938
21487
  source: resolved.source,
19939
- user
21488
+ user,
21489
+ activeWorkspace,
21490
+ organizations: value.organizations
19940
21491
  }), authMessages.whoamiAuthenticated);
19941
21492
  ctx.ui.outro(authMessages.outroReady);
19942
21493
  } else {
@@ -19944,7 +21495,9 @@ async function handleWhoami(ctx, deps = {}) {
19944
21495
  authenticated: true,
19945
21496
  organization,
19946
21497
  source: resolved.source,
19947
- user
21498
+ user,
21499
+ workspaceId: resolved.workspaceId,
21500
+ organizations: value.organizations
19948
21501
  }, authMessages.whoami.authenticatedAs(user.email, resolved.source));
19949
21502
  }
19950
21503
  return;
@@ -19966,26 +21519,15 @@ async function handleWhoami(ctx, deps = {}) {
19966
21519
  }
19967
21520
  return;
19968
21521
  }
19969
- const { team } = value;
19970
- const teamUrl = team.slug ? new URL("/" + encodeURIComponent(team.slug), ctx.apiBaseUrl).toString() : undefined;
19971
- if (ctx.ui.mode === "human") {
19972
- ctx.ui.note(authMessages.whoami.teamNote({ team, teamUrl, source: resolved.source }), authMessages.whoamiAuthenticated);
19973
- ctx.ui.outro(authMessages.outroReady);
19974
- } else {
19975
- ctx.ui.output({
19976
- authenticated: true,
19977
- source: resolved.source,
19978
- team,
19979
- teamUrl
19980
- }, authMessages.whoami.authenticatedAs(team.name, resolved.source));
19981
- }
21522
+ reportTeamIdentity(ctx, value, resolved.source);
19982
21523
  }
19983
21524
 
19984
21525
  // src/commands/auth/index.ts
19985
21526
  function registerAuthCommand(program, signals) {
19986
21527
  const auth = program.command("auth").description("Manage authentication with QA Wolf");
19987
- declareCommandKind(auth.command("login"), "local").description("Authenticate with your QA Wolf API key").action(withContext(signals, handleLogin));
21528
+ declareCommandKind(auth.command("login"), "local").description("Authenticate with QA Wolf in a browser or with an API key").action(withContext(signals, handleLogin));
19988
21529
  declareCommandKind(auth.command("logout"), "local").description("Remove stored credentials").action(withContext(signals, handleLogout));
21530
+ declareCommandKind(auth.command("switch"), "local").description("Choose which workspace to work in").action(withContext(signals, handleSwitchWorkspace));
19989
21531
  declareCommandKind(auth.command("whoami"), "read").description("Show authentication status").action(withContext(signals, handleWhoami));
19990
21532
  }
19991
21533
 
@@ -20035,11 +21577,11 @@ async function* batchMap(items, fn, size) {
20035
21577
  }
20036
21578
 
20037
21579
  // src/core/nodeModulesBins.ts
20038
- import { join as join11 } from "node:path";
21580
+ import { join as join15 } from "node:path";
20039
21581
  function nodeModulesBinCandidates(envDir, name, platform) {
20040
- const binDir = join11(envDir, "node_modules", ".bin");
21582
+ const binDir = join15(envDir, "node_modules", ".bin");
20041
21583
  const names = platform === "win32" ? [`${name}.cmd`, `${name}.exe`] : [name];
20042
- return names.map((shim) => join11(binDir, shim));
21584
+ return names.map((shim) => join15(binDir, shim));
20043
21585
  }
20044
21586
  function appiumCliCandidates(envDir, platform) {
20045
21587
  return nodeModulesBinCandidates(envDir, "appium", platform);
@@ -20096,21 +21638,21 @@ async function checkUiautomator2(spawn, appiumBin, platform) {
20096
21638
  }
20097
21639
 
20098
21640
  // src/core/androidBins.ts
20099
- import { join as join12 } from "node:path";
21641
+ import { join as join16 } from "node:path";
20100
21642
  function withExeSuffix(name, platform) {
20101
21643
  return platform === "win32" ? `${name}.exe` : name;
20102
21644
  }
20103
21645
  function emulatorBin(home, platform) {
20104
21646
  const name = withExeSuffix("emulator", platform);
20105
- return home ? join12(home, "emulator", name) : name;
21647
+ return home ? join16(home, "emulator", name) : name;
20106
21648
  }
20107
21649
  function adbBin(home, platform) {
20108
21650
  const name = withExeSuffix("adb", platform);
20109
- return home ? join12(home, "platform-tools", name) : name;
21651
+ return home ? join16(home, "platform-tools", name) : name;
20110
21652
  }
20111
21653
  function cmdlineToolsBin(home, name, platform) {
20112
21654
  const file = platform === "win32" ? `${name}.bat` : name;
20113
- return join12(home, "cmdline-tools", "latest", "bin", file);
21655
+ return join16(home, "cmdline-tools", "latest", "bin", file);
20114
21656
  }
20115
21657
  function sdkManagerBin(home, platform) {
20116
21658
  return cmdlineToolsBin(home, "sdkmanager", platform);
@@ -20407,9 +21949,9 @@ async function checkNpmRegistry(deps) {
20407
21949
  }
20408
21950
 
20409
21951
  // src/core/playwrightCli.ts
20410
- import { join as join13 } from "node:path";
21952
+ import { join as join17 } from "node:path";
20411
21953
  function playwrightCliJsPath(envDir) {
20412
- return join13(envDir, "node_modules", "playwright", "cli.js");
21954
+ return join17(envDir, "node_modules", "playwright", "cli.js");
20413
21955
  }
20414
21956
  function playwrightCliInvocation(args) {
20415
21957
  return {
@@ -20559,11 +22101,11 @@ function renderResults(ui, results) {
20559
22101
  }
20560
22102
 
20561
22103
  // src/domains/flows/ensureDeps.ts
20562
- import { dirname as dirname4, join as join14 } from "node:path";
22104
+ import { dirname as dirname4, join as join18 } from "node:path";
20563
22105
  function findEnvDir(flowPath, fs) {
20564
22106
  let dir = dirname4(flowPath);
20565
22107
  while (true) {
20566
- if (fs.existsSync(join14(dir, "package.json")))
22108
+ if (fs.existsSync(join18(dir, "package.json")))
20567
22109
  return dir;
20568
22110
  const parent = dirname4(dir);
20569
22111
  if (parent === dir)
@@ -20589,7 +22131,7 @@ function resolveProjectDirSafe(files, fs) {
20589
22131
  }
20590
22132
 
20591
22133
  // src/domains/flows/expand.ts
20592
- import { join as join15, resolve as resolve3 } from "node:path";
22134
+ import { join as join19, resolve as resolve3 } from "node:path";
20593
22135
 
20594
22136
  // node_modules/tinyglobby/dist/index.mjs
20595
22137
  import { readdir, readdirSync, realpath, realpathSync, stat, statSync } from "fs";
@@ -21578,11 +23120,11 @@ function makePeekFlowMeta(fs) {
21578
23120
  };
21579
23121
  }
21580
23122
  async function resolveGlobRoots(cwd, fs) {
21581
- const qawolfPath = join15(cwd, ".qawolf");
23123
+ const qawolfPath = join19(cwd, ".qawolf");
21582
23124
  let envDirs = [];
21583
23125
  try {
21584
23126
  const entries = await fs.readdirWithTypes(qawolfPath);
21585
- envDirs = entries.filter((e) => e.isDirectory()).map((e) => join15(qawolfPath, e.name));
23127
+ envDirs = entries.filter((e) => e.isDirectory()).map((e) => join19(qawolfPath, e.name));
21586
23128
  } catch {}
21587
23129
  return [cwd, ...envDirs];
21588
23130
  }
@@ -21608,11 +23150,11 @@ async function expandPatterns(patterns, cwd, logger, fs) {
21608
23150
  }
21609
23151
 
21610
23152
  // src/domains/runtimeEnv/clearRuntimeEnv.ts
21611
- import { join as join17 } from "node:path";
23153
+ import { join as join21 } from "node:path";
21612
23154
 
21613
23155
  // src/domains/runtimeEnv/managedEnvDir.ts
21614
23156
  import { createHash } from "node:crypto";
21615
- import { join as join16, resolve as resolve4 } from "node:path";
23157
+ import { join as join20, resolve as resolve4 } from "node:path";
21616
23158
 
21617
23159
  // src/domains/runtimeEnv/pinnedPackages.ts
21618
23160
  var pinnedPackages = [
@@ -21643,19 +23185,19 @@ function managedEnvBaseDir() {
21643
23185
  const override = process.env["QAWOLF_RUNTIME_DIR"]?.trim();
21644
23186
  if (override)
21645
23187
  return resolve4(override);
21646
- return join16(getDataDir(), "runtime");
23188
+ return join20(getDataDir(), "runtime");
21647
23189
  }
21648
23190
  function runStagingRoot() {
21649
23191
  return `${managedEnvBaseDir()}-runs`;
21650
23192
  }
21651
23193
  function managedEnvDir() {
21652
- return join16(managedEnvBaseDir(), managedEnvHash());
23194
+ return join20(managedEnvBaseDir(), managedEnvHash());
21653
23195
  }
21654
23196
  async function scaffoldManagedEnv(dir, fs) {
21655
23197
  await fs.mkdir(dir, { recursive: true });
21656
23198
  const dependencies = Object.fromEntries(pinnedPackages.map(({ name, version }) => [name, version]));
21657
- await fs.writeFile(join16(dir, "package.json"), JSON.stringify({ name: "qawolf-runtime", private: true, dependencies }, undefined, 2));
21658
- await fs.writeFile(join16(dir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
23199
+ await fs.writeFile(join20(dir, "package.json"), JSON.stringify({ name: "qawolf-runtime", private: true, dependencies }, undefined, 2));
23200
+ await fs.writeFile(join20(dir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
21659
23201
  `);
21660
23202
  }
21661
23203
 
@@ -21677,67 +23219,19 @@ async function isManagedRuntimeBase(dir, fs) {
21677
23219
  for (const entry of entries) {
21678
23220
  if (!entry.isDirectory())
21679
23221
  return false;
21680
- if (!await hasRuntimeSentinel(join17(dir, entry.name), fs))
23222
+ if (!await hasRuntimeSentinel(join21(dir, entry.name), fs))
21681
23223
  return false;
21682
23224
  }
21683
23225
  return true;
21684
23226
  }
21685
23227
  async function hasRuntimeSentinel(hashDir, fs) {
21686
23228
  try {
21687
- const pkg = JSON.parse(await fs.readFile(join17(hashDir, "package.json")));
23229
+ const pkg = JSON.parse(await fs.readFile(join21(hashDir, "package.json")));
21688
23230
  return pkg.name === runtimeSentinel;
21689
23231
  } catch {
21690
23232
  return false;
21691
23233
  }
21692
23234
  }
21693
- // src/shell/spawn.ts
21694
- import { spawn } from "node:child_process";
21695
-
21696
- // src/core/cmdEscape.ts
21697
- var metaChars = /([()\][%!^"`<>&|;, *?])/g;
21698
- function escapeCommand(command) {
21699
- return command.replace(metaChars, "^$1");
21700
- }
21701
- function escapeArgument(arg) {
21702
- const escaped = arg.replace(/(?=(\\+?)?)\1"/g, "$1$1\\\"").replace(/(?=(\\+?)?)\1$/, "$1$1");
21703
- return `"${escaped}"`.replace(metaChars, "^$1").replace(metaChars, "^$1");
21704
- }
21705
-
21706
- // src/shell/spawn.ts
21707
- function buildSpawnCommand(cmd, args, platform, env) {
21708
- const options = {};
21709
- if (env)
21710
- options.env = env;
21711
- if (platform !== "win32" || !/\.(cmd|bat)$/i.test(cmd)) {
21712
- return { cmd, args, options };
21713
- }
21714
- const line = [escapeCommand(cmd), ...args.map(escapeArgument)].join(" ");
21715
- return {
21716
- cmd: process.env["ComSpec"] ?? "cmd.exe",
21717
- args: ["/d", "/s", "/c", `"${line}"`],
21718
- options: { ...options, windowsVerbatimArguments: true }
21719
- };
21720
- }
21721
- var defaultSpawn = (cmd, args, opts) => new Promise((resolve) => {
21722
- const env = opts.env ? { ...process.env, ...opts.env } : undefined;
21723
- const built = buildSpawnCommand(cmd, args, opts.platform, env);
21724
- const child = spawn(built.cmd, built.args, built.options);
21725
- let stdout = "";
21726
- let stderr = "";
21727
- child.stdout?.on("data", (chunk) => {
21728
- stdout += String(chunk);
21729
- });
21730
- child.stderr?.on("data", (chunk) => {
21731
- stderr += String(chunk);
21732
- });
21733
- if (opts?.stdin !== undefined) {
21734
- child.stdin?.write(opts.stdin);
21735
- child.stdin?.end();
21736
- }
21737
- child.on("error", (err) => resolve({ exitCode: -1, stdout, stderr: stderr || err.message }));
21738
- child.on("close", (code) => resolve({ exitCode: code ?? -1, stdout, stderr }));
21739
- });
21740
-
21741
23235
  // src/domains/runtimeEnv/npmInstall.ts
21742
23236
  function buildNpmInstallSpawn(cwd, platform) {
21743
23237
  const built = buildSpawnCommand(resolveNpmCommand(platform), ["install", "--legacy-peer-deps"], platform, undefined);
@@ -21758,10 +23252,10 @@ function spawnNpmInstall(cwd, spawn2 = spawn) {
21758
23252
  }
21759
23253
 
21760
23254
  // src/domains/runtimeEnv/resolvePinned.ts
21761
- import { join as join18 } from "node:path";
23255
+ import { join as join22 } from "node:path";
21762
23256
  function readInstalledVersion(dir, pkgName, fs) {
21763
23257
  try {
21764
- const pkgPath = join18(dir, "node_modules", ...pkgName.split("/"), "package.json");
23258
+ const pkgPath = join22(dir, "node_modules", ...pkgName.split("/"), "package.json");
21765
23259
  const raw = JSON.parse(fs.readFileSync(pkgPath));
21766
23260
  const { version } = raw;
21767
23261
  return typeof version === "string" ? version : undefined;
@@ -21809,10 +23303,10 @@ function allPinnedResolved(dir, fs, platform) {
21809
23303
  }
21810
23304
 
21811
23305
  // src/domains/runtimeEnv/shimDeps.ts
21812
- import { join as join20 } from "node:path";
23306
+ import { join as join24 } from "node:path";
21813
23307
 
21814
23308
  // src/shell/resolveExport.ts
21815
- import { join as join19 } from "node:path";
23309
+ import { join as join23 } from "node:path";
21816
23310
  function pickPath(entry, preference) {
21817
23311
  if (typeof entry === "string")
21818
23312
  return entry;
@@ -21844,28 +23338,28 @@ function resolveFromEnvDir(envDir, specifier, preference = "esm", fs = makeDefau
21844
23338
  const pkgName = parts.slice(0, pkgPartCount).join("/");
21845
23339
  const rest = parts.slice(pkgPartCount);
21846
23340
  const subpath = rest.length > 0 ? "./" + rest.join("/") : ".";
21847
- const pkgDir = join19(envDir, "node_modules", ...pkgName.split("/"));
23341
+ const pkgDir = join23(envDir, "node_modules", ...pkgName.split("/"));
21848
23342
  let pkg;
21849
23343
  try {
21850
- pkg = JSON.parse(fs.readFileSync(join19(pkgDir, "package.json")));
23344
+ pkg = JSON.parse(fs.readFileSync(join23(pkgDir, "package.json")));
21851
23345
  } catch (err) {
21852
23346
  if (isNoEntError(err))
21853
- throw new Error(`Package '${pkgName}' not found in ${join19(envDir, "node_modules")}`, { cause: err });
23347
+ throw new Error(`Package '${pkgName}' not found in ${join23(envDir, "node_modules")}`, { cause: err });
21854
23348
  throw err;
21855
23349
  }
21856
23350
  const exportsMap = normalizeExports(pkg.exports);
21857
23351
  const entry = exportsMap?.[subpath];
21858
23352
  const relative = pickPath(entry, preference) ?? (subpath === "." ? pkg.module ?? pkg.main : undefined);
21859
23353
  if (!relative) {
21860
- throw new Error(`No entry for "${subpath}" in exports of ${join19(pkgDir, "package.json")}`);
23354
+ throw new Error(`No entry for "${subpath}" in exports of ${join23(pkgDir, "package.json")}`);
21861
23355
  }
21862
- return join19(pkgDir, relative);
23356
+ return join23(pkgDir, relative);
21863
23357
  }
21864
23358
 
21865
23359
  // src/domains/runtimeEnv/shimDeps.ts
21866
23360
  function readShimMarker(shimDir, fs) {
21867
23361
  try {
21868
- const pkg = JSON.parse(fs.readFileSync(join20(shimDir, "package.json")));
23362
+ const pkg = JSON.parse(fs.readFileSync(join24(shimDir, "package.json")));
21869
23363
  if (pkg._qawolf_format) {
21870
23364
  return {
21871
23365
  _qawolf_version: pkg._qawolf_version ?? "",
@@ -21881,11 +23375,11 @@ function resolveBunBuild(bunBuild) {
21881
23375
  return bunBuild === false ? undefined : { build: bunBuild };
21882
23376
  }
21883
23377
  async function removeStaleShims(flowsDir, flowsDeps, fs) {
21884
- const shimsDir = join20(flowsDir, "node_modules");
23378
+ const shimsDir = join24(flowsDir, "node_modules");
21885
23379
  if (!fs.existsSync(shimsDir))
21886
23380
  return;
21887
23381
  for (const dep of flowsDeps) {
21888
- const shimDepDir = join20(shimsDir, ...dep.split("/"));
23382
+ const shimDepDir = join24(shimsDir, ...dep.split("/"));
21889
23383
  if (readShimMarker(shimDepDir, fs)) {
21890
23384
  await fs.rm(shimDepDir, { recursive: true, force: true });
21891
23385
  }
@@ -21894,17 +23388,17 @@ async function removeStaleShims(flowsDir, flowsDeps, fs) {
21894
23388
  async function buildDepShim(args) {
21895
23389
  const { envDir, flowsDir, dep, fs, build } = args;
21896
23390
  const depParts = dep.split("/");
21897
- const depDir = join20(envDir, "node_modules", ...depParts);
23391
+ const depDir = join24(envDir, "node_modules", ...depParts);
21898
23392
  if (!fs.existsSync(depDir))
21899
23393
  return;
21900
23394
  let depVersion;
21901
23395
  try {
21902
- const pkg = JSON.parse(fs.readFileSync(join20(depDir, "package.json")));
23396
+ const pkg = JSON.parse(fs.readFileSync(join24(depDir, "package.json")));
21903
23397
  depVersion = pkg.version ?? "unknown";
21904
23398
  } catch {
21905
23399
  depVersion = "unknown";
21906
23400
  }
21907
- const shimDir = join20(flowsDir, "node_modules", ...depParts);
23401
+ const shimDir = join24(flowsDir, "node_modules", ...depParts);
21908
23402
  if (fs.existsSync(shimDir)) {
21909
23403
  const marker = readShimMarker(shimDir, fs);
21910
23404
  if (!marker)
@@ -21931,20 +23425,20 @@ async function buildDepShim(args) {
21931
23425
  }
21932
23426
  const shimCode = await output.text();
21933
23427
  await fs.mkdir(shimDir, { recursive: true });
21934
- await fs.writeFile(join20(shimDir, "package.json"), JSON.stringify({
23428
+ await fs.writeFile(join24(shimDir, "package.json"), JSON.stringify({
21935
23429
  name: dep,
21936
23430
  _qawolf_version: depVersion,
21937
23431
  _qawolf_format: "bun-build-v1"
21938
23432
  }));
21939
- await fs.writeFile(join20(shimDir, "index.js"), shimCode);
23433
+ await fs.writeFile(join24(shimDir, "index.js"), shimCode);
21940
23434
  }
21941
23435
  async function shimFlowsDeps(envDir, fs = makeDefaultFs(), bunBuild) {
21942
- const flowsDir = join20(envDir, "node_modules", "@qawolf", "flows");
23436
+ const flowsDir = join24(envDir, "node_modules", "@qawolf", "flows");
21943
23437
  if (!fs.existsSync(flowsDir))
21944
23438
  return;
21945
23439
  let flowsDeps;
21946
23440
  try {
21947
- const flowsPkg = JSON.parse(fs.readFileSync(join20(flowsDir, "package.json")));
23441
+ const flowsPkg = JSON.parse(fs.readFileSync(join24(flowsDir, "package.json")));
21948
23442
  flowsDeps = Object.keys(flowsPkg.dependencies ?? {});
21949
23443
  } catch {
21950
23444
  return;
@@ -22125,11 +23619,11 @@ function collectValue(value, previous) {
22125
23619
  }
22126
23620
 
22127
23621
  // src/shell/manifest/pulledEnv.ts
22128
- import { join as join22 } from "node:path";
23622
+ import { join as join26 } from "node:path";
22129
23623
 
22130
23624
  // src/shell/manifest/io.ts
22131
23625
  import { createHash as createHash2 } from "node:crypto";
22132
- import { join as join21 } from "node:path";
23626
+ import { join as join25 } from "node:path";
22133
23627
  var manifestFilename = ".manifest.json";
22134
23628
  var flowEntrySchema = object({
22135
23629
  path: string2(),
@@ -22151,7 +23645,7 @@ var manifestSchema = object({
22151
23645
  async function readManifest(envDir, fs = makeDefaultFs()) {
22152
23646
  let raw;
22153
23647
  try {
22154
- raw = await fs.readFile(join21(envDir, manifestFilename));
23648
+ raw = await fs.readFile(join25(envDir, manifestFilename));
22155
23649
  } catch (err) {
22156
23650
  if (isNoEntError(err))
22157
23651
  return "missing";
@@ -22185,7 +23679,7 @@ async function readManifest(envDir, fs = makeDefaultFs()) {
22185
23679
  }
22186
23680
  async function writeManifest(envDir, manifest, fs = makeDefaultFs()) {
22187
23681
  const body = JSON.stringify(manifest, undefined, 2);
22188
- await fs.writeFile(join21(envDir, manifestFilename), `${body}
23682
+ await fs.writeFile(join25(envDir, manifestFilename), `${body}
22189
23683
  `);
22190
23684
  }
22191
23685
  function hashFile(absPath, fs = makeDefaultFs()) {
@@ -22211,7 +23705,7 @@ async function findPulledEnv(ref, cwd, fs = makeDefaultFs()) {
22211
23705
  return;
22212
23706
  }
22213
23707
  async function listPulledEnvDirs(cwd, fs = makeDefaultFs()) {
22214
- const root = join22(cwd, ".qawolf");
23708
+ const root = join26(cwd, ".qawolf");
22215
23709
  let entries;
22216
23710
  try {
22217
23711
  entries = await fs.readdirWithTypes(root);
@@ -22224,7 +23718,7 @@ async function listPulledEnvDirs(cwd, fs = makeDefaultFs()) {
22224
23718
  for (const entry of entries) {
22225
23719
  if (!entry.isDirectory())
22226
23720
  continue;
22227
- const envDir = join22(root, entry.name);
23721
+ const envDir = join26(root, entry.name);
22228
23722
  const manifest = await readManifest(envDir, fs);
22229
23723
  if (typeof manifest === "string")
22230
23724
  continue;
@@ -22278,9 +23772,6 @@ async function readEnvLabels(files, readEnvLabel) {
22278
23772
  }
22279
23773
 
22280
23774
  // src/core/suggestNearMiss.ts
22281
- function normalize3(value) {
22282
- return value.toLowerCase().replace(/[-_\s]+/g, " ").trim();
22283
- }
22284
23775
  function editDistance(a, b) {
22285
23776
  let row = Array.from({ length: b.length + 1 }, (_, j) => j);
22286
23777
  for (let i = 1;i <= a.length; i += 1) {
@@ -22302,14 +23793,14 @@ function suggestNearMiss(input, candidates) {
22302
23793
  if (candidates.includes(input))
22303
23794
  return;
22304
23795
  const sorted = [...candidates].sort();
22305
- const normalizedInput = normalize3(input);
22306
- const presentationMatch = sorted.find((candidate) => normalize3(candidate) === normalizedInput);
23796
+ const normalizedInput = normalizeForSearch(input);
23797
+ const presentationMatch = sorted.find((candidate) => normalizeForSearch(candidate) === normalizedInput);
22307
23798
  if (presentationMatch !== undefined)
22308
23799
  return presentationMatch;
22309
23800
  let best;
22310
23801
  let bestDistance = Number.POSITIVE_INFINITY;
22311
23802
  for (const candidate of sorted) {
22312
- const distance = editDistance(normalizedInput, normalize3(candidate));
23803
+ const distance = editDistance(normalizedInput, normalizeForSearch(candidate));
22313
23804
  if (distance < bestDistance) {
22314
23805
  bestDistance = distance;
22315
23806
  best = candidate;
@@ -22619,7 +24110,7 @@ async function flowsListRemote(ctx, pattern, options) {
22619
24110
  }
22620
24111
 
22621
24112
  // src/domains/flows/pull/handler.ts
22622
- import { dirname as dirname8, join as join27, resolve as resolve8 } from "node:path";
24113
+ import { dirname as dirname8, join as join31, resolve as resolve8 } from "node:path";
22623
24114
 
22624
24115
  // src/domains/flows/pull/fetchPhase.ts
22625
24116
  async function fetchTags(ctx, envId) {
@@ -22763,7 +24254,7 @@ async function checkSafety(args) {
22763
24254
  }
22764
24255
 
22765
24256
  // src/domains/flows/pull/reportPull.ts
22766
- import { join as join23 } from "node:path";
24257
+ import { join as join27 } from "node:path";
22767
24258
  function reportPullResult(ui, args) {
22768
24259
  if (ui.mode !== "json")
22769
24260
  return;
@@ -22778,12 +24269,12 @@ function reportPullResult(ui, args) {
22778
24269
  assetDownloadedCount: args.assets.downloadedCount,
22779
24270
  assetReusedCount: args.assets.reusedCount,
22780
24271
  assetSkippedCount: args.assets.skippedCount,
22781
- manifestPath: join23(args.stage.envDir, manifestFilename)
24272
+ manifestPath: join27(args.stage.envDir, manifestFilename)
22782
24273
  }, "");
22783
24274
  }
22784
24275
 
22785
24276
  // src/domains/flows/pull/bundle.ts
22786
- import { join as join24, relative as relative6 } from "node:path";
24277
+ import { join as join28, relative as relative6 } from "node:path";
22787
24278
  async function flattenSingleWrapper(dir, fs = makeDefaultFs()) {
22788
24279
  const entries = await fs.readdir(dir);
22789
24280
  if (entries.length !== 1)
@@ -22791,12 +24282,12 @@ async function flattenSingleWrapper(dir, fs = makeDefaultFs()) {
22791
24282
  const innerName = entries[0];
22792
24283
  if (!innerName)
22793
24284
  return;
22794
- const inner = join24(dir, innerName);
24285
+ const inner = join28(dir, innerName);
22795
24286
  const innerStat = await fs.stat(inner);
22796
24287
  if (!innerStat.isDirectory())
22797
24288
  return;
22798
24289
  for (const e of await fs.readdir(inner)) {
22799
- await fs.rename(join24(inner, e), join24(dir, e));
24290
+ await fs.rename(join28(inner, e), join28(dir, e));
22800
24291
  }
22801
24292
  const leftovers = await fs.readdir(inner);
22802
24293
  if (leftovers.length > 0) {
@@ -22815,7 +24306,7 @@ async function buildManifest(args, fs = makeDefaultFs()) {
22815
24306
  const flowPaths = await walkForFlows(args.bundleDir, fs);
22816
24307
  const flows = await Promise.all(flowPaths.map(async (rel) => ({
22817
24308
  path: toPosix(rel),
22818
- contentHash: await hashFile(join24(args.bundleDir, rel), fs),
24309
+ contentHash: await hashFile(join28(args.bundleDir, rel), fs),
22819
24310
  tags: args.tags?.byPath.get(toPosix(rel))
22820
24311
  })));
22821
24312
  return {
@@ -22839,7 +24330,7 @@ async function walkForFlows(root, fs) {
22839
24330
  async function walk(current, root, out, fs) {
22840
24331
  const entries = await fs.readdirWithTypes(current);
22841
24332
  for (const e of entries) {
22842
- const abs = join24(current, e.name);
24333
+ const abs = join28(current, e.name);
22843
24334
  if (e.isDirectory()) {
22844
24335
  await walk(abs, root, out, fs);
22845
24336
  } else if (e.isFile() && flowExtensions.some((ext) => e.name.endsWith(ext))) {
@@ -22852,11 +24343,11 @@ async function sampleQawolfCommittedAt(bundleDir, fs = makeDefaultFs()) {
22852
24343
  const sample = flowPaths[0];
22853
24344
  if (!sample)
22854
24345
  return;
22855
- return (await fs.stat(join24(bundleDir, sample))).mtime.toISOString();
24346
+ return (await fs.stat(join28(bundleDir, sample))).mtime.toISOString();
22856
24347
  }
22857
24348
 
22858
24349
  // src/domains/flows/pull/applyTeamStorageRewrite.ts
22859
- import { join as join25, relative as relative7 } from "node:path";
24350
+ import { join as join29, relative as relative7 } from "node:path";
22860
24351
 
22861
24352
  // src/domains/flows/pull/rewriteTeamStorage.ts
22862
24353
  var runnerMountPrefix = "/home/wolf/team-storage/";
@@ -22892,7 +24383,7 @@ function isFlowFile(name) {
22892
24383
  async function walk2(dir, out, fs) {
22893
24384
  const entries = await fs.readdirWithTypes(dir);
22894
24385
  for (const e of entries) {
22895
- const abs = join25(dir, e.name);
24386
+ const abs = join29(dir, e.name);
22896
24387
  if (e.isDirectory()) {
22897
24388
  await walk2(abs, out, fs);
22898
24389
  } else if (e.isFile() && isSourceFile(e.name)) {
@@ -22920,7 +24411,7 @@ async function applyTeamStorageRewrite(rootDir, fs = makeDefaultFs()) {
22920
24411
  }
22921
24412
 
22922
24413
  // src/domains/flows/pull/envVars.ts
22923
- import { join as join26 } from "node:path";
24414
+ import { join as join30 } from "node:path";
22924
24415
 
22925
24416
  // src/core/dotenv.ts
22926
24417
  var bareKeyPattern = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -22985,7 +24476,7 @@ function unquote(escaped) {
22985
24476
  async function writeEnvFile(envDir, vars, fs = makeDefaultFs()) {
22986
24477
  if (Object.keys(vars).length === 0)
22987
24478
  return;
22988
- await fs.writeFile(join26(envDir, ".env"), serializeDotenv(vars), {
24479
+ await fs.writeFile(join30(envDir, ".env"), serializeDotenv(vars), {
22989
24480
  mode: 384
22990
24481
  });
22991
24482
  }
@@ -23049,11 +24540,11 @@ var Ae = Symbol("flush");
23049
24540
  var As = Symbol("flushChunk");
23050
24541
  var z = Symbol("encoding");
23051
24542
  var Mt = Symbol("decoder");
23052
- var g = Symbol("flowing");
24543
+ var g2 = Symbol("flowing");
23053
24544
  var Qt = Symbol("paused");
23054
24545
  var Bt = Symbol("resume");
23055
24546
  var b = Symbol("buffer");
23056
- var N = Symbol("pipes");
24547
+ var N2 = Symbol("pipes");
23057
24548
  var _ = Symbol("bufferLength");
23058
24549
  var bi = Symbol("bufferPush");
23059
24550
  var Ie = Symbol("bufferShift");
@@ -23101,9 +24592,9 @@ var Li = class extends Fe {
23101
24592
  var Xr = (s) => !!s.objectMode;
23102
24593
  var qr = (s) => !s.objectMode && !!s.encoding && s.encoding !== "buffer";
23103
24594
  var A2 = class extends Di {
23104
- [g] = false;
24595
+ [g2] = false;
23105
24596
  [Qt] = false;
23106
- [N] = [];
24597
+ [N2] = [];
23107
24598
  [b] = [];
23108
24599
  [L];
23109
24600
  [z];
@@ -23126,7 +24617,7 @@ var A2 = class extends Di {
23126
24617
  let e = t[0] || {};
23127
24618
  if (super(), e.objectMode && typeof e.encoding == "string")
23128
24619
  throw new TypeError("Encoding and objectMode may not be used together");
23129
- Xr(e) ? (this[L] = true, this[z] = null) : qr(e) ? (this[z] = e.encoding, this[L] = false) : (this[L] = false, this[z] = null), this[Z] = !!e.async, this[Mt] = this[z] ? new Hr(this[z]) : null, e && e.debugExposeBuffer === true && Object.defineProperty(this, "buffer", { get: () => this[b] }), e && e.debugExposePipes === true && Object.defineProperty(this, "pipes", { get: () => this[N] });
24620
+ Xr(e) ? (this[L] = true, this[z] = null) : qr(e) ? (this[z] = e.encoding, this[L] = false) : (this[L] = false, this[z] = null), this[Z] = !!e.async, this[Mt] = this[z] ? new Hr(this[z]) : null, e && e.debugExposeBuffer === true && Object.defineProperty(this, "buffer", { get: () => this[b] }), e && e.debugExposePipes === true && Object.defineProperty(this, "pipes", { get: () => this[N2] });
23130
24621
  let { signal: i } = e;
23131
24622
  i && (this[Jt] = i, i.aborted ? this[xi]() : i.addEventListener("abort", () => this[xi]()));
23132
24623
  }
@@ -23178,7 +24669,7 @@ var A2 = class extends Di {
23178
24669
  else if (typeof t != "string")
23179
24670
  throw new Error("Non-contiguous data written to non-objectMode stream");
23180
24671
  }
23181
- return this[L] ? (this[g] && this[_] !== 0 && this[Ae](true), this[g] ? this.emit("data", t) : this[bi](t), this[_] !== 0 && this.emit("readable"), i && r(i), this[g]) : t.length ? (typeof t == "string" && !(e === this[z] && !this[Mt]?.lastNeed) && (t = Buffer.from(t, e)), Buffer.isBuffer(t) && this[z] && (t = this[Mt].write(t)), this[g] && this[_] !== 0 && this[Ae](true), this[g] ? this.emit("data", t) : this[bi](t), this[_] !== 0 && this.emit("readable"), i && r(i), this[g]) : (this[_] !== 0 && this.emit("readable"), i && r(i), this[g]);
24672
+ return this[L] ? (this[g2] && this[_] !== 0 && this[Ae](true), this[g2] ? this.emit("data", t) : this[bi](t), this[_] !== 0 && this.emit("readable"), i && r(i), this[g2]) : t.length ? (typeof t == "string" && !(e === this[z] && !this[Mt]?.lastNeed) && (t = Buffer.from(t, e)), Buffer.isBuffer(t) && this[z] && (t = this[Mt].write(t)), this[g2] && this[_] !== 0 && this[Ae](true), this[g2] ? this.emit("data", t) : this[bi](t), this[_] !== 0 && this.emit("readable"), i && r(i), this[g2]) : (this[_] !== 0 && this.emit("readable"), i && r(i), this[g2]);
23182
24673
  }
23183
24674
  read(t) {
23184
24675
  if (this[S])
@@ -23199,22 +24690,22 @@ var A2 = class extends Di {
23199
24690
  return this.emit("data", e), !this[b].length && !this[Q] && this.emit("drain"), e;
23200
24691
  }
23201
24692
  end(t, e, i) {
23202
- return typeof t == "function" && (i = t, t = undefined), typeof e == "function" && (i = e, e = "utf8"), t !== undefined && this.write(t, e), i && this.once("end", i), this[Q] = true, this.writable = false, (this[g] || !this[Qt]) && this[J](), this;
24693
+ return typeof t == "function" && (i = t, t = undefined), typeof e == "function" && (i = e, e = "utf8"), t !== undefined && this.write(t, e), i && this.once("end", i), this[Q] = true, this.writable = false, (this[g2] || !this[Qt]) && this[J](), this;
23203
24694
  }
23204
24695
  [Bt]() {
23205
- this[S] || (!this[Rt] && !this[N].length && (this[C2] = true), this[Qt] = false, this[g] = true, this.emit("resume"), this[b].length ? this[Ae]() : this[Q] ? this[J]() : this.emit("drain"));
24696
+ this[S] || (!this[Rt] && !this[N2].length && (this[C2] = true), this[Qt] = false, this[g2] = true, this.emit("resume"), this[b].length ? this[Ae]() : this[Q] ? this[J]() : this.emit("drain"));
23206
24697
  }
23207
24698
  resume() {
23208
24699
  return this[Bt]();
23209
24700
  }
23210
24701
  pause() {
23211
- this[g] = false, this[Qt] = true, this[C2] = false;
24702
+ this[g2] = false, this[Qt] = true, this[C2] = false;
23212
24703
  }
23213
24704
  get destroyed() {
23214
24705
  return this[S];
23215
24706
  }
23216
24707
  get flowing() {
23217
- return this[g];
24708
+ return this[g2];
23218
24709
  }
23219
24710
  get paused() {
23220
24711
  return this[Qt];
@@ -23232,18 +24723,18 @@ var A2 = class extends Di {
23232
24723
  !t && !this[b].length && !this[Q] && this.emit("drain");
23233
24724
  }
23234
24725
  [As](t) {
23235
- return this.emit("data", t), this[g];
24726
+ return this.emit("data", t), this[g2];
23236
24727
  }
23237
24728
  pipe(t, e) {
23238
24729
  if (this[S])
23239
24730
  return t;
23240
24731
  this[C2] = false;
23241
24732
  let i = this[nt];
23242
- return e = e || {}, t === Ds.stdout || t === Ds.stderr ? e.end = false : e.end = e.end !== false, e.proxyErrors = !!e.proxyErrors, i ? e.end && t.end() : (this[N].push(e.proxyErrors ? new Li(this, t, e) : new Fe(this, t, e)), this[Z] ? jt(() => this[Bt]()) : this[Bt]()), t;
24733
+ return e = e || {}, t === Ds.stdout || t === Ds.stderr ? e.end = false : e.end = e.end !== false, e.proxyErrors = !!e.proxyErrors, i ? e.end && t.end() : (this[N2].push(e.proxyErrors ? new Li(this, t, e) : new Fe(this, t, e)), this[Z] ? jt(() => this[Bt]()) : this[Bt]()), t;
23243
24734
  }
23244
24735
  unpipe(t) {
23245
- let e = this[N].find((i) => i.dest === t);
23246
- e && (this[N].length === 1 ? (this[g] && this[Rt] === 0 && (this[g] = false), this[N] = []) : this[N].splice(this[N].indexOf(e), 1), e.unpipe());
24736
+ let e = this[N2].find((i) => i.dest === t);
24737
+ e && (this[N2].length === 1 ? (this[g2] && this[Rt] === 0 && (this[g2] = false), this[N2] = []) : this[N2].splice(this[N2].indexOf(e), 1), e.unpipe());
23247
24738
  }
23248
24739
  addListener(t, e) {
23249
24740
  return this.on(t, e);
@@ -23251,7 +24742,7 @@ var A2 = class extends Di {
23251
24742
  on(t, e) {
23252
24743
  let i = super.on(t, e);
23253
24744
  if (t === "data")
23254
- this[C2] = false, this[Rt]++, !this[N].length && !this[g] && this[Bt]();
24745
+ this[C2] = false, this[Rt]++, !this[N2].length && !this[g2] && this[Bt]();
23255
24746
  else if (t === "readable" && this[_] !== 0)
23256
24747
  super.emit("readable");
23257
24748
  else if (Kr(t) && this[nt])
@@ -23267,11 +24758,11 @@ var A2 = class extends Di {
23267
24758
  }
23268
24759
  off(t, e) {
23269
24760
  let i = super.off(t, e);
23270
- return t === "data" && (this[Rt] = this.listeners("data").length, this[Rt] === 0 && !this[C2] && !this[N].length && (this[g] = false)), i;
24761
+ return t === "data" && (this[Rt] = this.listeners("data").length, this[Rt] === 0 && !this[C2] && !this[N2].length && (this[g2] = false)), i;
23271
24762
  }
23272
24763
  removeAllListeners(t) {
23273
24764
  let e = super.removeAllListeners(t);
23274
- return (t === "data" || t === undefined) && (this[Rt] = 0, !this[C2] && !this[N].length && (this[g] = false)), e;
24765
+ return (t === "data" || t === undefined) && (this[Rt] = 0, !this[C2] && !this[N2].length && (this[g2] = false)), e;
23275
24766
  }
23276
24767
  get emittedEnd() {
23277
24768
  return this[nt];
@@ -23307,7 +24798,7 @@ var A2 = class extends Di {
23307
24798
  return this[J](), r;
23308
24799
  }
23309
24800
  [Oi](t) {
23310
- for (let i of this[N])
24801
+ for (let i of this[N2])
23311
24802
  i.dest.write(t) === false && this.pause();
23312
24803
  let e = this[C2] ? false : super.emit("data", t);
23313
24804
  return this[J](), e;
@@ -23319,12 +24810,12 @@ var A2 = class extends Di {
23319
24810
  if (this[Mt]) {
23320
24811
  let e = this[Mt].end();
23321
24812
  if (e) {
23322
- for (let i of this[N])
24813
+ for (let i of this[N2])
23323
24814
  i.dest.write(e);
23324
24815
  this[C2] || super.emit("data", e);
23325
24816
  }
23326
24817
  }
23327
- for (let e of this[N])
24818
+ for (let e of this[N2])
23328
24819
  e.end();
23329
24820
  let t = super.emit("end");
23330
24821
  return this.removeAllListeners("end"), t;
@@ -23400,7 +24891,7 @@ var Jr = I.writev;
23400
24891
  var ht = Symbol("_autoClose");
23401
24892
  var H = Symbol("_close");
23402
24893
  var te = Symbol("_ended");
23403
- var m2 = Symbol("_fd");
24894
+ var m3 = Symbol("_fd");
23404
24895
  var Ni = Symbol("_finished");
23405
24896
  var tt = Symbol("_flags");
23406
24897
  var Ai = Symbol("_flush");
@@ -23427,7 +24918,7 @@ var Me = Symbol("_defaultFlag");
23427
24918
  var bt = Symbol("_errored");
23428
24919
  var _t = class extends A2 {
23429
24920
  [bt] = false;
23430
- [m2];
24921
+ [m3];
23431
24922
  [U2];
23432
24923
  [Ci];
23433
24924
  [j] = false;
@@ -23437,10 +24928,10 @@ var _t = class extends A2 {
23437
24928
  constructor(t, e) {
23438
24929
  if (e = e || {}, super(e), this.readable = true, this.writable = false, typeof t != "string")
23439
24930
  throw new TypeError("path must be a string");
23440
- this[bt] = false, this[m2] = typeof e.fd == "number" ? e.fd : undefined, this[U2] = t, this[Ci] = e.readSize || 16 * 1024 * 1024, this[j] = false, this[Fi] = typeof e.size == "number" ? e.size : 1 / 0, this[ee] = this[Fi], this[ht] = typeof e.autoClose == "boolean" ? e.autoClose : true, typeof this[m2] == "number" ? this[zt]() : this[at]();
24931
+ this[bt] = false, this[m3] = typeof e.fd == "number" ? e.fd : undefined, this[U2] = t, this[Ci] = e.readSize || 16 * 1024 * 1024, this[j] = false, this[Fi] = typeof e.size == "number" ? e.size : 1 / 0, this[ee] = this[Fi], this[ht] = typeof e.autoClose == "boolean" ? e.autoClose : true, typeof this[m3] == "number" ? this[zt]() : this[at]();
23441
24932
  }
23442
24933
  get fd() {
23443
- return this[m2];
24934
+ return this[m3];
23444
24935
  }
23445
24936
  get path() {
23446
24937
  return this[U2];
@@ -23455,7 +24946,7 @@ var _t = class extends A2 {
23455
24946
  I.open(this[U2], "r", (t, e) => this[Ht](t, e));
23456
24947
  }
23457
24948
  [Ht](t, e) {
23458
- t ? this[Ut](t) : (this[m2] = e, this.emit("open", e), this[zt]());
24949
+ t ? this[Ut](t) : (this[m3] = e, this.emit("open", e), this[zt]());
23459
24950
  }
23460
24951
  [vi]() {
23461
24952
  return Buffer.allocUnsafe(Math.min(this[Ci], this[ee]));
@@ -23466,16 +24957,16 @@ var _t = class extends A2 {
23466
24957
  let t = this[vi]();
23467
24958
  if (t.length === 0)
23468
24959
  return process.nextTick(() => this[Ii](null, 0, t));
23469
- I.read(this[m2], t, 0, t.length, null, (e, i, r) => this[Ii](e, i, r));
24960
+ I.read(this[m3], t, 0, t.length, null, (e, i, r) => this[Ii](e, i, r));
23470
24961
  }
23471
24962
  }
23472
24963
  [Ii](t, e, i) {
23473
24964
  this[j] = false, t ? this[Ut](t) : this[ki](e, i) && this[zt]();
23474
24965
  }
23475
24966
  [H]() {
23476
- if (this[ht] && typeof this[m2] == "number") {
23477
- let t = this[m2];
23478
- this[m2] = undefined, I.close(t, (e) => e ? this.emit("error", e) : this.emit("close"));
24967
+ if (this[ht] && typeof this[m3] == "number") {
24968
+ let t = this[m3];
24969
+ this[m3] = undefined, I.close(t, (e) => e ? this.emit("error", e) : this.emit("close"));
23479
24970
  }
23480
24971
  }
23481
24972
  [Ut](t) {
@@ -23491,7 +24982,7 @@ var _t = class extends A2 {
23491
24982
  case "finish":
23492
24983
  return false;
23493
24984
  case "drain":
23494
- return typeof this[m2] == "number" && this[zt](), false;
24985
+ return typeof this[m3] == "number" && this[zt](), false;
23495
24986
  case "error":
23496
24987
  return this[bt] ? false : (this[bt] = true, super.emit(t, ...e));
23497
24988
  default:
@@ -23514,7 +25005,7 @@ var Be = class extends _t {
23514
25005
  if (!this[j]) {
23515
25006
  this[j] = true;
23516
25007
  do {
23517
- let e = this[vi](), i = e.length === 0 ? 0 : I.readSync(this[m2], e, 0, e.length, null);
25008
+ let e = this[vi](), i = e.length === 0 ? 0 : I.readSync(this[m3], e, 0, e.length, null);
23518
25009
  if (!this[ki](i, e))
23519
25010
  break;
23520
25011
  } while (true);
@@ -23526,9 +25017,9 @@ var Be = class extends _t {
23526
25017
  }
23527
25018
  }
23528
25019
  [H]() {
23529
- if (this[ht] && typeof this[m2] == "number") {
23530
- let t = this[m2];
23531
- this[m2] = undefined, I.closeSync(t), this.emit("close");
25020
+ if (this[ht] && typeof this[m3] == "number") {
25021
+ let t = this[m3];
25022
+ this[m3] = undefined, I.closeSync(t), this.emit("close");
23532
25023
  }
23533
25024
  }
23534
25025
  };
@@ -23543,15 +25034,15 @@ var et = class extends Qr {
23543
25034
  [U2];
23544
25035
  [ie];
23545
25036
  [ht];
23546
- [m2];
25037
+ [m3];
23547
25038
  [Me];
23548
25039
  [tt];
23549
25040
  [Ni] = false;
23550
25041
  [ot];
23551
25042
  constructor(t, e) {
23552
- e = e || {}, super(e), this[U2] = t, this[m2] = typeof e.fd == "number" ? e.fd : undefined, this[ie] = e.mode === undefined ? 438 : e.mode, this[ot] = typeof e.start == "number" ? e.start : undefined, this[ht] = typeof e.autoClose == "boolean" ? e.autoClose : true;
25043
+ e = e || {}, super(e), this[U2] = t, this[m3] = typeof e.fd == "number" ? e.fd : undefined, this[ie] = e.mode === undefined ? 438 : e.mode, this[ot] = typeof e.start == "number" ? e.start : undefined, this[ht] = typeof e.autoClose == "boolean" ? e.autoClose : true;
23553
25044
  let i = this[ot] !== undefined ? "r+" : "w";
23554
- this[Me] = e.flags === undefined, this[tt] = e.flags === undefined ? i : e.flags, this[m2] === undefined && this[at]();
25045
+ this[Me] = e.flags === undefined, this[tt] = e.flags === undefined ? i : e.flags, this[m3] === undefined && this[at]();
23555
25046
  }
23556
25047
  emit(t, ...e) {
23557
25048
  if (t === "error") {
@@ -23562,7 +25053,7 @@ var et = class extends Qr {
23562
25053
  return super.emit(t, ...e);
23563
25054
  }
23564
25055
  get fd() {
23565
- return this[m2];
25056
+ return this[m3];
23566
25057
  }
23567
25058
  get path() {
23568
25059
  return this[U2];
@@ -23574,16 +25065,16 @@ var et = class extends Qr {
23574
25065
  I.open(this[U2], this[tt], this[ie], (t, e) => this[Ht](t, e));
23575
25066
  }
23576
25067
  [Ht](t, e) {
23577
- this[Me] && this[tt] === "r+" && t && t.code === "ENOENT" ? (this[tt] = "w", this[at]()) : t ? this[Ut](t) : (this[m2] = e, this.emit("open", e), this[gt] || this[Ai]());
25068
+ this[Me] && this[tt] === "r+" && t && t.code === "ENOENT" ? (this[tt] = "w", this[at]()) : t ? this[Ut](t) : (this[m3] = e, this.emit("open", e), this[gt] || this[Ai]());
23578
25069
  }
23579
25070
  end(t, e) {
23580
- return t && this.write(t, e), this[te] = true, !this[gt] && !this[Y].length && typeof this[m2] == "number" && this[Pt](null, 0), this;
25071
+ return t && this.write(t, e), this[te] = true, !this[gt] && !this[Y].length && typeof this[m3] == "number" && this[Pt](null, 0), this;
23581
25072
  }
23582
25073
  write(t, e) {
23583
- return typeof t == "string" && (t = Buffer.from(t, e)), this[te] ? (this.emit("error", new Error("write() after end()")), false) : this[m2] === undefined || this[gt] || this[Y].length ? (this[Y].push(t), this[ke] = true, false) : (this[gt] = true, this[ve](t), true);
25074
+ return typeof t == "string" && (t = Buffer.from(t, e)), this[te] ? (this.emit("error", new Error("write() after end()")), false) : this[m3] === undefined || this[gt] || this[Y].length ? (this[Y].push(t), this[ke] = true, false) : (this[gt] = true, this[ve](t), true);
23584
25075
  }
23585
25076
  [ve](t) {
23586
- I.write(this[m2], t, 0, t.length, this[ot], (e, i) => this[Pt](e, i));
25077
+ I.write(this[m3], t, 0, t.length, this[ot], (e, i) => this[Pt](e, i));
23587
25078
  }
23588
25079
  [Pt](t, e) {
23589
25080
  t ? this[Ut](t) : (this[ot] !== undefined && typeof e == "number" && (this[ot] += e), this[Y].length ? this[Ai]() : (this[gt] = false, this[te] && !this[Ni] ? (this[Ni] = true, this[H](), this.emit("finish")) : this[ke] && (this[ke] = false, this.emit("drain"))));
@@ -23595,13 +25086,13 @@ var et = class extends Qr {
23595
25086
  this[ve](this[Y].pop());
23596
25087
  else {
23597
25088
  let t = this[Y];
23598
- this[Y] = [], Jr(this[m2], t, this[ot], (e, i) => this[Pt](e, i));
25089
+ this[Y] = [], Jr(this[m3], t, this[ot], (e, i) => this[Pt](e, i));
23599
25090
  }
23600
25091
  }
23601
25092
  [H]() {
23602
- if (this[ht] && typeof this[m2] == "number") {
23603
- let t = this[m2];
23604
- this[m2] = undefined, I.close(t, (e) => e ? this.emit("error", e) : this.emit("close"));
25093
+ if (this[ht] && typeof this[m3] == "number") {
25094
+ let t = this[m3];
25095
+ this[m3] = undefined, I.close(t, (e) => e ? this.emit("error", e) : this.emit("close"));
23605
25096
  }
23606
25097
  }
23607
25098
  };
@@ -23621,15 +25112,15 @@ var Wt = class extends et {
23621
25112
  this[Ht](null, t);
23622
25113
  }
23623
25114
  [H]() {
23624
- if (this[ht] && typeof this[m2] == "number") {
23625
- let t = this[m2];
23626
- this[m2] = undefined, I.closeSync(t), this.emit("close");
25115
+ if (this[ht] && typeof this[m3] == "number") {
25116
+ let t = this[m3];
25117
+ this[m3] = undefined, I.closeSync(t), this.emit("close");
23627
25118
  }
23628
25119
  }
23629
25120
  [ve](t) {
23630
25121
  let e = true;
23631
25122
  try {
23632
- this[Pt](null, I.writeSync(this[m2], t, 0, t.length, this[ot])), e = false;
25123
+ this[Pt](null, I.writeSync(this[m3], t, 0, t.length, this[ot])), e = false;
23633
25124
  } finally {
23634
25125
  if (e)
23635
25126
  try {
@@ -25607,7 +27098,7 @@ var Dr = Symbol("onEntry");
25607
27098
  var _s = Symbol("checkFs");
25608
27099
  var Nr = Symbol("checkFs2");
25609
27100
  var Os = Symbol("isReusable");
25610
- var P = Symbol("makeFs");
27101
+ var P2 = Symbol("makeFs");
25611
27102
  var Ts = Symbol("file");
25612
27103
  var xs = Symbol("directory");
25613
27104
  var gi = Symbol("link");
@@ -25895,23 +27386,23 @@ var Xt = class extends rt {
25895
27386
  return;
25896
27387
  }
25897
27388
  if (h || this[Os](t, a))
25898
- return this[P](null, t, i);
27389
+ return this[P2](null, t, i);
25899
27390
  if (a.isDirectory()) {
25900
27391
  if (t.type === "Directory") {
25901
- let l = this.chmod && t.mode && (a.mode & 4095) !== t.mode, c = (d) => this[P](d ?? null, t, i);
27392
+ let l = this.chmod && t.mode && (a.mode & 4095) !== t.mode, c = (d) => this[P2](d ?? null, t, i);
25902
27393
  return l ? u3.chmod(String(t.absolute), Number(t.mode), c) : c();
25903
27394
  }
25904
27395
  if (t.absolute !== this.cwd)
25905
- return u3.rmdir(String(t.absolute), (l) => this[P](l ?? null, t, i));
27396
+ return u3.rmdir(String(t.absolute), (l) => this[P2](l ?? null, t, i));
25906
27397
  }
25907
27398
  if (t.absolute === this.cwd)
25908
- return this[P](null, t, i);
25909
- mo(String(t.absolute), (l) => this[P](l ?? null, t, i));
27399
+ return this[P2](null, t, i);
27400
+ mo(String(t.absolute), (l) => this[P2](l ?? null, t, i));
25910
27401
  });
25911
27402
  };
25912
27403
  this[Oe] ? n() : r();
25913
27404
  }
25914
- [P](t, e, i) {
27405
+ [P2](t, e, i) {
25915
27406
  if (t) {
25916
27407
  this[O](t, e), i();
25917
27408
  return;
@@ -25945,8 +27436,8 @@ var ye = (s) => {
25945
27436
  };
25946
27437
  var xe = class extends Xt {
25947
27438
  sync = true;
25948
- [P](t, e) {
25949
- return super[P](t, e, () => {});
27439
+ [P2](t, e) {
27440
+ return super[P2](t, e, () => {});
25950
27441
  }
25951
27442
  [_s](t) {
25952
27443
  if (!this[Oe]) {
@@ -25967,19 +27458,19 @@ var xe = class extends Xt {
25967
27458
  if (i && (this.keep || this.newer && i.mtime > (t.mtime ?? i.mtime)))
25968
27459
  return this[Ls](t);
25969
27460
  if (e || this[Os](t, i))
25970
- return this[P](null, t);
27461
+ return this[P2](null, t);
25971
27462
  if (i.isDirectory()) {
25972
27463
  if (t.type === "Directory") {
25973
27464
  let o = this.chmod && t.mode && (i.mode & 4095) !== t.mode, [h] = o ? ye(() => {
25974
27465
  u3.chmodSync(String(t.absolute), Number(t.mode));
25975
27466
  }) : [];
25976
- return this[P](h, t);
27467
+ return this[P2](h, t);
25977
27468
  }
25978
27469
  let [n] = ye(() => u3.rmdirSync(String(t.absolute)));
25979
- this[P](n, t);
27470
+ this[P2](n, t);
25980
27471
  }
25981
27472
  let [r] = t.absolute === this.cwd ? [] : ye(() => po(String(t.absolute)));
25982
- this[P](r, t);
27473
+ this[P2](r, t);
25983
27474
  }
25984
27475
  [Ts](t, e) {
25985
27476
  let i = typeof t.mode == "number" ? t.mode & 4095 : this.fmode, r = (h) => {
@@ -26427,8 +27918,8 @@ async function handleFlowsPull(ctx, opts, deps) {
26427
27918
  ctx.ui.error(validation.error);
26428
27919
  return { error: validation.error };
26429
27920
  }
26430
- const destAbs = resolve8(opts.out ?? join27(".qawolf", opts.env));
26431
- const assetsAbs = join27(dirname8(destAbs), "assets");
27921
+ const destAbs = resolve8(opts.out ?? join31(".qawolf", opts.env));
27922
+ const assetsAbs = join31(dirname8(destAbs), "assets");
26432
27923
  await resolvedDeps.fs.mkdir(assetsAbs, { recursive: true });
26433
27924
  const yes = opts.yes ?? false;
26434
27925
  let archive;
@@ -27303,15 +28794,15 @@ async function defaultRunWebFlowDeps(envDir = process.cwd(), signals) {
27303
28794
 
27304
28795
  // src/domains/runtimeEnv/prepareRunDir.ts
27305
28796
  import { createHash as createHash4 } from "node:crypto";
27306
- import { join as join35, resolve as resolve9 } from "node:path";
28797
+ import { join as join39, resolve as resolve9 } from "node:path";
27307
28798
 
27308
28799
  // src/domains/runtimeEnv/execSubpathImports.ts
27309
- import { join as join28 } from "node:path";
28800
+ import { join as join32 } from "node:path";
27310
28801
  var flowSubpathImports = { "#playwright": "playwright" };
27311
28802
  var packageJsonSchema = looseObject({ imports: record(string2(), unknown()).catch({}) }).catch({ imports: {} });
27312
28803
  async function writeExecSubpathImports(args) {
27313
28804
  const { execDir, fs } = args;
27314
- const pkgPath = join28(execDir, "package.json");
28805
+ const pkgPath = join32(execDir, "package.json");
27315
28806
  const pkg = packageJsonSchema.parse(await readPackageJson(pkgPath, fs));
27316
28807
  const merged = { ...pkg, imports: { ...pkg.imports, ...flowSubpathImports } };
27317
28808
  await fs.writeFile(pkgPath, JSON.stringify(merged, undefined, 2));
@@ -27333,10 +28824,10 @@ async function readPackageJson(pkgPath, fs) {
27333
28824
  }
27334
28825
 
27335
28826
  // src/domains/runtimeEnv/innerHop.ts
27336
- import { join as join30 } from "node:path";
28827
+ import { join as join34 } from "node:path";
27337
28828
 
27338
28829
  // src/domains/runtimeEnv/linkPinnedPackages.ts
27339
- import { dirname as dirname9, join as join29 } from "node:path";
28830
+ import { dirname as dirname9, join as join33 } from "node:path";
27340
28831
 
27341
28832
  // src/domains/runtimeEnv/symlinkDir.ts
27342
28833
  import { symlink } from "node:fs/promises";
@@ -27348,16 +28839,16 @@ async function createDirSymlink(source, target) {
27348
28839
  // src/domains/runtimeEnv/linkPinnedPackages.ts
27349
28840
  async function linkPinnedPackages(args) {
27350
28841
  const { depsRoot, nodeModulesDir, fs } = args;
27351
- const managedModules = join29(depsRoot, "node_modules");
28842
+ const managedModules = join33(depsRoot, "node_modules");
27352
28843
  await fs.mkdir(nodeModulesDir, { recursive: true });
27353
28844
  for (const { name } of pinnedPackages) {
27354
28845
  const segments = name.split("/");
27355
- const target = join29(nodeModulesDir, ...segments);
28846
+ const target = join33(nodeModulesDir, ...segments);
27356
28847
  if (segments.length > 1) {
27357
28848
  await fs.mkdir(dirname9(target), { recursive: true });
27358
28849
  }
27359
28850
  await fs.rm(target, { recursive: true, force: true });
27360
- await createDirSymlink(join29(managedModules, ...segments), target);
28851
+ await createDirSymlink(join33(managedModules, ...segments), target);
27361
28852
  }
27362
28853
  }
27363
28854
 
@@ -27366,18 +28857,18 @@ async function populateInnerHop(args) {
27366
28857
  const { depsRoot, execDir, fs } = args;
27367
28858
  await linkPinnedPackages({
27368
28859
  depsRoot,
27369
- nodeModulesDir: join30(execDir, "node_modules"),
28860
+ nodeModulesDir: join34(execDir, "node_modules"),
27370
28861
  fs
27371
28862
  });
27372
28863
  }
27373
28864
 
27374
28865
  // src/domains/runtimeEnv/outerHop.ts
27375
28866
  import { lstat as lstat2 } from "node:fs/promises";
27376
- import { dirname as dirname11, join as join32 } from "node:path";
28867
+ import { dirname as dirname11, join as join36 } from "node:path";
27377
28868
 
27378
28869
  // src/domains/runtimeEnv/carryOverPackages.ts
27379
28870
  import { lstat } from "node:fs/promises";
27380
- import { dirname as dirname10, join as join31 } from "node:path";
28871
+ import { dirname as dirname10, join as join35 } from "node:path";
27381
28872
 
27382
28873
  // src/domains/runtimeEnv/carryOverPlan.ts
27383
28874
  var executorPackageNames = new Set(pinnedPackages.map((p) => p.name));
@@ -27399,12 +28890,12 @@ async function carryOverPackages(args) {
27399
28890
  const linked = [];
27400
28891
  for (const name of names) {
27401
28892
  const segments = name.split("/");
27402
- const target = join31(nodeModulesDir, ...segments);
28893
+ const target = join35(nodeModulesDir, ...segments);
27403
28894
  if (await pathOccupied(target))
27404
28895
  continue;
27405
28896
  if (segments.length > 1)
27406
28897
  await fs.mkdir(dirname10(target), { recursive: true });
27407
- await createDirSymlink(join31(projectModulesDir, ...segments), target);
28898
+ await createDirSymlink(join35(projectModulesDir, ...segments), target);
27408
28899
  linked.push(name);
27409
28900
  }
27410
28901
  return linked;
@@ -27427,18 +28918,18 @@ async function listPackageNames(dir, fs) {
27427
28918
  const names = [];
27428
28919
  for (const entry of entries) {
27429
28920
  if (!entry.startsWith("@")) {
27430
- if (fs.existsSync(join31(dir, entry, "package.json")))
28921
+ if (fs.existsSync(join35(dir, entry, "package.json")))
27431
28922
  names.push(entry);
27432
28923
  continue;
27433
28924
  }
27434
28925
  let scoped;
27435
28926
  try {
27436
- scoped = await fs.readdir(join31(dir, entry));
28927
+ scoped = await fs.readdir(join35(dir, entry));
27437
28928
  } catch {
27438
28929
  continue;
27439
28930
  }
27440
28931
  for (const inner of scoped) {
27441
- if (fs.existsSync(join31(dir, entry, inner, "package.json"))) {
28932
+ if (fs.existsSync(join35(dir, entry, inner, "package.json"))) {
27442
28933
  names.push(`${entry}/${inner}`);
27443
28934
  }
27444
28935
  }
@@ -27456,7 +28947,7 @@ async function populateOuterHop(args) {
27456
28947
  const depNames = Object.keys(installableDeps);
27457
28948
  const { satisfying, rejected } = await findSatisfyingNodeModulesDir(projectDir, depNames, fs);
27458
28949
  if (satisfying !== undefined) {
27459
- await createDirSymlink(satisfying, join32(runDir, "node_modules"));
28950
+ await createDirSymlink(satisfying, join36(runDir, "node_modules"));
27460
28951
  return { mode: "symlink", nodeModulesDir: satisfying };
27461
28952
  }
27462
28953
  if (depNames.length === 0)
@@ -27465,12 +28956,12 @@ async function populateOuterHop(args) {
27465
28956
  await installOuterDeps(runDir, installableDeps, fs, args.install ?? spawnNpmInstall);
27466
28957
  await linkPinnedPackages({
27467
28958
  depsRoot: args.depsRoot,
27468
- nodeModulesDir: join32(runDir, "node_modules"),
28959
+ nodeModulesDir: join36(runDir, "node_modules"),
27469
28960
  fs
27470
28961
  });
27471
28962
  const carriedOver = await carryOverPackages({
27472
- projectModulesDir: join32(projectDir, "node_modules"),
27473
- nodeModulesDir: join32(runDir, "node_modules"),
28963
+ projectModulesDir: join36(projectDir, "node_modules"),
28964
+ nodeModulesDir: join36(runDir, "node_modules"),
27474
28965
  fs
27475
28966
  });
27476
28967
  return { mode: "install", depCount: depNames.length, rejected, carriedOver };
@@ -27479,10 +28970,10 @@ async function findSatisfyingNodeModulesDir(startDir, depNames, fs) {
27479
28970
  const rejected = [];
27480
28971
  let dir = startDir;
27481
28972
  while (true) {
27482
- const candidate = join32(dir, "node_modules");
28973
+ const candidate = join36(dir, "node_modules");
27483
28974
  const stats = await lstatSafe(candidate);
27484
28975
  if (stats !== undefined && stats.isDirectory() && !stats.isSymbolicLink()) {
27485
- const missing = depNames.filter((name) => !fs.existsSync(join32(candidate, name, "package.json")));
28976
+ const missing = depNames.filter((name) => !fs.existsSync(join36(candidate, name, "package.json")));
27486
28977
  if (missing.length === 0)
27487
28978
  return { satisfying: candidate, rejected };
27488
28979
  rejected.push({ dir: candidate, missing });
@@ -27503,7 +28994,7 @@ async function lstatSafe(path) {
27503
28994
  async function readInstallableDeps(projectDir, fs) {
27504
28995
  let content;
27505
28996
  try {
27506
- content = await fs.readFile(join32(projectDir, "package.json"));
28997
+ content = await fs.readFile(join36(projectDir, "package.json"));
27507
28998
  } catch {
27508
28999
  return {};
27509
29000
  }
@@ -27521,8 +29012,8 @@ async function readInstallableDeps(projectDir, fs) {
27521
29012
  return Object.fromEntries(Object.entries(rawDeps).filter(([name]) => !executorPackageNames2.has(name)).filter((entry) => typeof entry[1] === "string"));
27522
29013
  }
27523
29014
  async function installOuterDeps(runDir, deps, fs, install) {
27524
- await fs.writeFile(join32(runDir, "package.json"), JSON.stringify({ name: "qawolf-run", private: true, dependencies: deps }, undefined, 2));
27525
- await fs.writeFile(join32(runDir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
29015
+ await fs.writeFile(join36(runDir, "package.json"), JSON.stringify({ name: "qawolf-run", private: true, dependencies: deps }, undefined, 2));
29016
+ await fs.writeFile(join36(runDir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
27526
29017
  `);
27527
29018
  const result = await install(runDir);
27528
29019
  if (result.exitCode !== 0) {
@@ -27532,14 +29023,14 @@ async function installOuterDeps(runDir, deps, fs, install) {
27532
29023
 
27533
29024
  // src/domains/runtimeEnv/stageFlowFiles.ts
27534
29025
  import { createHash as createHash3 } from "node:crypto";
27535
- import { basename as basename6, dirname as dirname12, join as join34, sep as sep4 } from "node:path";
29026
+ import { basename as basename6, dirname as dirname12, join as join38, sep as sep4 } from "node:path";
27536
29027
 
27537
29028
  // src/shell/copyDir.ts
27538
29029
  import { cp, readdir as readdir2 } from "node:fs/promises";
27539
- import { basename as basename5, join as join33 } from "node:path";
29030
+ import { basename as basename5, join as join37 } from "node:path";
27540
29031
  async function copyDirExcluding(source, destination, excludedNames) {
27541
29032
  const entries = await readdir2(source, { withFileTypes: true });
27542
- await Promise.all(entries.filter((entry) => !excludedNames.has(entry.name)).map((entry) => cp(join33(source, entry.name), join33(destination, entry.name), {
29033
+ await Promise.all(entries.filter((entry) => !excludedNames.has(entry.name)).map((entry) => cp(join37(source, entry.name), join37(destination, entry.name), {
27543
29034
  recursive: true,
27544
29035
  filter: (path) => !excludedNames.has(basename5(path))
27545
29036
  })));
@@ -27556,14 +29047,14 @@ async function stageFlowFiles(args) {
27556
29047
  if (files.length > 1) {
27557
29048
  return Promise.all(files.map((f) => stageOneFile(f, execDir, fs)));
27558
29049
  }
27559
- await Promise.all(files.map((f) => fs.copyFile(f, join34(execDir, basename6(f)))));
27560
- return files.map((f) => join34(execDir, basename6(f)));
29050
+ await Promise.all(files.map((f) => fs.copyFile(f, join38(execDir, basename6(f)))));
29051
+ return files.map((f) => join38(execDir, basename6(f)));
27561
29052
  }
27562
29053
  async function stageOneFile(file, execDir, fs) {
27563
29054
  const dirHash = createHash3("sha256").update(dirname12(file)).digest("hex").slice(0, 8);
27564
- const subDir = join34(execDir, dirHash);
29055
+ const subDir = join38(execDir, dirHash);
27565
29056
  await fs.mkdir(subDir, { recursive: true });
27566
- const dest = join34(subDir, basename6(file));
29057
+ const dest = join38(subDir, basename6(file));
27567
29058
  await fs.copyFile(file, dest);
27568
29059
  return dest;
27569
29060
  }
@@ -27571,7 +29062,7 @@ function remapPath(file, projectDir, execDir) {
27571
29062
  if (file === projectDir)
27572
29063
  return execDir;
27573
29064
  if (file.startsWith(projectDir + sep4)) {
27574
- return join34(execDir, file.slice(projectDir.length + 1));
29065
+ return join38(execDir, file.slice(projectDir.length + 1));
27575
29066
  }
27576
29067
  return;
27577
29068
  }
@@ -27581,10 +29072,10 @@ async function prepareRunDir(args) {
27581
29072
  const { files, projectDir, depsRoot, runRoot } = args;
27582
29073
  const fs = args.fs ?? makeDefaultFs();
27583
29074
  const runId = buildRunId(projectDir, files);
27584
- const runDir = join35(runRoot, runId);
29075
+ const runDir = join39(runRoot, runId);
27585
29076
  await fs.rm(runDir, { recursive: true, force: true });
27586
29077
  await fs.mkdir(runDir, { recursive: true });
27587
- const execDir = join35(runDir, "exec");
29078
+ const execDir = join39(runDir, "exec");
27588
29079
  await fs.mkdir(execDir, { recursive: true });
27589
29080
  await populateInnerHop({ depsRoot, execDir, fs });
27590
29081
  const stagedFiles = await stageFlowFiles({ files, projectDir, execDir, fs });
@@ -27615,17 +29106,17 @@ function buildRunId(projectDir, files) {
27615
29106
  }
27616
29107
 
27617
29108
  // src/shell/testkit.ts
27618
- import { join as join36 } from "node:path";
29109
+ import { join as join40 } from "node:path";
27619
29110
  function notAvailableLocally(name) {
27620
29111
  throw new Error(runnerMessages.notAvailableLocally(name));
27621
29112
  }
27622
29113
  async function loadSdkDeps(cwd) {
27623
- const testkitDir = join36(cwd, "node_modules", "@qawolf", "testkit");
29114
+ const testkitDir = join40(cwd, "node_modules", "@qawolf", "testkit");
27624
29115
  try {
27625
29116
  const clientMod = await importFromPath(resolveFromEnvDir(cwd, "@qawolf/testkit/client"));
27626
29117
  if (typeof clientMod.createTestkitClient !== "function")
27627
29118
  throw new Error("createTestkitClient is not a function");
27628
- const scopeMod = await importFromPath(join36(testkitDir, "dist", "clientScope.js"));
29119
+ const scopeMod = await importFromPath(join40(testkitDir, "dist", "clientScope.js"));
27629
29120
  if (typeof scopeMod.configureTestkitClient !== "function")
27630
29121
  throw new Error("configureTestkitClient is not a function");
27631
29122
  return {
@@ -27683,9 +29174,9 @@ async function registerLazyEmailsClient(getInbox, cwd, deps) {
27683
29174
  }
27684
29175
 
27685
29176
  // src/commands/flows/envVarDeps.ts
27686
- import { join as join37 } from "node:path";
29177
+ import { join as join41 } from "node:path";
27687
29178
  function envFilePath(envDir) {
27688
- return join37(envDir, ".env");
29179
+ return join41(envDir, ".env");
27689
29180
  }
27690
29181
  async function readEnv(envDir, fs) {
27691
29182
  try {
@@ -27809,7 +29300,7 @@ async function selectRunByTag(args) {
27809
29300
  }
27810
29301
 
27811
29302
  // src/commands/flows/runStagedFlows.ts
27812
- import { join as join41 } from "node:path";
29303
+ import { join as join45 } from "node:path";
27813
29304
 
27814
29305
  // src/shell/appium/createAppiumServer.ts
27815
29306
  import { existsSync as existsSync2 } from "node:fs";
@@ -29115,14 +30606,14 @@ async function runWebFlow({
29115
30606
 
29116
30607
  // src/shell/embeddedWorkerCli.ts
29117
30608
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
29118
- import { basename as basename7, join as join38 } from "node:path";
30609
+ import { basename as basename7, join as join42 } from "node:path";
29119
30610
  var embeddedCliPathEnv = "QAWOLF_EMBEDDED_CLI_PATH";
29120
30611
  function extractEmbeddedWorkerCli() {
29121
30612
  const assetPath = process.env[embeddedCliPathEnv];
29122
30613
  if (assetPath === undefined || assetPath === "")
29123
30614
  return;
29124
- const dir = join38(getDataDir(), "worker");
29125
- const dest = join38(dir, basename7(assetPath));
30615
+ const dir = join42(getDataDir(), "worker");
30616
+ const dest = join42(dir, basename7(assetPath));
29126
30617
  if (existsSync3(dest))
29127
30618
  return dest;
29128
30619
  mkdirSync3(dir, { recursive: true });
@@ -29360,7 +30851,7 @@ ${styleText3("dim", "─".repeat(width))}
29360
30851
  };
29361
30852
  }
29362
30853
  // src/shell/reporter/createJUnitReporter.ts
29363
- import { join as join39 } from "node:path";
30854
+ import { join as join43 } from "node:path";
29364
30855
 
29365
30856
  // src/shell/reporter/junitXml.ts
29366
30857
  function escapeXml(str) {
@@ -29399,7 +30890,7 @@ ${suites}`;
29399
30890
 
29400
30891
  // src/shell/reporter/createJUnitReporter.ts
29401
30892
  function resolveJUnitOutputPath(junit, outputDir) {
29402
- return typeof junit === "string" && junit.trim() !== "" ? junit : join39(outputDir, "junit-report.xml");
30893
+ return typeof junit === "string" && junit.trim() !== "" ? junit : join43(outputDir, "junit-report.xml");
29403
30894
  }
29404
30895
  function createJUnitReporter(deps) {
29405
30896
  const flows = [];
@@ -29490,11 +30981,11 @@ function buildFlowsRunDeps(args) {
29490
30981
 
29491
30982
  // src/commands/flows/loadEnvFile.ts
29492
30983
  import { readFile } from "node:fs/promises";
29493
- import { join as join40 } from "node:path";
30984
+ import { join as join44 } from "node:path";
29494
30985
  async function loadEnvFile(envDir) {
29495
30986
  let content;
29496
30987
  try {
29497
- content = await readFile(join40(envDir, ".env"), "utf8");
30988
+ content = await readFile(join44(envDir, ".env"), "utf8");
29498
30989
  } catch (err) {
29499
30990
  if (isNoEntError(err))
29500
30991
  return;
@@ -29539,7 +31030,7 @@ async function runStagedFlows(args) {
29539
31030
  for (const candidate of staged.outerHop.rejected) {
29540
31031
  log.debug(runnerMessages.outerHopCandidateRejected(candidate.dir, candidate.missing));
29541
31032
  }
29542
- const ownModules = projectDir === undefined ? undefined : join41(projectDir, "node_modules");
31033
+ const ownModules = projectDir === undefined ? undefined : join45(projectDir, "node_modules");
29543
31034
  const own = staged.outerHop.rejected.find((c) => c.dir === ownModules);
29544
31035
  if (own !== undefined) {
29545
31036
  ctx.ui.warn(runnerMessages.outerHopFallbackNotice(own.dir, own.missing));
@@ -29574,7 +31065,7 @@ async function runStagedFlows(args) {
29574
31065
  }
29575
31066
 
29576
31067
  // src/commands/flows/runDefaults.ts
29577
- function makeDefaultDeps2(fs) {
31068
+ function makeDefaultDeps3(fs) {
29578
31069
  return {
29579
31070
  expandPatterns: (patterns, cwd, logger) => expandPatterns(patterns, cwd, logger, fs),
29580
31071
  resolveDepsRoot: (args) => resolveDepsRoot({ ...args, fs }),
@@ -29586,7 +31077,7 @@ function makeDefaultDeps2(fs) {
29586
31077
  };
29587
31078
  }
29588
31079
  async function handleFlowsRun(ctx, pattern, flags, deps, selectors = { tags: [] }, allEnvs = false) {
29589
- const resolvedDeps = deps ?? makeDefaultDeps2(ctx.fs);
31080
+ const resolvedDeps = deps ?? makeDefaultDeps3(ctx.fs);
29590
31081
  const cwd = process.cwd();
29591
31082
  const expandedFiles = await resolvedDeps.expandPatterns(buildPatternArgs(pattern), cwd, ctx.log("flows"));
29592
31083
  ctx.log("flows").debug(`discovered ${pluralize(expandedFiles.length, "flow")}`);
@@ -29635,7 +31126,7 @@ async function chooseEnv(ctx, files, allEnvs) {
29635
31126
  }
29636
31127
 
29637
31128
  // src/commands/flows/hybridRunDefaults.ts
29638
- import { join as join42, resolve as resolve10 } from "node:path";
31129
+ import { join as join46, resolve as resolve10 } from "node:path";
29639
31130
 
29640
31131
  // src/core/selectFlowFiles.ts
29641
31132
  function selectFlowFiles(args) {
@@ -29740,7 +31231,7 @@ async function handleHybridFlowsRun(ctx, pattern, flags, options = {}) {
29740
31231
  if (validation !== "ok") {
29741
31232
  return { error: validation.error, exitCode: 2 };
29742
31233
  }
29743
- const envDir = resolve10(join42(".qawolf", flags.env));
31234
+ const envDir = resolve10(join46(".qawolf", flags.env));
29744
31235
  const patternArgs = buildPatternArgs(pattern);
29745
31236
  const globFlows = () => resolvedDeps.expandPatterns(patternArgs, envDir, ctx.log("flows"));
29746
31237
  let files = await globFlows();
@@ -29936,12 +31427,12 @@ function registerFlowsCommand(program, signals, deps = { withResolvedEnv }) {
29936
31427
  }
29937
31428
 
29938
31429
  // src/domains/init/init.ts
29939
- import { dirname as dirname14, join as join44, relative as relative9 } from "node:path";
31430
+ import { dirname as dirname14, join as join48, relative as relative9 } from "node:path";
29940
31431
 
29941
31432
  // src/domains/init/ensurePackageJson.ts
29942
- import { join as join43 } from "node:path";
31433
+ import { join as join47 } from "node:path";
29943
31434
  async function ensurePackageJson(ctx, yes, deps) {
29944
- const pkgPath = join43(deps.cwd, "package.json");
31435
+ const pkgPath = join47(deps.cwd, "package.json");
29945
31436
  if (!await deps.fs.pathExists(pkgPath)) {
29946
31437
  const confirmed = await ctx.ui.confirm(initMessages.createPackageJsonPrompt, { yes });
29947
31438
  if (!confirmed.ok || !confirmed.value) {
@@ -30067,9 +31558,9 @@ function makeDefaultInitDeps() {
30067
31558
  async function handleInit(ctx, opts, deps = makeDefaultInitDeps()) {
30068
31559
  ctx.ui.gap();
30069
31560
  ctx.ui.intro(initMessages.title);
30070
- const configPath = join44(deps.cwd, "qawolf.config.ts");
30071
- const flowPath = join44(deps.cwd, "src", "flows", "example.flow.ts");
30072
- const gitignorePath = join44(deps.cwd, ".qawolf", ".gitignore");
31561
+ const configPath = join48(deps.cwd, "qawolf.config.ts");
31562
+ const flowPath = join48(deps.cwd, "src", "flows", "example.flow.ts");
31563
+ const gitignorePath = join48(deps.cwd, ".qawolf", ".gitignore");
30073
31564
  await writeWithPrompt(ctx, configPath, qawolfConfigTs, opts.yes, deps);
30074
31565
  await writeWithPrompt(ctx, flowPath, exampleFlowTs, opts.yes, deps);
30075
31566
  await writeWithPrompt(ctx, gitignorePath, qawolfGitignore, opts.yes, deps);
@@ -30107,7 +31598,7 @@ Examples:
30107
31598
  import { parseExecutionTarget as parseExecutionTarget4 } from "@qawolf/flow-targets";
30108
31599
 
30109
31600
  // src/domains/install/android/avd.ts
30110
- import { join as join45 } from "node:path";
31601
+ import { join as join49 } from "node:path";
30111
31602
  async function installAvds(ctx, specs, deps) {
30112
31603
  const sdkManagerPath = sdkManagerBin(deps.androidHome, deps.platform);
30113
31604
  const avdManagerPath = avdManagerBin(deps.androidHome, deps.platform);
@@ -30117,7 +31608,7 @@ async function installAvds(ctx, specs, deps) {
30117
31608
  if (versionResult.exitCode !== 0) {
30118
31609
  throw new Error(installMessages.android.sdkmanagerNotFound(sdkManagerPath));
30119
31610
  }
30120
- const licenseFile = join45(deps.androidHome, "licenses", "android-sdk-license");
31611
+ const licenseFile = join49(deps.androidHome, "licenses", "android-sdk-license");
30121
31612
  if (deps.checkExists(licenseFile)) {
30122
31613
  ctx.ui.info(installMessages.android.licensesAlreadyAccepted);
30123
31614
  } else {
@@ -30132,7 +31623,7 @@ async function installAvds(ctx, specs, deps) {
30132
31623
  for (const spec of specs) {
30133
31624
  if (installedImages.has(spec.systemImage))
30134
31625
  continue;
30135
- const systemImg = join45(deps.androidHome, ...spec.systemImage.split(";"), "system.img");
31626
+ const systemImg = join49(deps.androidHome, ...spec.systemImage.split(";"), "system.img");
30136
31627
  if (deps.checkExists(systemImg)) {
30137
31628
  ctx.ui.info(installMessages.android.systemImageAlreadyInstalled(spec.systemImage));
30138
31629
  installedImages.add(spec.systemImage);
@@ -30430,42 +31921,6 @@ Examples:
30430
31921
  });
30431
31922
  }
30432
31923
 
30433
- // src/core/publicApi/flagKind.ts
30434
- function flagKind(schema) {
30435
- if (schema.type === "string")
30436
- return { ok: true, kind: "string" };
30437
- if (schema.type === "number" || schema.type === "integer") {
30438
- return { ok: true, kind: "number" };
30439
- }
30440
- if (schema.type === "boolean")
30441
- return { ok: true, kind: "boolean" };
30442
- if (schema.type === "array") {
30443
- if (schema.items?.type !== "string") {
30444
- return {
30445
- ok: false,
30446
- reason: "only string arrays can be expressed as flags"
30447
- };
30448
- }
30449
- return { ok: true, kind: "string-array" };
30450
- }
30451
- if (schema.type === "object") {
30452
- if (schema.properties) {
30453
- return {
30454
- ok: false,
30455
- reason: "nested objects cannot be expressed as flags"
30456
- };
30457
- }
30458
- if (typeof schema.additionalProperties === "object" && schema.additionalProperties.type === "string") {
30459
- return { ok: true, kind: "key-value-record" };
30460
- }
30461
- return { ok: false, reason: "nested objects cannot be expressed as flags" };
30462
- }
30463
- return {
30464
- ok: false,
30465
- reason: `type ${schema.type ?? "unknown"} cannot be expressed as a flag`
30466
- };
30467
- }
30468
-
30469
31924
  // src/core/publicApi/flagNames.ts
30470
31925
  var kebabCase = (segment) => segment.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
30471
31926
  var capitalize = (segment) => `${segment.slice(0, 1).toUpperCase()}${segment.slice(1)}`;
@@ -30484,108 +31939,6 @@ function flagUsage(path, kind) {
30484
31939
  return `${name} <value>`;
30485
31940
  }
30486
31941
 
30487
- // src/core/publicApi/objectShape.ts
30488
- var asObjectShape = (schema) => schema.type === "object" && schema.properties ? { properties: schema.properties, required: new Set(schema.required) } : undefined;
30489
- function mergeIntersection(members) {
30490
- const shape = { properties: {}, required: new Set };
30491
- for (const member of members) {
30492
- const memberShape = asObjectShape(member);
30493
- if (!memberShape) {
30494
- return {
30495
- ok: false,
30496
- field: "",
30497
- reason: "intersection members must be object schemas"
30498
- };
30499
- }
30500
- for (const [field, fieldSchema] of Object.entries(memberShape.properties)) {
30501
- if (field in shape.properties) {
30502
- return {
30503
- ok: false,
30504
- field,
30505
- reason: "field appears in multiple intersection members"
30506
- };
30507
- }
30508
- shape.properties[field] = fieldSchema;
30509
- if (memberShape.required.has(field))
30510
- shape.required.add(field);
30511
- }
30512
- }
30513
- return { ok: true, shape };
30514
- }
30515
- function mergeUnion(branches) {
30516
- const shapes = [];
30517
- for (const branch of branches) {
30518
- const branchShape = asObjectShape(branch);
30519
- if (!branchShape) {
30520
- return {
30521
- ok: false,
30522
- field: "",
30523
- reason: "union branches must be object schemas"
30524
- };
30525
- }
30526
- shapes.push(branchShape);
30527
- }
30528
- const shape = { properties: {}, required: new Set };
30529
- for (const branchShape of shapes) {
30530
- for (const [field, fieldSchema] of Object.entries(branchShape.properties)) {
30531
- const existing = shape.properties[field];
30532
- if (!existing) {
30533
- shape.properties[field] = { ...fieldSchema };
30534
- continue;
30535
- }
30536
- const kinds = [flagKind(existing), flagKind(fieldSchema)];
30537
- if (kinds[0]?.ok && kinds[1]?.ok) {
30538
- if (kinds[0].kind !== kinds[1].kind) {
30539
- return {
30540
- ok: false,
30541
- field,
30542
- reason: "field has conflicting types across union branches"
30543
- };
30544
- }
30545
- } else if (JSON.stringify({ ...existing, description: undefined }) !== JSON.stringify({ ...fieldSchema, description: undefined })) {
30546
- return {
30547
- ok: false,
30548
- field,
30549
- reason: "field has conflicting object schemas across union branches"
30550
- };
30551
- }
30552
- if (!existing.description && fieldSchema.description) {
30553
- existing.description = fieldSchema.description;
30554
- }
30555
- }
30556
- }
30557
- const discriminator = Object.keys(shape.properties).find((field) => {
30558
- const values = shapes.map((branch) => branch.properties[field]?.const);
30559
- return values.every((value) => typeof value === "string") && new Set(values).size === shapes.length && shapes.every((branch) => branch.required.has(field));
30560
- });
30561
- for (const [field, fieldSchema] of Object.entries(shape.properties)) {
30562
- const occurrences = shapes.filter((branch) => (field in branch.properties));
30563
- if (occurrences.length === shapes.length && occurrences.every((branch) => branch.required.has(field))) {
30564
- shape.required.add(field);
30565
- }
30566
- if (field === discriminator) {
30567
- const values = shapes.map((branch) => branch.properties[field]?.const);
30568
- fieldSchema.description = `One of: ${values.join(", ")}`;
30569
- }
30570
- }
30571
- return { ok: true, shape };
30572
- }
30573
- function toObjectShape(schema) {
30574
- if (schema.allOf)
30575
- return mergeIntersection(schema.allOf);
30576
- const branches = schema.oneOf ?? schema.anyOf;
30577
- if (branches)
30578
- return mergeUnion(branches);
30579
- const shape = asObjectShape(schema);
30580
- if (shape)
30581
- return { ok: true, shape };
30582
- return {
30583
- ok: false,
30584
- field: "",
30585
- reason: "contract input must be an object schema"
30586
- };
30587
- }
30588
-
30589
31942
  // src/core/publicApi/flagSpecs.ts
30590
31943
  function describeFlag(schema) {
30591
31944
  const description = schema.description ?? "";
@@ -31039,7 +32392,7 @@ function describeFailure(failureReason) {
31039
32392
  }
31040
32393
 
31041
32394
  // src/shell/interactiveRunner/collectRunFiles.ts
31042
- import { join as join47 } from "node:path";
32395
+ import { join as join51 } from "node:path";
31043
32396
 
31044
32397
  // src/core/interactiveRunner/getImports.ts
31045
32398
  function getImports(options) {
@@ -31078,7 +32431,7 @@ function isPathAlias(importPath, paths) {
31078
32431
  }
31079
32432
 
31080
32433
  // src/core/interactiveRunner/resolveImportPath.ts
31081
- import { dirname as dirname15, join as join46, normalize as normalize4 } from "node:path/posix";
32434
+ import { dirname as dirname15, join as join50, normalize as normalize3 } from "node:path/posix";
31082
32435
 
31083
32436
  // src/core/interactiveRunner/tsconfigPaths.ts
31084
32437
  function isRecord4(value) {
@@ -31131,7 +32484,7 @@ function candidatesForExplicitExtension(options) {
31131
32484
  }
31132
32485
  function resolveImportPath(options) {
31133
32486
  const aliasTarget = resolvePathAlias(options.importPath, options.tsconfigPaths);
31134
- const resolvedPath = aliasTarget === undefined ? normalize4(join46(dirname15(options.importingFilePath), options.importPath)) : normalize4(aliasTarget);
32487
+ const resolvedPath = aliasTarget === undefined ? normalize3(join50(dirname15(options.importingFilePath), options.importPath)) : normalize3(aliasTarget);
31135
32488
  const hasExplicitExtension = supportedExtensions.some((extension) => options.importPath.endsWith(extension));
31136
32489
  const candidates = hasExplicitExtension ? candidatesForExplicitExtension({
31137
32490
  importPath: options.importPath,
@@ -31155,7 +32508,7 @@ function resolveImportPath(options) {
31155
32508
  var tsconfigPath = "tsconfig.json";
31156
32509
  async function collectRunFiles(options) {
31157
32510
  const held = await listShippablePaths(options);
31158
- const readFile = (path) => options.fs.readFile(join47(options.cwd, path));
32511
+ const readFile = (path) => options.fs.readFile(join51(options.cwd, path));
31159
32512
  const tsconfigContent = held.has(tsconfigPath) ? await readFile(tsconfigPath).catch(() => {
31160
32513
  return;
31161
32514
  }) : undefined;
@@ -31232,13 +32585,13 @@ async function walkImportGraph(options) {
31232
32585
  }
31233
32586
 
31234
32587
  // src/shell/interactiveRunner/makeRunnerId.ts
31235
- import { randomUUID } from "node:crypto";
32588
+ import { randomUUID as randomUUID2 } from "node:crypto";
31236
32589
  function makeRunnerId() {
31237
- return `cli-${randomUUID().replaceAll("-", "").slice(0, 12)}`;
32590
+ return `cli-${randomUUID2().replaceAll("-", "").slice(0, 12)}`;
31238
32591
  }
31239
32592
 
31240
32593
  // src/shell/interactiveRunner/runFilesManifest.ts
31241
- import { join as join48 } from "node:path";
32594
+ import { join as join52 } from "node:path";
31242
32595
  var manifestFileName = "runner-files.json";
31243
32596
  var manifestSchema2 = object({
31244
32597
  files: array(object({ contentHash: string2(), path: string2() })),
@@ -31246,8 +32599,8 @@ var manifestSchema2 = object({
31246
32599
  version: literal(1)
31247
32600
  });
31248
32601
  function makeRunFilesManifestStore(options) {
31249
- const directory = join48(options.cwd, qawolfDir);
31250
- const path = join48(directory, manifestFileName);
32602
+ const directory = join52(options.cwd, qawolfDir);
32603
+ const path = join52(directory, manifestFileName);
31251
32604
  let pendingWrites = 0;
31252
32605
  return {
31253
32606
  async read() {
@@ -31277,7 +32630,7 @@ function parseJson(text) {
31277
32630
  }
31278
32631
 
31279
32632
  // src/shell/interactiveRunner/runnerStore.ts
31280
- import { join as join49 } from "node:path";
32633
+ import { join as join53 } from "node:path";
31281
32634
  var storeFileName = "runner.json";
31282
32635
  var runnersDirName = "runners";
31283
32636
  var storedRunnerSchema = object({
@@ -31296,11 +32649,11 @@ function parseJson2(text) {
31296
32649
  }
31297
32650
  var pendingWrites = 0;
31298
32651
  function makeRunnerStore(options) {
31299
- const directory = join49(options.cwd, qawolfDir);
31300
- const path = join49(directory, storeFileName);
31301
- const runnersDir = join49(directory, runnersDirName);
32652
+ const directory = join53(options.cwd, qawolfDir);
32653
+ const path = join53(directory, storeFileName);
32654
+ const runnersDir = join53(directory, runnersDirName);
31302
32655
  const nextPendingPath = (target) => `${target}.${process.pid}.${++pendingWrites}.tmp`;
31303
- const runnerPath = (runnerId) => join49(runnersDir, `${encodeURIComponent(runnerId)}.json`);
32656
+ const runnerPath = (runnerId) => join53(runnersDir, `${encodeURIComponent(runnerId)}.json`);
31304
32657
  const writeAtomically = async (target, contents) => {
31305
32658
  const pendingPath = nextPendingPath(target);
31306
32659
  await options.fs.writeFile(pendingPath, `${JSON.stringify(contents, undefined, 2)}
@@ -31337,7 +32690,7 @@ function makeRunnerStore(options) {
31337
32690
  readDefaultRunnerId,
31338
32691
  async readRunners() {
31339
32692
  const runners = await Promise.all((await readRunnerFileNames()).map(async (name) => {
31340
- const contents = await options.fs.readFile(join49(runnersDir, name)).catch(() => {
32693
+ const contents = await options.fs.readFile(join53(runnersDir, name)).catch(() => {
31341
32694
  return;
31342
32695
  });
31343
32696
  if (contents === undefined)
@@ -31356,7 +32709,7 @@ function makeRunnerStore(options) {
31356
32709
  await Promise.all(runnerIds.map((runnerId) => options.fs.rm(runnerPath(runnerId), { force: true })));
31357
32710
  const names = await readRunnerFileNames();
31358
32711
  await Promise.all(names.map(async (name) => {
31359
- const runnerFile = join49(runnersDir, name);
32712
+ const runnerFile = join53(runnersDir, name);
31360
32713
  const contents = await options.fs.readFile(runnerFile).catch(() => {
31361
32714
  return;
31362
32715
  });
@@ -31440,7 +32793,7 @@ function registerRunnerHighlightSelectorCommand(runner, signals) {
31440
32793
  }
31441
32794
 
31442
32795
  // src/domains/interactiveRunner/importPackage.ts
31443
- import { join as join50 } from "node:path";
32796
+ import { join as join54 } from "node:path";
31444
32797
 
31445
32798
  // src/core/interactiveRunner/npmDependencies.ts
31446
32799
  var sectionsInPrecedenceOrder = ["dependencies", "devDependencies"];
@@ -31481,7 +32834,7 @@ function readNpmDependencies(packageJsonContent) {
31481
32834
  // src/domains/interactiveRunner/importPackage.ts
31482
32835
  var defaultPackageVersion = "latest";
31483
32836
  async function handleRunnerImportPackage(ctx, options, deps) {
31484
- const content = await deps.readFile(join50(deps.cwd, runPackageJsonPath)).catch(() => {
32837
+ const content = await deps.readFile(join54(deps.cwd, runPackageJsonPath)).catch(() => {
31485
32838
  return;
31486
32839
  });
31487
32840
  if (content === undefined) {
@@ -33205,4 +34558,4 @@ createProgram({ signals }).parseAsync().catch(() => {
33205
34558
  process.exitCode = 1;
33206
34559
  }).finally(() => flushAndExit(typeof process.exitCode === "number" ? process.exitCode : 0));
33207
34560
 
33208
- //# debugId=599EFC31E23F426D64756E2164756E21
34561
+ //# debugId=DA8D4A9E373CFE5C64756E2164756E21