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
@@ -50,23 +50,26 @@ ScreenCI uses Playwright-style `.screenci.ts` files and adds recording-specific
50
50
  - `hide()` removes setup and loading sections from the final recording.
51
51
  - `autoZoom()` follows navigation, click-driven, and broader interaction sequences with smooth camera motion. Use it sparingly, and start with its default options unless the user explicitly asks for different zoom behavior or the flow clearly needs a targeted override.
52
52
  - `zoomTo()` and `resetZoom()` are better for forms and other steady editing sections where the camera should stay fixed while the user types, selects, toggles, and confirms within one area.
53
- - `video.localize({ narration })` is mandatory for every video: declare narration in every `.screenci.ts` file and include spoken narration throughout the demo. The opening narration should first state the purpose of the video, then continue with the explanation or walkthrough. The body receives the `narration` marker object: call `await narration.key()` for the common case where the full line should run before moving on. Use `await narration.key.start()` when narration should overlap with the next action, and `await narration.key.end()` only to close that same active cue later, especially before visible navigation or route changes. Voice is set in the same localize spec via `voice` (one config for all languages, or a per-language map), and a single cue can override it with the `{ cue, voice }` object form.
53
+ - `video.narration({ ... })` is mandatory for every video: declare narration in every `.screenci.ts` file and include spoken narration throughout the demo. Pass either a flat object of cue name to text (shared across all languages) or an object keyed by language (`en`, `es`, ...) of cue name to text. The opening narration should first state the purpose of the video, then continue with the explanation or walkthrough. The body receives the `narration` marker object: call `await narration.key()` for the common case where the full line should run before moving on. Use `await narration.key.start()` when narration should overlap with the next action, and `await narration.key.end()` only to close that same active cue later, especially before visible navigation or route changes. Voice is a render option, not part of the narration spec: set a default (and optional per-language overrides) with `video.use({ renderOptions: { narration: { voice: { name: voices.Ava } } } })` or in `screenci.config.ts`.
54
54
  - Narration text can include inline speech-control tags such as `[pronounce: screen see eye]`, `[short pause]`, `[medium pause]`, and `[long pause]` when a word needs guided pronunciation or an intentional pause. When narration includes a URL or domain name, add a pronunciation guide or rewrite the line so it will be spoken clearly. Example: `screenci.com [pronounce: screen see eye dot com]`.
55
55
 
56
56
  Example:
57
57
 
58
58
  ```ts
59
- video.localize({
60
- narration: {
61
- en: {
62
- intro:
63
- 'This video shows how to update your billing details and save the changes.',
64
- explainForm:
65
- 'We start on the billing page and update the company name, email, and tax ID.',
66
- saving: 'Now we save the changes and wait for the confirmation message.',
67
- nextPage:
68
- 'Next, we open the invoices section to confirm the new billing details are in use.',
69
- },
59
+ import { video, voices } from 'screenci'
60
+
61
+ // Voice is a render option (how narration is spoken).
62
+ video.use({ renderOptions: { narration: { voice: { name: voices.Ava } } } })
63
+
64
+ video.narration({
65
+ en: {
66
+ intro:
67
+ 'This video shows how to update your billing details and save the changes.',
68
+ explainForm:
69
+ 'We start on the billing page and update the company name, email, and tax ID.',
70
+ saving: 'Now we save the changes and wait for the confirmation message.',
71
+ nextPage:
72
+ 'Next, we open the invoices section to confirm the new billing details are in use.',
70
73
  },
71
74
  })('Update billing details', async ({ page, narration }) => {
72
75
  await narration.intro()
@@ -83,7 +86,7 @@ video.localize({
83
86
 
84
87
  **Every video MUST follow these conventions:**
85
88
 
86
- - **Narration on every video (required, no exceptions)** — always declare `video.localize({ narration: { ... } })` and add narration to every `.screenci.ts` file. Videos without narration are not acceptable.
89
+ - **Narration on every video (required, no exceptions)** — always declare `video.narration({ ... })` and add narration to every `.screenci.ts` file. Videos without narration are not acceptable.
87
90
  - **Open with the video's purpose** — the first spoken narration should clearly state what the video is for before moving into the step-by-step explanation.
88
91
  - **Guide pronunciation for URLs and domains** — if narration says a URL, domain, product name, or other term that a voice model might read incorrectly, add a `[pronounce: ...]` hint or phrase it in a clearly spoken way.
89
92
  - **Start on the requested page** — the visible video should always begin on the page the user requested.
@@ -123,48 +126,36 @@ await autoZoom(async () => {
123
126
 
124
127
  ## Command Notes
125
128
 
126
- - `screenci init` (or `npm init screenci`) scaffolds a new project. It can be run at any time, but if the project is already initialized it fails on purpose: it exits with an error like `screenci/ already exists`. That is expected, not a problem to fix. Do not delete the existing project to force a re-init. Continue working with the project that is already there. On a fresh init it also prints a sign-in link (valid for 24 hours) so the user can sign in early, before any recording.
129
+ - `screenci init` (or `npm init screenci`) scaffolds a new project. It can be run at any time, but if the project is already initialized it fails on purpose: it exits with an error like `screenci/ already exists`. That is expected, not a problem to fix. Do not delete the existing project to force a re-init. Continue working with the project that is already there. If the user gives you a one-time setup token (it looks like `otp_...`), pass it as the first argument (`npm init screenci@latest otp_... -- --yes`) and init connects the project by writing `SCREENCI_SECRET` into `screenci/.env`.
127
130
  - `screenci record` runs the recording flow with local Playwright.
128
131
  - `screenci test <playwright args...>` forwards most Playwright test arguments unchanged, while still using `screenci.config.ts`.
129
132
 
130
- ### Sign in early (do this first)
133
+ ### Connect the project (do this first)
131
134
 
132
- Signing in does not block writing or testing the video, only the final recording. So get it started up front, in parallel with your work:
135
+ `screenci record` needs a `SCREENCI_SECRET`. There is no browser sign-in; connect the project one of two ways:
133
136
 
134
- 1. Run `npx screenci login`. It opens the sign-in link (valid for 24 hours) in the user's browser and prints it, then returns immediately without waiting. It reuses the link a fresh `screenci init` created, or makes one. (You can also `cat screenci/.screenci/link-session.json` if you just want to read a cached link.)
135
- 2. Surface the printed link to the user verbatim and ask them to sign in and choose a plan while you build and test the video, in case no browser could open. Tell them it just needs to be done before the final recording.
136
- 3. Build and test the video as normal (`npx screenci test`) while they sign in.
137
- 4. For the final recording, run `npx screenci record` yourself (no special flag). If the user already signed in it records immediately; otherwise it prints the link, waits, and records automatically once they finish. Do not hand this step back to the user. If sign-in does not happen before the timeout, `record` exits non-zero and reprints the still-valid link: re-surface it, ask the user to sign in, and run `npx screenci record` again.
137
+ 1. **One-time setup token.** If the user gave you a token (`otp_...`), run `npm init screenci@latest <token> -- --yes`. Init exchanges the token for the org's `SCREENCI_SECRET` and writes it into `screenci/.env`, so `record` uploads immediately on the free tier. The token is single-use and short-lived.
138
+ 2. **Get Started page or secrets page.** If you have no token (or it was already used or expired), ask the user to open their ScreenCI **Get Started** page and copy the shown init command (it embeds a fresh one-time token), or to copy `SCREENCI_SECRET` from their **secrets page** into `screenci/.env`. The org secret is shared across projects, so either works for a new project. Relay this and keep building and testing the video while they do it. Only the final `record` needs the secret.
138
139
 
139
- ### How sign-in works, and when a run waits vs hands off
140
+ If `SCREENCI_SECRET` is missing when you run `record`, it prints guidance and exits **non-zero** without recording. That is a setup step, not a code problem: get the secret into `screenci/.env` (token or secrets page) and run `npx screenci record` again. Do not hand the `record` step back to the user otherwise.
140
141
 
141
- `screenci record` needs a `SCREENCI_SECRET`. If one is not set, it bootstraps sign-in:
142
-
143
- - At an **interactive terminal**, and in a **plain non-interactive run** (the common case for a coding agent: no terminal, no CI), `screenci record` prints a one-time auth link, **waits** for browser sign-in, saves the secret into the configured `envFile` or project `.env`, and continues recording automatically. You do not need `--poll-auth`; waiting is the default. If sign-in does not complete before the timeout, it exits **non-zero** with the link still valid, so rerun `record` once the user signs in.
144
- - Under **CI** (`CI=true`), or with `SCREENCI_NONINTERACTIVE=1` or `--no-poll-auth`, `screenci record` does **not** wait. It prints the sign-in link and exits cleanly (exit code 0) **without recording**. In these cases a clean exit that printed a sign-in link is a handoff, not a finished recording: set `SCREENCI_SECRET` ahead of time (the expected setup for CI) so recording proceeds without an interactive sign-in.
145
-
146
- - To skip sign-in entirely, set `SCREENCI_SECRET` ahead of time (env var or project `.env`).
147
- - Pending auth state is cached in `.screenci/link-session.json`, so rerunning `record` reuses the same link until it expires or completes.
148
- - `--poll-auth` still works (it forces waiting) but is no longer needed off-CI, where waiting is already the default.
142
+ Free renders include a ScreenCI watermark. Mention that the user can upgrade to remove it.
149
143
 
150
144
  ## Recording Workflow
151
145
 
152
- 1. Start from the existing initialized ScreenCI package. Early on, surface the sign-in link to the user (printed by `screenci init`, or by running `npx screenci record` once, or from `screenci/.screenci/link-session.json`) and ask them to sign in while you build the video. The link is valid for 24 hours and only needs to be used before the final recording.
146
+ 1. Start from the existing initialized ScreenCI package. Early on, make sure the project is connected: either init already wrote `SCREENCI_SECRET` from a one-time setup token, or ask the user to copy `SCREENCI_SECRET` from their secrets page into `screenci/.env`. Only the final recording needs it, so keep building while they do it.
153
147
  2. Add or edit `.screenci.ts` files in `recordings/`.
154
148
  Remove `recordings/example.screenci.ts` if you are creating new videos and do not need the starter video.
155
- For narration, declare it on the test with `video.localize({ narration: { en: { ... } } })` and trigger lines from the `narration` fixture with `await narration.someKey()` when the full line should finish before moving on. Use `await narration.someKey.start()` only when narration should overlap with the next action, and `await narration.someKey.end()` only to close that same active cue later. This is especially important before visible navigation or page changes. Use inline tags like `[pronounce: ...]` and `[short pause]` inside cue text when needed, especially for URLs and domains such as `screenci.com [pronounce: screen see eye dot com]`.
149
+ For narration, declare it on the test with `video.narration({ en: { ... } })` and trigger lines from the `narration` fixture with `await narration.someKey()` when the full line should finish before moving on. Use `await narration.someKey.start()` only when narration should overlap with the next action, and `await narration.someKey.end()` only to close that same active cue later. This is especially important before visible navigation or page changes. Use inline tags like `[pronounce: ...]` and `[short pause]` inside cue text when needed, especially for URLs and domains such as `screenci.com [pronounce: screen see eye dot com]`.
156
150
  Example:
157
151
 
158
152
  ```ts
159
- video.localize({
160
- narration: {
161
- en: {
162
- intro: 'This video shows how to export a monthly sales report.',
163
- filters:
164
- 'First, we set the report range and select the sales channel filters.',
165
- export:
166
- 'Then we export the report and wait for the download to start.',
167
- },
153
+ video.narration({
154
+ en: {
155
+ intro: 'This video shows how to export a monthly sales report.',
156
+ filters:
157
+ 'First, we set the report range and select the sales channel filters.',
158
+ export: 'Then we export the report and wait for the download to start.',
168
159
  },
169
160
  })('Export sales report', async ({ page, narration }) => {
170
161
  await narration.intro()
@@ -176,7 +167,7 @@ Signing in does not block writing or testing the video, only the final recording
176
167
  ```
177
168
 
178
169
  3. Run `npx screenci test` until it passes.
179
- 4. For the final recording, run `npx screenci record` yourself (no special flag). If the user already signed in (via the link from step 1) it records immediately; otherwise it prints the link, waits, and continues automatically once they finish. If sign-in does not happen before the timeout, the command exits non-zero and reprints the still-valid link: re-surface it, ask the user to sign in, and run `npx screenci record` again. The link can be retried until it completes.
170
+ 4. For the final recording, run `npx screenci record` yourself. If `SCREENCI_SECRET` is set it records and uploads immediately. If it is missing, `record` exits non-zero with guidance: get the secret into `screenci/.env` (one-time setup token or the secrets page) and run `npx screenci record` again. Do not hand this step back to the user otherwise.
180
171
  5. ScreenCI writes `.screenci/<video-name>/recording.mp4` and `data.json` for each recorded video.
181
172
 
182
173
  ## Specific Tasks
@@ -30,20 +30,20 @@ package.json
30
30
  ## Notes
31
31
 
32
32
  - `init` can be run at any time, but it is non-destructive and will not re-initialize an existing project. If the project is already initialized (a `screenci/` directory already exists), it fails on purpose and exits with an error like `screenci/ already exists`. That is expected. Do not delete the existing project to force a re-init: continue working with the project that is already there.
33
- - On a fresh init, the command also prints a sign-in link (valid for 24 hours) and caches it in `screenci/.screenci/link-session.json`. Right after init, run `screenci login` (see below): it opens this link in the user's browser and prints it, without waiting, so sign-in can start while the video is being built. Sign-in only needs to be completed before the final recording, and a later `screenci record` reuses this same link.
34
- - Prefer `--yes` for non-interactive setup. Without it, the command prompts for setup choices and defaults the project name to the current directory name when none is provided.
33
+ - `init` accepts a one-time setup token as its first positional argument (it looks like `otp_...`, copied from the Get Started or secrets page). With a token, init exchanges it for the org's `SCREENCI_SECRET` and writes it into `screenci/.env`, so `record` uploads immediately on the free tier. The token is single-use and short-lived; a used or expired token falls back to the manual path without failing the scaffold.
34
+ - Without a token, init tells you to copy `SCREENCI_SECRET` from the secrets page into `screenci/.env` before the first record. There is no browser sign-in. A fresh token (and a ready-to-run command) is always available on the Get Started page, including for adding a second project; the org secret is shared across projects.
35
+ - Prefer `--yes` for non-interactive setup. Without it, the command prompts for setup choices and defaults the project name to the current directory name when none is provided. A positional that looks like a setup token is treated as the token, not the project name.
35
36
  - The name is used as the ScreenCI project display name. Files are always created in the current directory.
36
37
  - `--yes` accepts the defaults.
37
38
  - `--agent <name>` is passed to the selected skills install command.
38
39
  - `--verbose` shows more setup output.
39
- - `record` bootstraps `SCREENCI_SECRET` on first run if it is missing.
40
+ - `record` requires `SCREENCI_SECRET`; it does not bootstrap it.
40
41
  - `record` uses local Playwright.
41
42
 
42
43
  ## Typical Flow
43
44
 
44
45
  ```bash
45
- npm init screenci "My Project" -- --yes
46
- npx screenci login # open the sign-in link in the browser (returns immediately)
47
- npx screenci test # verify the video works while the user signs in
48
- npx screenci record # capture the final recording (waits for sign-in if pending)
46
+ npm init screenci@latest otp_your_token -- --yes # connects the project (writes SCREENCI_SECRET)
47
+ npx screenci test # verify the video works
48
+ npx screenci record # capture the final recording and upload
49
49
  ```
@@ -19,19 +19,20 @@ npx screenci record -c screenci.config.ts
19
19
  - Saves output under `.screenci/<video-name>/`
20
20
  - Produces at least `recording.mp4` and `data.json`
21
21
 
22
- ## Sign In Early
22
+ ## Connect Before Recording
23
23
 
24
- Sign-in only blocks the final recording, not authoring or testing. Get it going up front: run `npx screenci login` to open the sign-in link in the user's browser (it also prints the link and returns immediately without waiting), then surface that link to the user and ask them to sign in while you build the video. The link is valid for 24 hours. For the final recording, run `npx screenci record` (no flag): off-CI it waits and records as soon as sign-in completes. Attempting it before they finish does no harm: it reprints the link, and if the timeout elapses it exits non-zero with the link still valid, so re-prompt and run again.
24
+ `record` needs `SCREENCI_SECRET`, and there is no browser sign-in. Get it in place before the final recording (it does not block authoring or testing):
25
+
26
+ - If the user has a one-time setup token (`otp_...`), run `npm init screenci@latest <token> -- --yes`; init writes `SCREENCI_SECRET` into `screenci/.env`. A fresh token is on the user's Get Started page.
27
+ - Otherwise ask the user to copy `SCREENCI_SECRET` from their secrets page into `screenci/.env`. The org secret is shared across projects.
25
28
 
26
29
  ## Runtime Behavior
27
30
 
28
31
  - Recording runs with local Playwright.
29
- - When `SCREENCI_SECRET` is missing, `screenci record` waits for browser sign-in by default and continues once it completes. This is the default at an interactive terminal and in a plain non-interactive session (no terminal, no CI), so an agent does not need a flag. It prints the link, polls (every 2 seconds interactively, every 5 seconds otherwise) up to a timeout (15 minutes interactively, 5 minutes otherwise), then saves the secret and records. If the timeout elapses first, it exits **non-zero** with the link still valid, so re-surface the link and rerun once signed in. Override the timeout with `SCREENCI_POLL_AUTH_TIMEOUT_MS` (milliseconds).
30
- - Under CI (`CI=true`), or with `SCREENCI_NONINTERACTIVE=1` or `--no-poll-auth`, `screenci record` does not wait: it prints the sign-in link and exits cleanly with exit code 0 without recording. A clean exit here is a handoff, not a finished recording, so do not treat exit code 0 as "recording done" when the output contains a sign-in link. Set `SCREENCI_SECRET` ahead of time (the expected CI setup) to record without an interactive sign-in.
31
- - `--poll-auth` still forces waiting and is kept for backwards compatibility, but is no longer needed off-CI where waiting is already the default.
32
- - Pending auth state is cached in `.screenci/link-session.json`, so rerunning `record` reuses the same link until it expires or completes.
32
+ - `SCREENCI_SECRET` is required (from `screenci/.env` or the environment). If it is missing, `screenci record` prints guidance (pointing at the secrets page) and exits **non-zero** without recording. Get the secret in place and rerun. This is a setup step, not a code problem.
33
+ - New accounts start on the free tier; free renders include a ScreenCI watermark, and after upload `record` prints an upgrade link that removes it.
33
34
  - Playwright arguments can be passed through after the command.
34
- - When API configuration and `SCREENCI_SECRET` are available, uploads may run after recording.
35
+ - When API configuration and `SCREENCI_SECRET` are available, uploads run after recording.
35
36
 
36
37
  ## Recommended Workflow
37
38
 
@@ -1,34 +0,0 @@
1
- import { spawn } from 'child_process';
2
- export type BrowserOpenSpec = {
3
- command: string;
4
- args: string[];
5
- };
6
- export type OpenBrowserResult = {
7
- opened: true;
8
- } | {
9
- opened: false;
10
- reason: string;
11
- };
12
- /**
13
- * Resolve the platform-specific command that opens a URL in the default
14
- * browser: `open` on macOS, `cmd /c start` on Windows, and `xdg-open`
15
- * everywhere else (Linux, BSD). The empty `""` on Windows is `start`'s window
16
- * title argument, so a quoted URL is not consumed as the title.
17
- */
18
- export declare function resolveBrowserOpenSpec(url: string, platform?: NodeJS.Platform): BrowserOpenSpec;
19
- /**
20
- * Try to open `url` in the user's default browser. Best-effort: a synchronous
21
- * spawn failure or an asynchronous launch error (e.g. `xdg-open` missing on a
22
- * minimal Linux box) resolves to `{ opened: false, reason }` instead of
23
- * throwing. A resolved `{ opened: true }` only means the launch was dispatched,
24
- * not that a browser window actually appeared, so callers still surface the
25
- * link as a fallback.
26
- *
27
- * Dependency-injected (`platform`, `spawnFn`) so tests can drive both outcomes
28
- * without a real terminal or browser.
29
- */
30
- export declare function openUrlInBrowser(url: string, options?: {
31
- platform?: NodeJS.Platform;
32
- spawnFn?: typeof spawn;
33
- }): Promise<OpenBrowserResult>;
34
- //# sourceMappingURL=openBrowser.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"openBrowser.d.ts","sourceRoot":"","sources":["../../src/openBrowser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AAQrC,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,EAAE,CAAA;CACf,CAAA;AAED,MAAM,MAAM,iBAAiB,GACzB;IAAE,MAAM,EAAE,IAAI,CAAA;CAAE,GAChB;IAAE,MAAM,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAErC;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,eAAe,CAQjB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE;IACP,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;IAC1B,OAAO,CAAC,EAAE,OAAO,KAAK,CAAA;CAClB,GACL,OAAO,CAAC,iBAAiB,CAAC,CAqC5B"}
@@ -1,62 +0,0 @@
1
- import { spawn } from 'child_process';
2
- /**
3
- * Resolve the platform-specific command that opens a URL in the default
4
- * browser: `open` on macOS, `cmd /c start` on Windows, and `xdg-open`
5
- * everywhere else (Linux, BSD). The empty `""` on Windows is `start`'s window
6
- * title argument, so a quoted URL is not consumed as the title.
7
- */
8
- export function resolveBrowserOpenSpec(url, platform = process.platform) {
9
- if (platform === 'darwin') {
10
- return { command: 'open', args: [url] };
11
- }
12
- if (platform === 'win32') {
13
- return { command: 'cmd', args: ['/c', 'start', '', url] };
14
- }
15
- return { command: 'xdg-open', args: [url] };
16
- }
17
- /**
18
- * Try to open `url` in the user's default browser. Best-effort: a synchronous
19
- * spawn failure or an asynchronous launch error (e.g. `xdg-open` missing on a
20
- * minimal Linux box) resolves to `{ opened: false, reason }` instead of
21
- * throwing. A resolved `{ opened: true }` only means the launch was dispatched,
22
- * not that a browser window actually appeared, so callers still surface the
23
- * link as a fallback.
24
- *
25
- * Dependency-injected (`platform`, `spawnFn`) so tests can drive both outcomes
26
- * without a real terminal or browser.
27
- */
28
- export async function openUrlInBrowser(url, options = {}) {
29
- const platform = options.platform ?? process.platform;
30
- const spawnFn = options.spawnFn ?? spawn;
31
- const spec = resolveBrowserOpenSpec(url, platform);
32
- let child;
33
- try {
34
- child = spawnFn(spec.command, spec.args, {
35
- stdio: 'ignore',
36
- detached: true,
37
- });
38
- }
39
- catch (err) {
40
- return {
41
- opened: false,
42
- reason: err instanceof Error ? err.message : String(err),
43
- };
44
- }
45
- // Keep an error listener attached for the child's lifetime so a late launch
46
- // error (e.g. ENOENT) is handled rather than thrown as an unhandled event.
47
- const launchError = new Promise((resolveError) => {
48
- child.on('error', resolveError);
49
- });
50
- // Detach so the parent (login) can exit without waiting on the browser.
51
- child.unref();
52
- // ENOENT and friends surface on the next tick, ahead of this setImmediate, so
53
- // a failed launch wins the race; otherwise we treat the dispatch as success.
54
- const settled = new Promise((resolveTick) => {
55
- setImmediate(() => resolveTick(null));
56
- });
57
- const result = await Promise.race([launchError, settled]);
58
- return result === null
59
- ? { opened: true }
60
- : { opened: false, reason: result.message };
61
- }
62
- //# sourceMappingURL=openBrowser.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"openBrowser.js","sourceRoot":"","sources":["../../src/openBrowser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AAiBrC;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CACpC,GAAW,EACX,WAA4B,OAAO,CAAC,QAAQ;IAE5C,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAA;IACzC,CAAC;IACD,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,CAAA;IAC3D,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAA;AAC7C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,GAAW,EACX,UAGI,EAAE;IAEN,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAA;IACrD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,KAAK,CAAA;IACxC,MAAM,IAAI,GAAG,sBAAsB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;IAElD,IAAI,KAA+B,CAAA;IACnC,IAAI,CAAC;QACH,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE;YACvC,KAAK,EAAE,QAAQ;YACf,QAAQ,EAAE,IAAI;SACC,CAAC,CAAA;IACpB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACzD,CAAA;IACH,CAAC;IAED,4EAA4E;IAC5E,2EAA2E;IAC3E,MAAM,WAAW,GAAG,IAAI,OAAO,CAAQ,CAAC,YAAY,EAAE,EAAE;QACtD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,CAAA;IACjC,CAAC,CAAC,CAAA;IAEF,wEAAwE;IACxE,KAAK,CAAC,KAAK,EAAE,CAAA;IAEb,8EAA8E;IAC9E,6EAA6E;IAC7E,MAAM,OAAO,GAAG,IAAI,OAAO,CAAO,CAAC,WAAW,EAAE,EAAE;QAChD,YAAY,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAA;IACvC,CAAC,CAAC,CAAA;IAEF,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAA;IACzD,OAAO,MAAM,KAAK,IAAI;QACpB,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE;QAClB,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,CAAA;AAC/C,CAAC"}