screenci 0.0.79 → 0.0.81

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.
Files changed (40) hide show
  1. package/README.md +12 -12
  2. package/dist/cli.d.ts +7 -21
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +23 -359
  5. package/dist/cli.js.map +1 -1
  6. package/dist/docs/video-sources/installation.screenci.js +2 -7
  7. package/dist/docs/video-sources/installation.screenci.js.map +1 -1
  8. package/dist/docs/video-sources/landing.screenci.js +1 -6
  9. package/dist/docs/video-sources/landing.screenci.js.map +1 -1
  10. package/dist/docs/video-sources/speaking-style.screenci.js +8 -35
  11. package/dist/docs/video-sources/speaking-style.screenci.js.map +1 -1
  12. package/dist/docs/video-sources/vertical-video.screenci.js +8 -46
  13. package/dist/docs/video-sources/vertical-video.screenci.js.map +1 -1
  14. package/dist/src/events.d.ts +3 -3
  15. package/dist/src/events.d.ts.map +1 -1
  16. package/dist/src/init.d.ts +10 -10
  17. package/dist/src/init.d.ts.map +1 -1
  18. package/dist/src/init.js +74 -57
  19. package/dist/src/init.js.map +1 -1
  20. package/dist/src/linkSession.d.ts +25 -28
  21. package/dist/src/linkSession.d.ts.map +1 -1
  22. package/dist/src/linkSession.js +74 -58
  23. package/dist/src/linkSession.js.map +1 -1
  24. package/dist/src/localize.d.ts +3 -2
  25. package/dist/src/localize.d.ts.map +1 -1
  26. package/dist/src/localize.js +3 -3
  27. package/dist/src/localize.js.map +1 -1
  28. package/dist/src/video.d.ts +7 -9
  29. package/dist/src/video.d.ts.map +1 -1
  30. package/dist/src/video.js +5 -7
  31. package/dist/src/video.js.map +1 -1
  32. package/dist/tsconfig.tsbuildinfo +1 -1
  33. package/package.json +1 -1
  34. package/skills/screenci/SKILL.md +32 -41
  35. package/skills/screenci/references/init.md +7 -7
  36. package/skills/screenci/references/record.md +8 -7
  37. package/dist/src/openBrowser.d.ts +0 -34
  38. package/dist/src/openBrowser.d.ts.map +0 -1
  39. package/dist/src/openBrowser.js +0 -62
  40. package/dist/src/openBrowser.js.map +0 -1
package/README.md CHANGED
@@ -71,18 +71,18 @@ but remap its rendered duration.
71
71
 
72
72
  ## Authoring helpers
73
73
 
74
- | Export | What it does |
75
- | ---------------- | ------------------------------------------------------------ |
76
- | `defineConfig` | Wraps Playwright config with ScreenCI defaults |
77
- | `video` | Declares a video recording test |
78
- | `video.localize` | Declares localized narration / text and records per language |
79
- | `hide` | Cuts setup or cleanup out of the visible recording |
80
- | `autoZoom` | Smooth camera follow for an interaction block |
81
- | `zoomTo` | Manual camera framing for a locator or point |
82
- | `resetZoom` | Returns from manual framing to the full viewport |
83
- | `createOverlays` | Inserts timed media overlays into the recording timeline |
84
- | `voices` | Available voice constants such as `voices.Ava` |
85
- | `modelTypes` | Narration model constants |
74
+ | Export | What it does |
75
+ | ----------------- | ------------------------------------------------------------ |
76
+ | `defineConfig` | Wraps Playwright config with ScreenCI defaults |
77
+ | `video` | Declares a video recording test |
78
+ | `video.narration` | Declares narration cues (per language or shared) for a video |
79
+ | `hide` | Cuts setup or cleanup out of the visible recording |
80
+ | `autoZoom` | Smooth camera follow for an interaction block |
81
+ | `zoomTo` | Manual camera framing for a locator or point |
82
+ | `resetZoom` | Returns from manual framing to the full viewport |
83
+ | `createOverlays` | Inserts timed media overlays into the recording timeline |
84
+ | `voices` | Available voice constants such as `voices.Ava` |
85
+ | `modelTypes` | Narration model constants |
86
86
 
87
87
  See the [docs](https://screenci.com/docs) for configuration, narration,
88
88
  camera, and CI setup.
package/dist/cli.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { RecordingCustomVoiceRef, RecordingData } from './src/events.js';
2
2
  import type { VoiceKey } from './src/voices.js';
3
- import type { RecordUploadPolicy } from './src/types.js';
3
+ import type { RecordUploadPolicy, ScreenCIConfig } from './src/types.js';
4
4
  import { getCliLinkSessionApiUrl, getDevBackendUrl, getDevFrontendUrl } from './src/linkSession.js';
5
5
  export { getCliLinkSessionApiUrl, getDevBackendUrl, getDevFrontendUrl };
6
6
  type PlaywrightListReportSuite = {
@@ -51,9 +51,6 @@ export type StudioUploadNotice = {
51
51
  };
52
52
  export declare function formatStudioUrl(appUrl: string, projectId: string, videoId: string): string;
53
53
  type OrgPlan = 'free' | 'starter' | 'business';
54
- export declare class SignInTimeoutError extends Error {
55
- constructor(message: string);
56
- }
57
54
  export declare function withUploadRetry<T>(fn: () => Promise<T>, signal: AbortSignal | undefined, maxAttempts?: number): Promise<T>;
58
55
  export declare function attachUploadAbortStdinListener(input: Pick<NodeJS.ReadStream, 'on' | 'off' | 'pause'>, onAbort: (signal: NodeJS.Signals) => void): () => void;
59
56
  export declare function clearRecordingDirectories(dir: string): void;
@@ -131,28 +128,17 @@ export declare function extractConfigStringLiteral(configSource: string, propert
131
128
  export declare function extractRecordUploadPolicyLiteral(configSource: string): RecordUploadPolicy | undefined;
132
129
  export declare function extractMockRecordLiteral(configSource: string): boolean | undefined;
133
130
  export declare function getConfigModuleSpecifier(resolvedConfigPath: string): string;
131
+ export declare function requireScreenCISecret(configPath?: string): Promise<{
132
+ resolvedConfigPath: string;
133
+ screenciConfig: ScreenCIConfig;
134
+ secret: string;
135
+ apiUrl: string;
136
+ }>;
134
137
  export declare function extractGrep(args: string[]): string | undefined;
135
- export declare function ensureScreenciSecret(resolvedConfigPath?: string, opts?: {
136
- interactive?: boolean;
137
- pollAuth?: boolean;
138
- noPollAuth?: boolean;
139
- }): Promise<string | undefined>;
140
- /**
141
- * `screenci login`: get a sign-in link in front of a human as early as
142
- * possible. It reuses the link `init` (or a prior run) persisted, or creates a
143
- * fresh one, tries to open it in the browser best-effort, and always prints it
144
- * as a fallback. It does NOT wait for sign-in to finish: the agent can run this,
145
- * keep authoring and `test`ing, then `record` (which blocks for sign-in) once
146
- * the script is green. If sign-in already completed, it saves the secret and
147
- * exits.
148
- */
149
- export declare function runLogin(configPath?: string): Promise<void>;
150
138
  export declare function main(): Promise<void>;
151
139
  export declare function parseRecordCliArgs(args: string[]): {
152
140
  configPath: string | undefined;
153
141
  verbose: boolean;
154
- pollAuth: boolean;
155
- noPollAuth: boolean;
156
142
  remote: boolean;
157
143
  languages: string | undefined;
158
144
  otherArgs: string[];
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EACV,uBAAuB,EACvB,aAAa,EAEd,MAAM,iBAAiB,CAAA;AAUxB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,KAAK,EAAE,kBAAkB,EAAkB,MAAM,gBAAgB,CAAA;AAKxE,OAAO,EAGL,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,EAOlB,MAAM,sBAAsB,CAAA;AAW7B,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,CAAA;AAyBvE,KAAK,yBAAyB,GAAG;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAChC,MAAM,CAAC,EAAE,yBAAyB,EAAE,CAAA;CACrC,CAAA;AAUD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,MAAM,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAmB,EAC5C,KAAK,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAkB,GACzC,OAAO,CAIT;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,SAAS,yBAAyB,EAAE,GAC3C,MAAM,EAAE,CAiBV;AA0ND,6EAA6E;AAC7E,KAAK,eAAe,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAA;AAY7D,KAAK,mBAAmB,GAAG;IACzB,IAAI,EAAE,eAAe,CAAA;IACrB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IAIpB,YAAY,CAAC,EAAE,OAAO,CAAA;IAMtB,YAAY,CAAC,EAAE,OAAO,CAAA;IAItB,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B,CAAA;AASD,MAAM,MAAM,gBAAgB,GAAG;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,CAAA;AAEjE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,MAAM,EAAE,gBAAgB,CAAA;CACzB,CAAA;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,MAAM,CAIR;AAED,KAAK,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,CAAA;AAuD9C,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAsBD,wBAAsB,eAAe,CAAC,CAAC,EACrC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,WAAW,SAAI,GACd,OAAO,CAAC,CAAC,CAAC,CAWZ;AA0XD,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,GAAG,KAAK,GAAG,OAAO,CAAC,EACtD,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,KAAK,IAAI,GACxC,MAAM,IAAI,CAiBZ;AAwOD,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAW3D;AA4MD,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,aAAa,EACnB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAgNhC;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE,QAAQ,GAAG,uBAAuB,EACzC,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,QAAQ,GAAG,uBAAuB,CAYpC;AAED,wBAAgB,oCAAoC,CAClD,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,mBAAmB,EAAE,GAC5B,aAAa,CAmGf;AAQD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAWhE;AAED;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAC/C,YAAY,EAAE,MAAM,GACnB,OAAO,CAUT;AAED,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,MAAM,CAeR;AAKD,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,MAAM,CASR;AAED,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,IAAI,CAcN;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAM1D;AAID,KAAK,kBAAkB,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAAE,QAAQ,EAAE,kBAAkB,EAAE,CAAA;CAAE,CAAA;AAEtE;;;;;;;;;;GAUG;AACH,wBAAsB,0BAA0B,CAC9C,MAAM,EAAE,mBAAmB,EAAE,EAC7B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,WAAW,EACnB,gBAAgB,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GACrD,OAAO,CAAC,mBAAmB,EAAE,CAAC,CA0DhC;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,mBAAmB,EAAE,GAChC,MAAM,CASR;AAiID,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,aAAa,CAAC,EAAE,MAAM,EACtB,OAAO,UAAQ,GACd,OAAO,CAAC;IACT,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,WAAW,EAAE,OAAO,CAAA;IACpB,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,mBAAmB,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAClE,aAAa,EAAE,kBAAkB,EAAE,CAAA;IACnC,0BAA0B,EAAE,MAAM,EAAE,CAAA;IACpC,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,IAAI,EAAE,OAAO,GAAG,IAAI,CAAA;CACrB,CAAC,CAkID;AAgLD,wBAAsB,4BAA4B,CAChD,kBAAkB,EAAE,MAAM,GACzB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAU7B;AAWD,wBAAgB,0BAA0B,CACxC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,aAAa,GAAG,SAAS,GAClC,MAAM,GAAG,SAAS,CAepB;AAED,wBAAgB,gCAAgC,CAC9C,YAAY,EAAE,MAAM,GACnB,kBAAkB,GAAG,SAAS,CAmBhC;AAED,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,MAAM,GACnB,OAAO,GAAG,SAAS,CAQrB;AAkCD,wBAAgB,wBAAwB,CAAC,kBAAkB,EAAE,MAAM,GAAG,MAAM,CAS3E;AA8GD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAY9D;AAyND,wBAAsB,oBAAoB,CACxC,kBAAkB,CAAC,EAAE,MAAM,EAC3B,IAAI,GAAE;IACJ,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,UAAU,CAAC,EAAE,OAAO,CAAA;CAChB,GACL,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAoJ7B;AAED;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA+EjE;AA+LD,wBAAsB,IAAI,kBAmQzB;AAQD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;IAClD,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;IACjB,UAAU,EAAE,OAAO,CAAA;IACnB,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,SAAS,EAAE,MAAM,EAAE,CAAA;CACpB,CAwDA;AAkUD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAchD"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -13,9 +13,8 @@ import { determinePackageManager, parsePackageManager, runInit, } from './src/in
13
13
  import { SCREENCI_DISABLE_RECORDING_TIMINGS_ENV, SCREENCI_MOCK_RECORD_ENV, SCREENCI_LANGUAGES_ENV, SCREENCI_VALUES_OVERRIDES_ENV, SCREENCI_RECORD_OPTIONS_ENV, isUploadExistingEnabled, } from './src/runtimeMode.js';
14
14
  import { DEFAULT_RECORD_UPLOAD_POLICY } from './src/defaults.js';
15
15
  import { findDuplicateTitles, formatDuplicateTitlesMessage, } from './src/titleValidation.js';
16
- import { createLinkSessionSpec, deletePersistedLinkSessionSpec, getCliLinkSessionApiUrl, getDevBackendUrl, getDevFrontendUrl, getLinkSessionFilePath, getScreenCIEnvironment, isStoredLinkSessionReusable, readPersistedLinkSessionSpec, SCREENCI_LINK_SESSION_FILE, writePersistedLinkSessionSpec, } from './src/linkSession.js';
16
+ import { getCliLinkSessionApiUrl, getDevBackendUrl, getDevFrontendUrl, getScreenCISecretsUrl, } from './src/linkSession.js';
17
17
  import { OVERLAY_CACHE_DIR_NAME } from './src/htmlRasterizer.js';
18
- import { openUrlInBrowser } from './src/openBrowser.js';
19
18
  import { maybeExtractVoiceSampleAudio } from './src/voiceSampleAudio.js';
20
19
  // Re-export the environment-aware URL helpers so existing importers (and tests)
21
20
  // can keep importing them from the CLI entrypoint.
@@ -25,20 +24,6 @@ const SCREENCI_RECORD_DOCS_URL = 'https://screenci.com/docs/reference/cli/#scree
25
24
  // Records the recordId of the most recent `screenci record` upload so
26
25
  // `screenci info` can report exactly the run that was just made.
27
26
  const SCREENCI_LAST_RECORD_FILE = 'last-record.json';
28
- const SCREENCI_LINK_SESSION_POLL_INTERVAL_MS = 2_000;
29
- // `record --poll` keeps a non-interactive session (an agent or CI) waiting for
30
- // sign-in. We poll on a slower cadence than the interactive loop so a long wait
31
- // for a human to click the link does not hammer the backend.
32
- const SCREENCI_LINK_SESSION_POLL_FLAG_INTERVAL_MS = 5_000;
33
- // A waiting `record` does not wait forever: after this long without a completed
34
- // sign-in we stop polling so a non-interactive agent run does not hang. The link
35
- // stays valid, so the command can be rerun. The default can be overridden with
36
- // SCREENCI_POLL_AUTH_TIMEOUT_MS (milliseconds).
37
- const SCREENCI_LINK_SESSION_POLL_FLAG_TIMEOUT_MS = 5 * 60 * 1_000;
38
- // An interactive `record` (a human at a terminal) waits longer before giving up,
39
- // since a person may take a while to open the link and finish signing in. Also
40
- // overridable with SCREENCI_POLL_AUTH_TIMEOUT_MS.
41
- const SCREENCI_LINK_SESSION_INTERACTIVE_TIMEOUT_MS = 15 * 60 * 1_000;
42
27
  const require = createRequire(import.meta.url);
43
28
  /**
44
29
  * Reports whether the current session can complete an interactive browser
@@ -264,17 +249,6 @@ class RecordFailureHintError extends Error {
264
249
  this.cause = cause;
265
250
  }
266
251
  }
267
- // Thrown when we wait for a browser sign-in and the deadline passes before it
268
- // completes. The link stays valid, so the message tells the user to sign in and
269
- // rerun. A timed-out sign-in means recording did not happen, so the caller maps
270
- // this to a non-zero exit (it is a failure, not the clean print-and-exit handoff
271
- // used by CI).
272
- export class SignInTimeoutError extends Error {
273
- constructor(message) {
274
- super(message);
275
- this.name = 'SignInTimeoutError';
276
- }
277
- }
278
252
  function isUploadCancelledError(err) {
279
253
  return (err instanceof UploadCancelledError ||
280
254
  (err instanceof Error &&
@@ -771,8 +745,6 @@ function forwardChildSignals(child, activityLabel, options = {}) {
771
745
  export function clearRecordingDirectories(dir) {
772
746
  mkdirSync(dir, { recursive: true });
773
747
  for (const entry of readdirSync(dir)) {
774
- if (entry === SCREENCI_LINK_SESSION_FILE)
775
- continue;
776
748
  // Preserve the cross-run overlay cache: it lives as a sibling of the
777
749
  // per-recording directories so unchanged overlays are served byte for byte
778
750
  // from a previous run. Wiping it would re-render and re-encode every overlay
@@ -1575,9 +1547,6 @@ async function countCompletedRecordings(screenciDir) {
1575
1547
  }
1576
1548
  return entries.filter((entry) => existsSync(resolve(screenciDir, entry, 'data.json'))).length;
1577
1549
  }
1578
- function getScreenCISecretsUrl() {
1579
- return `${getDevFrontendUrl()}/secrets`;
1580
- }
1581
1550
  async function writeGitHubProjectOutput(projectUrl) {
1582
1551
  const githubOutput = process.env.GITHUB_OUTPUT;
1583
1552
  if (!githubOutput)
@@ -1787,34 +1756,15 @@ async function loadRecordConfigWithoutPlaywrightCollision(resolvedConfigPath) {
1787
1756
  throw err;
1788
1757
  }
1789
1758
  }
1790
- async function requireScreenCISecret(configPath, opts = {}) {
1759
+ export async function requireScreenCISecret(configPath) {
1791
1760
  const { resolvedConfigPath, screenciConfig } = await loadScreenCIConfigAndEnv(configPath);
1792
- let secret;
1793
- try {
1794
- secret =
1795
- process.env.SCREENCI_SECRET ??
1796
- (await ensureScreenciSecret(resolvedConfigPath, opts));
1797
- }
1798
- catch (err) {
1799
- // A waited-out sign-in means recording did not happen: print the (already
1800
- // formatted) message and exit non-zero so the failure is loud, instead of
1801
- // looking like a successful run.
1802
- if (err instanceof SignInTimeoutError) {
1803
- logger.error(err.message);
1804
- process.exit(1);
1805
- }
1806
- throw err;
1807
- }
1761
+ const secret = process.env.SCREENCI_SECRET;
1808
1762
  if (!secret) {
1809
- // In a non-interactive session ensureScreenciSecret already printed the
1810
- // sign-in link and the next step, so we exit without repeating guidance.
1811
- // A pending sign-in is an expected handoff (surface the link, then rerun),
1812
- // not a failure, so exit cleanly (0) to avoid flagging the run as red.
1813
- if (opts.interactive === false) {
1814
- process.exit(0);
1815
- }
1763
+ // No browser sign-in flow: the secret comes from an init one-time token or
1764
+ // from the secrets page. Guide the user to either path and exit non-zero.
1816
1765
  const envFilePath = await resolveProjectEnvFilePath(resolvedConfigPath);
1817
- logger.error(`No SCREENCI_SECRET configured. Rerun ${pc.cyan(getSuggestedScreenciCommand('record'))} or add SCREENCI_SECRET manually to ${envFilePath} by following the guide at ${pc.cyan(SCREENCI_RECORD_DOCS_URL)}.`);
1766
+ logger.error(`No SCREENCI_SECRET configured. Copy your secret from ${pc.cyan(getScreenCISecretsUrl())} into ${envFilePath}, or re-run init with a one-time setup token from ${pc.cyan(getScreenCISecretsUrl())}.`);
1767
+ logScreenCISecretGuide();
1818
1768
  process.exit(1);
1819
1769
  }
1820
1770
  return {
@@ -1825,9 +1775,7 @@ async function requireScreenCISecret(configPath, opts = {}) {
1825
1775
  };
1826
1776
  }
1827
1777
  async function updateVideoVisibility(videoId, isPublic, configPath) {
1828
- const { secret, apiUrl } = await requireScreenCISecret(configPath, {
1829
- interactive: detectInteractiveSession(),
1830
- });
1778
+ const { secret, apiUrl } = await requireScreenCISecret(configPath);
1831
1779
  const method = isPublic ? 'PUT' : 'DELETE';
1832
1780
  const res = await fetch(`${apiUrl}/cli/public-video/${videoId}`, {
1833
1781
  method,
@@ -1864,7 +1812,7 @@ export function extractGrep(args) {
1864
1812
  // token stored for the project. An optional `--grep` records only matching
1865
1813
  // videos/screenshots.
1866
1814
  async function triggerRemoteRun(configPath, grep, languages) {
1867
- const { screenciConfig, secret, apiUrl } = await requireScreenCISecret(configPath, { interactive: detectInteractiveSession() });
1815
+ const { screenciConfig, secret, apiUrl } = await requireScreenCISecret(configPath);
1868
1816
  const res = await fetch(`${apiUrl}/cli/trigger-run`, {
1869
1817
  method: 'POST',
1870
1818
  headers: {
@@ -1926,9 +1874,7 @@ async function readLastRecordId(screenciDir) {
1926
1874
  // URLs. Without a local run, only the project-wide listing with `static` URLs is
1927
1875
  // returned. The server does the merge; the CLI just passes the recordId.
1928
1876
  async function printInfo(configPath) {
1929
- const { resolvedConfigPath, screenciConfig, secret, apiUrl } = await requireScreenCISecret(configPath, {
1930
- interactive: detectInteractiveSession(),
1931
- });
1877
+ const { resolvedConfigPath, screenciConfig, secret, apiUrl } = await requireScreenCISecret(configPath);
1932
1878
  const screenciDir = resolve(dirname(resolvedConfigPath), '.screenci');
1933
1879
  const recordId = await readLastRecordId(screenciDir);
1934
1880
  const url = new URL(`${apiUrl}/cli/info`);
@@ -1945,268 +1891,6 @@ async function printInfo(configPath) {
1945
1891
  const data = await res.json();
1946
1892
  process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
1947
1893
  }
1948
- async function persistScreenCISecret(envFilePath, secret) {
1949
- const nextLine = `SCREENCI_SECRET=${secret}`;
1950
- try {
1951
- const existing = await readFile(envFilePath, 'utf-8');
1952
- const lines = existing === '' ? [] : existing.split(/\r?\n/);
1953
- const firstSecretIndex = lines.findIndex((line) => line.startsWith('SCREENCI_SECRET='));
1954
- const linesWithoutSecret = lines.filter((line) => !line.startsWith('SCREENCI_SECRET='));
1955
- const finalLines = firstSecretIndex >= 0
1956
- ? [
1957
- ...linesWithoutSecret.slice(0, firstSecretIndex),
1958
- nextLine,
1959
- ...linesWithoutSecret.slice(firstSecretIndex),
1960
- ]
1961
- : [...linesWithoutSecret, nextLine];
1962
- let nextContent = finalLines.join('\n');
1963
- if (!nextContent.endsWith('\n'))
1964
- nextContent += '\n';
1965
- await writeFile(envFilePath, nextContent);
1966
- return;
1967
- }
1968
- catch (err) {
1969
- if (!isMissingFileError(err))
1970
- throw err;
1971
- }
1972
- await writeFile(envFilePath, `${nextLine}\n`);
1973
- }
1974
- async function pollLinkSessionOnce(spec) {
1975
- const response = await fetch(spec.pollUrl);
1976
- const body = (await response.json());
1977
- const status = body.status ?? 'invalid';
1978
- if (status === 'completed' && body.secret) {
1979
- return { status, secret: body.secret };
1980
- }
1981
- if (status === 'pending' && new Date().toISOString() >= spec.expiresAt) {
1982
- return { status: 'expired' };
1983
- }
1984
- return { status };
1985
- }
1986
- function getAuthPollTimeoutMs(defaultMs) {
1987
- const raw = process.env.SCREENCI_POLL_AUTH_TIMEOUT_MS;
1988
- if (raw === undefined)
1989
- return defaultMs;
1990
- const parsed = Number(raw);
1991
- return Number.isFinite(parsed) && parsed >= 0 ? parsed : defaultMs;
1992
- }
1993
- async function pollLinkSession(spec, pollIntervalMs = SCREENCI_LINK_SESSION_POLL_INTERVAL_MS, deadlineEpochMs) {
1994
- for (;;) {
1995
- const result = await pollLinkSessionOnce(spec);
1996
- if (result.status === 'completed' && result.secret) {
1997
- return result;
1998
- }
1999
- if (result.status === 'expired' ||
2000
- result.status === 'consumed' ||
2001
- result.status === 'invalid') {
2002
- return result;
2003
- }
2004
- // Stop before sleeping again once the optional deadline has passed so
2005
- // `--poll-auth` cannot block forever waiting for a human to sign in.
2006
- if (deadlineEpochMs !== undefined && Date.now() >= deadlineEpochMs) {
2007
- return { status: 'timed-out' };
2008
- }
2009
- await new Promise((resolveDelay) => setTimeout(resolveDelay, pollIntervalMs));
2010
- }
2011
- }
2012
- export async function ensureScreenciSecret(resolvedConfigPath, opts = {}) {
2013
- const interactive = opts.interactive ?? true;
2014
- const pollAuth = opts.pollAuth ?? false;
2015
- const noPollAuth = opts.noPollAuth ?? false;
2016
- const existingSecret = process.env.SCREENCI_SECRET;
2017
- if (existingSecret)
2018
- return existingSecret;
2019
- try {
2020
- const environment = getScreenCIEnvironment();
2021
- const apiUrl = getCliLinkSessionApiUrl();
2022
- const appUrl = getDevFrontendUrl();
2023
- const envFilePath = resolvedConfigPath
2024
- ? await resolveProjectEnvFilePath(resolvedConfigPath)
2025
- : resolve(process.cwd(), '.env');
2026
- const projectDir = resolvedConfigPath
2027
- ? dirname(resolvedConfigPath)
2028
- : process.cwd();
2029
- const specPath = getLinkSessionFilePath(projectDir);
2030
- const linkSessionContext = {
2031
- environment,
2032
- envFilePath,
2033
- ...(resolvedConfigPath ? { resolvedConfigPath } : {}),
2034
- };
2035
- const ensureSpec = async () => {
2036
- const storedSpec = await readPersistedLinkSessionSpec(specPath);
2037
- const spec = storedSpec &&
2038
- isStoredLinkSessionReusable(storedSpec, linkSessionContext)
2039
- ? storedSpec
2040
- : await createLinkSessionSpec({
2041
- apiUrl,
2042
- appUrl,
2043
- ...linkSessionContext,
2044
- });
2045
- if (spec !== storedSpec) {
2046
- await writePersistedLinkSessionSpec(specPath, spec);
2047
- }
2048
- return spec;
2049
- };
2050
- const saveCompletedSecret = async (secret) => {
2051
- process.env.SCREENCI_SECRET = secret;
2052
- await persistScreenCISecret(envFilePath, secret);
2053
- deletePersistedLinkSessionSpec(specPath);
2054
- logger.info(`Successfully saved SCREENCI_SECRET to ${envFilePath}`);
2055
- return secret;
2056
- };
2057
- // Decide whether this run waits for a browser sign-in or just prints the
2058
- // link and hands off. A human at a terminal always waits. A plain
2059
- // non-interactive run (a coding agent: no terminal, no CI) also waits by
2060
- // default, so `npx screenci record` records without needing a flag. CI and
2061
- // an explicit SCREENCI_NONINTERACTIVE never wait (CI must not hang and is
2062
- // expected to provide SCREENCI_SECRET), and `--no-poll-auth` opts back into
2063
- // that print-and-exit handoff. `--poll-auth` forces waiting and is kept for
2064
- // backwards compatibility (it is the default for agents now).
2065
- const isCI = process.env.CI === 'true';
2066
- const nonInteractiveForced = process.env.SCREENCI_NONINTERACTIVE === '1';
2067
- const waitForSignIn = noPollAuth
2068
- ? false
2069
- : interactive || pollAuth || (!isCI && !nonInteractiveForced);
2070
- const pollIntervalMs = interactive
2071
- ? SCREENCI_LINK_SESSION_POLL_INTERVAL_MS
2072
- : SCREENCI_LINK_SESSION_POLL_FLAG_INTERVAL_MS;
2073
- const timeoutMs = getAuthPollTimeoutMs(interactive
2074
- ? SCREENCI_LINK_SESSION_INTERACTIVE_TIMEOUT_MS
2075
- : SCREENCI_LINK_SESSION_POLL_FLAG_TIMEOUT_MS);
2076
- // Check the current/stored session once. If sign-in already completed (for
2077
- // example between runs) we pick up the secret immediately. Recreate a stale
2078
- // session and check once more before deciding to wait or hand off.
2079
- let spec = await ensureSpec();
2080
- let result = await pollLinkSessionOnce(spec);
2081
- if (result.status === 'expired' ||
2082
- result.status === 'consumed' ||
2083
- result.status === 'invalid') {
2084
- deletePersistedLinkSessionSpec(specPath);
2085
- spec = await ensureSpec();
2086
- result = await pollLinkSessionOnce(spec);
2087
- }
2088
- if (result.status === 'completed' && result.secret) {
2089
- return await saveCompletedSecret(result.secret);
2090
- }
2091
- if (!waitForSignIn) {
2092
- // CI / SCREENCI_NONINTERACTIVE / --no-poll-auth: print the link and hand
2093
- // off without waiting. The caller exits cleanly (0); a pending sign-in
2094
- // here is an expected handoff, not a failure.
2095
- logger.info(`Sign-in required to record. Open this link to sign in:\n${pc.cyan(spec.appUrl)}\n` +
2096
- `This session won't wait for sign-in. After signing in, rerun ${pc.cyan(getSuggestedScreenciCommand('record'))} to record. In CI, set SCREENCI_SECRET (from ${getScreenCISecretsUrl()}) to record without an interactive sign-in.`);
2097
- return undefined;
2098
- }
2099
- // Wait for sign-in: print the link, then poll until the human finishes and
2100
- // continue recording automatically once the secret lands. We re-print the
2101
- // link on every (re)created session so the latest valid link is always
2102
- // visible. We do not wait forever: after the timeout we throw so the caller
2103
- // exits non-zero (a timed-out sign-in means nothing was recorded). The link
2104
- // stays valid for a rerun.
2105
- const timeoutMinutes = Math.max(1, Math.round(timeoutMs / 60_000));
2106
- const intervalSeconds = Math.max(1, Math.round(pollIntervalMs / 1_000));
2107
- const deadlineEpochMs = Date.now() + timeoutMs;
2108
- for (;;) {
2109
- logger.info(`Sign-in required to record. Open this link to sign in:\n${pc.cyan(spec.appUrl)}\n` +
2110
- `Waiting for sign-in (checking every ${intervalSeconds} seconds, up to ${timeoutMinutes} minutes). Recording continues automatically once you finish.`);
2111
- const polled = await pollLinkSession(spec, pollIntervalMs, deadlineEpochMs);
2112
- if (polled.status === 'completed' && polled.secret) {
2113
- return await saveCompletedSecret(polled.secret);
2114
- }
2115
- if (polled.status === 'timed-out' || Date.now() >= deadlineEpochMs) {
2116
- throw new SignInTimeoutError(`Timed out after ${timeoutMinutes} minutes waiting for sign-in. The link is still valid:\n${pc.cyan(spec.appUrl)}\n` +
2117
- `After signing in, rerun ${pc.cyan(getSuggestedScreenciCommand('record'))} to record.`);
2118
- }
2119
- // Session went stale (expired/consumed/invalid) before sign-in: recreate
2120
- // and keep waiting until the deadline.
2121
- deletePersistedLinkSessionSpec(specPath);
2122
- spec = await ensureSpec();
2123
- }
2124
- }
2125
- catch (err) {
2126
- // A timed-out sign-in is a deliberate, already-formatted failure: let it
2127
- // propagate so the caller can exit non-zero rather than swallowing it as a
2128
- // generic auth error.
2129
- if (err instanceof SignInTimeoutError)
2130
- throw err;
2131
- const msg = err instanceof Error ? err.message : String(err);
2132
- logger.warn(`Authentication failed: ${msg}`);
2133
- logger.info(`You can add SCREENCI_SECRET manually to ${resolvedConfigPath ? await resolveProjectEnvFilePath(resolvedConfigPath) : '.env'} later. Get it from ${getScreenCISecretsUrl()}.`);
2134
- logScreenCISecretGuide();
2135
- return undefined;
2136
- }
2137
- }
2138
- /**
2139
- * `screenci login`: get a sign-in link in front of a human as early as
2140
- * possible. It reuses the link `init` (or a prior run) persisted, or creates a
2141
- * fresh one, tries to open it in the browser best-effort, and always prints it
2142
- * as a fallback. It does NOT wait for sign-in to finish: the agent can run this,
2143
- * keep authoring and `test`ing, then `record` (which blocks for sign-in) once
2144
- * the script is green. If sign-in already completed, it saves the secret and
2145
- * exits.
2146
- */
2147
- export async function runLogin(configPath) {
2148
- const { resolvedConfigPath } = await loadScreenCIConfigAndEnv(configPath);
2149
- if (process.env.SCREENCI_SECRET) {
2150
- logger.info(`Already signed in (SCREENCI_SECRET is set). Run ${pc.cyan(getSuggestedScreenciCommand('record'))} to record.`);
2151
- return;
2152
- }
2153
- const environment = getScreenCIEnvironment();
2154
- const apiUrl = getCliLinkSessionApiUrl();
2155
- const appUrl = getDevFrontendUrl();
2156
- const envFilePath = await resolveProjectEnvFilePath(resolvedConfigPath);
2157
- const projectDir = dirname(resolvedConfigPath);
2158
- const specPath = getLinkSessionFilePath(projectDir);
2159
- const linkSessionContext = { environment, envFilePath, resolvedConfigPath };
2160
- const createSpec = async () => {
2161
- const created = await createLinkSessionSpec({
2162
- apiUrl,
2163
- appUrl,
2164
- ...linkSessionContext,
2165
- });
2166
- await writePersistedLinkSessionSpec(specPath, created);
2167
- return created;
2168
- };
2169
- try {
2170
- // Reuse a still-valid persisted session so login and a later record share
2171
- // the same link; otherwise create and persist a fresh one.
2172
- const stored = await readPersistedLinkSessionSpec(specPath);
2173
- let spec = stored && isStoredLinkSessionReusable(stored, linkSessionContext)
2174
- ? stored
2175
- : await createSpec();
2176
- // If sign-in already completed (e.g. between runs) save the secret and stop:
2177
- // there is nothing left to open. Recreate a stale session before opening so
2178
- // the link we hand off is valid.
2179
- const result = await pollLinkSessionOnce(spec);
2180
- if (result.status === 'completed' && result.secret) {
2181
- process.env.SCREENCI_SECRET = result.secret;
2182
- await persistScreenCISecret(envFilePath, result.secret);
2183
- deletePersistedLinkSessionSpec(specPath);
2184
- logger.info(`Already signed in. Saved SCREENCI_SECRET to ${envFilePath}.`);
2185
- return;
2186
- }
2187
- if (result.status === 'expired' ||
2188
- result.status === 'consumed' ||
2189
- result.status === 'invalid') {
2190
- deletePersistedLinkSessionSpec(specPath);
2191
- spec = await createSpec();
2192
- }
2193
- const opened = await openUrlInBrowser(spec.appUrl);
2194
- if (opened.opened) {
2195
- logger.info(`Opening the sign-in link in your browser:\n${pc.cyan(spec.appUrl)}`);
2196
- }
2197
- else {
2198
- logger.info(`Could not open a browser automatically (${opened.reason}). Open this link to sign in:\n${pc.cyan(spec.appUrl)}`);
2199
- }
2200
- logger.info(`The link stays valid for 24 hours. You can keep authoring and run ${pc.cyan(getSuggestedScreenciCommand('test'))} while you sign in. Then run ${pc.cyan(getSuggestedScreenciCommand('record'))}, which waits for sign-in and records once you finish.`);
2201
- }
2202
- catch (err) {
2203
- // Login is a best-effort convenience: never fail the run on a network or
2204
- // browser hiccup. record can still create and wait on the link later.
2205
- const msg = err instanceof Error ? err.message : String(err);
2206
- logger.warn(`Could not start sign-in: ${msg}`);
2207
- logger.info(`You can sign in later: ${pc.cyan(getSuggestedScreenciCommand('record'))} prints a link and waits for sign-in. In CI, set SCREENCI_SECRET (from ${getScreenCISecretsUrl()}).`);
2208
- }
2209
- }
2210
1894
  // Uploads the recordings already written under `.screenci` for the resolved
2211
1895
  // config. Shared by `record` (after a Playwright run) and `retry` (which
2212
1896
  // re-sends the existing recordings without re-running Playwright). A non-null
@@ -2236,7 +1920,7 @@ async function uploadRecordedVideosForConfig(configPath, playwrightFailure, verb
2236
1920
  logger.info('All recordings failed.');
2237
1921
  }
2238
1922
  else if (!secret) {
2239
- logger.info(`No SCREENCI_SECRET configured for uploads. Rerun ${getSuggestedScreenciCommand('record')} or add it to the project env file.`);
1923
+ logger.info(`No SCREENCI_SECRET configured for uploads. Copy your secret from ${getScreenCISecretsUrl()} into the project env file, then rerun ${getSuggestedScreenciCommand('record')}.`);
2240
1924
  }
2241
1925
  else if (playwrightFailure !== null &&
2242
1926
  uploadPolicy === 'all-or-nothing') {
@@ -2309,7 +1993,12 @@ async function uploadRecordedVideosForConfig(configPath, playwrightFailure, verb
2309
1993
  }
2310
1994
  if (projectId !== null && plan !== 'business') {
2311
1995
  logger.info('');
2312
- logger.info('Upgrade for more renders, more active videos, and expressive narration:');
1996
+ if (plan === 'free') {
1997
+ logger.info('You are on the free tier, so this render includes a ScreenCI watermark. Upgrade to remove it and get more renders, more active videos, and expressive narration:');
1998
+ }
1999
+ else {
2000
+ logger.info('Upgrade for more renders, more active videos, and expressive narration:');
2001
+ }
2313
2002
  logger.info(pc.cyan(`${appUrl}/select-plan`));
2314
2003
  }
2315
2004
  for (const notice of studioNotices) {
@@ -2348,7 +2037,7 @@ async function uploadRecordedVideosForConfig(configPath, playwrightFailure, verb
2348
2037
  export async function main() {
2349
2038
  if (process.argv.length <= 2) {
2350
2039
  logger.error('Error: No command provided');
2351
- logger.error('Available commands: record, test, login, info, make-public, make-private, init');
2040
+ logger.error('Available commands: record, test, info, make-public, make-private, init');
2352
2041
  process.exit(1);
2353
2042
  }
2354
2043
  const program = new Command();
@@ -2360,8 +2049,6 @@ export async function main() {
2360
2049
  .command('record [playwrightArgs...]')
2361
2050
  .description('Record videos using Playwright')
2362
2051
  .option('-v, --verbose', 'verbose output')
2363
- .option('--poll-auth', 'wait for sign-in before recording (now the default off-CI; kept for backwards compatibility)')
2364
- .option('--no-poll-auth', 'do not wait for sign-in: print the link and exit cleanly if not signed in (the default under CI)')
2365
2052
  .option('--remote', 'trigger the GitHub Actions recording workflow for this project remotely instead of recording locally')
2366
2053
  .option('--languages <langs>', 'record/render only these languages (comma-separated, e.g. fi,en)')
2367
2054
  .allowUnknownOption(true)
@@ -2383,7 +2070,7 @@ export async function main() {
2383
2070
  const uploadExisting = isUploadExistingEnabled();
2384
2071
  if (!uploadExisting) {
2385
2072
  try {
2386
- await run('record', parsed.otherArgs, parsed.configPath, parsed.verbose, false, parsed.pollAuth, parsed.languages, parsed.noPollAuth);
2073
+ await run('record', parsed.otherArgs, parsed.configPath, parsed.verbose, false, parsed.languages);
2387
2074
  }
2388
2075
  catch (error) {
2389
2076
  if (!(error instanceof Error))
@@ -2438,13 +2125,6 @@ export async function main() {
2438
2125
  const recordCommand = getSuggestedScreenciCommand('record');
2439
2126
  logger.info(`Tests passed. Run ${pc.cyan(recordCommand)} to render the videos.`);
2440
2127
  });
2441
- program
2442
- .command('login')
2443
- .description('Open the sign-in link in your browser (best-effort) so recording can finish later')
2444
- .option('-c, --config <path>', 'path to screenci.config.ts')
2445
- .action(async (options) => {
2446
- await runLogin(options['config']);
2447
- });
2448
2128
  program
2449
2129
  .command('info')
2450
2130
  .description("Print the latest record run's video URLs and render status as JSON")
@@ -2522,8 +2202,6 @@ function getSubcommandArgv(command) {
2522
2202
  export function parseRecordCliArgs(args) {
2523
2203
  let configPath;
2524
2204
  let verbose = false;
2525
- let pollAuth = false;
2526
- let noPollAuth = false;
2527
2205
  let remote = false;
2528
2206
  let languages;
2529
2207
  const otherArgs = [];
@@ -2557,12 +2235,6 @@ export function parseRecordCliArgs(args) {
2557
2235
  else if (arg === '--verbose' || arg === '-v') {
2558
2236
  verbose = true;
2559
2237
  }
2560
- else if (arg === '--poll-auth') {
2561
- pollAuth = true;
2562
- }
2563
- else if (arg === '--no-poll-auth') {
2564
- noPollAuth = true;
2565
- }
2566
2238
  else if (arg === '--remote') {
2567
2239
  remote = true;
2568
2240
  }
@@ -2573,8 +2245,6 @@ export function parseRecordCliArgs(args) {
2573
2245
  return {
2574
2246
  configPath,
2575
2247
  verbose,
2576
- pollAuth,
2577
- noPollAuth,
2578
2248
  remote,
2579
2249
  languages,
2580
2250
  otherArgs,
@@ -2637,7 +2307,7 @@ function validateArgs(args) {
2637
2307
  */
2638
2308
  async function fetchTextOverridesEnv(configPath, languages, verbose) {
2639
2309
  try {
2640
- const { screenciConfig, secret, apiUrl } = await requireScreenCISecret(configPath, { interactive: false });
2310
+ const { screenciConfig, secret, apiUrl } = await requireScreenCISecret(configPath);
2641
2311
  const params = new URLSearchParams({
2642
2312
  projectName: screenciConfig.projectName,
2643
2313
  });
@@ -2677,7 +2347,7 @@ async function fetchTextOverridesEnv(configPath, languages, verbose) {
2677
2347
  */
2678
2348
  async function fetchRecordOptionsEnv(configPath, verbose) {
2679
2349
  try {
2680
- const { screenciConfig, secret, apiUrl } = await requireScreenCISecret(configPath, { interactive: false });
2350
+ const { screenciConfig, secret, apiUrl } = await requireScreenCISecret(configPath);
2681
2351
  const params = new URLSearchParams({
2682
2352
  projectName: screenciConfig.projectName,
2683
2353
  });
@@ -2705,20 +2375,14 @@ async function fetchRecordOptionsEnv(configPath, verbose) {
2705
2375
  return {};
2706
2376
  }
2707
2377
  }
2708
- async function run(command, additionalArgs, customConfigPath, verbose = false, mockRecord = false, pollAuth = false, languages, noPollAuth = false) {
2378
+ async function run(command, additionalArgs, customConfigPath, verbose = false, mockRecord = false, languages) {
2709
2379
  const configPath = resolveScreenCIConfigPathOrExit(customConfigPath);
2710
2380
  if (command === 'test' || process.env.SCREENCI_RECORDING !== 'true') {
2711
2381
  await loadEnvFileFromConfigSource(configPath, false);
2712
2382
  }
2713
2383
  // Only validate args for record command
2714
2384
  if (command === 'record') {
2715
- if (!process.env.SCREENCI_SECRET) {
2716
- await requireScreenCISecret(configPath, {
2717
- interactive: detectInteractiveSession(),
2718
- pollAuth,
2719
- noPollAuth,
2720
- });
2721
- }
2385
+ await requireScreenCISecret(configPath);
2722
2386
  validateArgs(additionalArgs);
2723
2387
  const screenciDir = resolve(dirname(configPath), '.screenci');
2724
2388
  clearRecordingDirectories(screenciDir);