astroshot 0.2.1

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 (119) hide show
  1. package/README.md +87 -0
  2. package/bin/astroshot.mjs +71 -0
  3. package/node_modules/@archastro/astroshot/LICENSE +21 -0
  4. package/node_modules/@archastro/astroshot/README.md +96 -0
  5. package/node_modules/@archastro/astroshot/bin/astroshot.mjs +276 -0
  6. package/node_modules/@archastro/astroshot/bin/demo.mjs +275 -0
  7. package/node_modules/@archastro/astroshot/bin/doctor.mjs +492 -0
  8. package/node_modules/@archastro/astroshot/bin/mac-preferences.mjs +346 -0
  9. package/node_modules/@archastro/astroshot/bin/templates.mjs +186 -0
  10. package/node_modules/@archastro/astroshot/fixtures/demo/fixtures.json +42 -0
  11. package/node_modules/@archastro/astroshot/fixtures/demo/journey.png +0 -0
  12. package/node_modules/@archastro/astroshot/fixtures/demo/journey.webm +0 -0
  13. package/node_modules/@archastro/astroshot/fixtures/demo/next-steps.png +0 -0
  14. package/node_modules/@archastro/astroshot/fixtures/demo/welcome.png +0 -0
  15. package/node_modules/@archastro/astroshot/ink.d.ts +5 -0
  16. package/node_modules/@archastro/astroshot/ink.js +1 -0
  17. package/node_modules/@archastro/astroshot/package.json +79 -0
  18. package/node_modules/@archastro/astroshot/pty.d.ts +8 -0
  19. package/node_modules/@archastro/astroshot/pty.js +1 -0
  20. package/node_modules/@archastro/astroshot/react.d.ts +1 -0
  21. package/node_modules/@archastro/astroshot/react.js +1 -0
  22. package/node_modules/@archastro/astroshot/tui.d.ts +1 -0
  23. package/node_modules/@archastro/astroshot/tui.js +1 -0
  24. package/node_modules/@archastro/movie-harness/README.md +138 -0
  25. package/node_modules/@archastro/movie-harness/bin/astroshot-movie.mjs +5 -0
  26. package/node_modules/@archastro/movie-harness/dist/cli.d.ts +1 -0
  27. package/node_modules/@archastro/movie-harness/dist/cli.js +389 -0
  28. package/node_modules/@archastro/movie-harness/dist/encode.d.ts +14 -0
  29. package/node_modules/@archastro/movie-harness/dist/encode.js +184 -0
  30. package/node_modules/@archastro/movie-harness/dist/index.d.ts +14 -0
  31. package/node_modules/@archastro/movie-harness/dist/index.js +11 -0
  32. package/node_modules/@archastro/movie-harness/dist/paths.d.ts +9 -0
  33. package/node_modules/@archastro/movie-harness/dist/paths.js +57 -0
  34. package/node_modules/@archastro/movie-harness/dist/png.d.ts +8 -0
  35. package/node_modules/@archastro/movie-harness/dist/png.js +79 -0
  36. package/node_modules/@archastro/movie-harness/dist/session.d.ts +45 -0
  37. package/node_modules/@archastro/movie-harness/dist/session.js +170 -0
  38. package/node_modules/@archastro/movie-harness/dist/sink.d.ts +7 -0
  39. package/node_modules/@archastro/movie-harness/dist/sink.js +113 -0
  40. package/node_modules/@archastro/movie-harness/dist/source-help.d.ts +29 -0
  41. package/node_modules/@archastro/movie-harness/dist/source-help.js +222 -0
  42. package/node_modules/@archastro/movie-harness/dist/sources/browser.d.ts +7 -0
  43. package/node_modules/@archastro/movie-harness/dist/sources/browser.js +87 -0
  44. package/node_modules/@archastro/movie-harness/dist/sources/desktop-macos.d.ts +110 -0
  45. package/node_modules/@archastro/movie-harness/dist/sources/desktop-macos.js +505 -0
  46. package/node_modules/@archastro/movie-harness/dist/sources/frames-store.d.ts +19 -0
  47. package/node_modules/@archastro/movie-harness/dist/sources/frames-store.js +165 -0
  48. package/node_modules/@archastro/movie-harness/dist/sources/pty.d.ts +15 -0
  49. package/node_modules/@archastro/movie-harness/dist/sources/pty.js +330 -0
  50. package/node_modules/@archastro/movie-harness/dist/terminal-paint.d.ts +27 -0
  51. package/node_modules/@archastro/movie-harness/dist/terminal-paint.js +190 -0
  52. package/node_modules/@archastro/movie-harness/dist/types.d.ts +140 -0
  53. package/node_modules/@archastro/movie-harness/dist/types.js +1 -0
  54. package/node_modules/@archastro/movie-harness/native/macos/WindowTools.swift +164 -0
  55. package/node_modules/@archastro/movie-harness/package.json +67 -0
  56. package/node_modules/@archastro/react-shot/LICENSE +21 -0
  57. package/node_modules/@archastro/react-shot/README.md +181 -0
  58. package/node_modules/@archastro/react-shot/bin/react-shot.mjs +2 -0
  59. package/node_modules/@archastro/react-shot/dist/batch-paths.d.ts +4 -0
  60. package/node_modules/@archastro/react-shot/dist/batch-paths.d.ts.map +1 -0
  61. package/node_modules/@archastro/react-shot/dist/batch-paths.js +34 -0
  62. package/node_modules/@archastro/react-shot/dist/batch-paths.js.map +1 -0
  63. package/node_modules/@archastro/react-shot/dist/cli.d.ts +3 -0
  64. package/node_modules/@archastro/react-shot/dist/cli.d.ts.map +1 -0
  65. package/node_modules/@archastro/react-shot/dist/cli.js +217 -0
  66. package/node_modules/@archastro/react-shot/dist/cli.js.map +1 -0
  67. package/node_modules/@archastro/react-shot/dist/config.d.ts +5 -0
  68. package/node_modules/@archastro/react-shot/dist/config.d.ts.map +1 -0
  69. package/node_modules/@archastro/react-shot/dist/config.js +75 -0
  70. package/node_modules/@archastro/react-shot/dist/config.js.map +1 -0
  71. package/node_modules/@archastro/react-shot/dist/create-server.d.ts +13 -0
  72. package/node_modules/@archastro/react-shot/dist/create-server.d.ts.map +1 -0
  73. package/node_modules/@archastro/react-shot/dist/create-server.js +254 -0
  74. package/node_modules/@archastro/react-shot/dist/create-server.js.map +1 -0
  75. package/node_modules/@archastro/react-shot/dist/index.d.ts +4 -0
  76. package/node_modules/@archastro/react-shot/dist/index.d.ts.map +1 -0
  77. package/node_modules/@archastro/react-shot/dist/index.js +3 -0
  78. package/node_modules/@archastro/react-shot/dist/index.js.map +1 -0
  79. package/node_modules/@archastro/react-shot/dist/meta.d.ts +9 -0
  80. package/node_modules/@archastro/react-shot/dist/meta.d.ts.map +1 -0
  81. package/node_modules/@archastro/react-shot/dist/meta.js +23 -0
  82. package/node_modules/@archastro/react-shot/dist/meta.js.map +1 -0
  83. package/node_modules/@archastro/react-shot/dist/shot.d.ts +4 -0
  84. package/node_modules/@archastro/react-shot/dist/shot.d.ts.map +1 -0
  85. package/node_modules/@archastro/react-shot/dist/shot.js +259 -0
  86. package/node_modules/@archastro/react-shot/dist/shot.js.map +1 -0
  87. package/node_modules/@archastro/react-shot/dist/stubs.d.ts +6 -0
  88. package/node_modules/@archastro/react-shot/dist/stubs.d.ts.map +1 -0
  89. package/node_modules/@archastro/react-shot/dist/stubs.js +49 -0
  90. package/node_modules/@archastro/react-shot/dist/stubs.js.map +1 -0
  91. package/node_modules/@archastro/react-shot/dist/types.d.ts +79 -0
  92. package/node_modules/@archastro/react-shot/dist/types.d.ts.map +1 -0
  93. package/node_modules/@archastro/react-shot/dist/types.js +2 -0
  94. package/node_modules/@archastro/react-shot/dist/types.js.map +1 -0
  95. package/node_modules/@archastro/react-shot/host/index.html +22 -0
  96. package/node_modules/@archastro/react-shot/package.json +81 -0
  97. package/node_modules/@archastro/tui-shot/LICENSE +21 -0
  98. package/node_modules/@archastro/tui-shot/README.md +145 -0
  99. package/node_modules/@archastro/tui-shot/bin/tui-shot.mjs +23 -0
  100. package/node_modules/@archastro/tui-shot/dist/batch-paths.d.ts +3 -0
  101. package/node_modules/@archastro/tui-shot/dist/batch-paths.js +33 -0
  102. package/node_modules/@archastro/tui-shot/dist/cli.d.ts +2 -0
  103. package/node_modules/@archastro/tui-shot/dist/cli.js +224 -0
  104. package/node_modules/@archastro/tui-shot/dist/index.d.ts +3 -0
  105. package/node_modules/@archastro/tui-shot/dist/index.js +2 -0
  106. package/node_modules/@archastro/tui-shot/dist/pty-exit-wrapper.d.ts +1 -0
  107. package/node_modules/@archastro/tui-shot/dist/pty-exit-wrapper.js +50 -0
  108. package/node_modules/@archastro/tui-shot/dist/pty-shot.d.ts +2 -0
  109. package/node_modules/@archastro/tui-shot/dist/pty-shot.js +483 -0
  110. package/node_modules/@archastro/tui-shot/dist/render-ink.d.ts +10 -0
  111. package/node_modules/@archastro/tui-shot/dist/render-ink.js +87 -0
  112. package/node_modules/@archastro/tui-shot/dist/shot.d.ts +24 -0
  113. package/node_modules/@archastro/tui-shot/dist/shot.js +250 -0
  114. package/node_modules/@archastro/tui-shot/dist/terminal-html.d.ts +14 -0
  115. package/node_modules/@archastro/tui-shot/dist/terminal-html.js +144 -0
  116. package/node_modules/@archastro/tui-shot/dist/types.d.ts +81 -0
  117. package/node_modules/@archastro/tui-shot/dist/types.js +1 -0
  118. package/node_modules/@archastro/tui-shot/package.json +90 -0
  119. package/package.json +68 -0
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # `astroshot`
2
+
3
+ One command for deterministic React, Ink, and arbitrary terminal screenshots —
4
+ plus **journey movies** into `.astroshot/`.
5
+
6
+ ```bash
7
+ npx astroshot --help
8
+ npx astroshot init react
9
+ npx astroshot react ./react.shot.tsx -o ./react.png
10
+ npx astroshot ink ./ink.shot.tsx -o ./ink.png
11
+ npx astroshot pty ./pty.shot.yaml -o ./terminal.png
12
+ npx astroshot movie which-source "ratatui truecolor dashboard"
13
+ ```
14
+
15
+ No registry flags, no scope configuration, no `--registry` override.
16
+
17
+ ## What this package is
18
+
19
+ `astroshot` is the public, unscoped entry point for
20
+ [`@archastro/astroshot`](https://www.npmjs.com/package/@archastro/astroshot).
21
+ It ships the unified CLI and all three capture engines
22
+ (`@archastro/react-shot`, `@archastro/tui-shot`, `@archastro/movie-harness`)
23
+ *inside its own tarball* using npm `bundleDependencies`.
24
+
25
+ That matters because many developers have a `~/.npmrc` that maps the whole
26
+ `@archastro` scope to a private registry:
27
+
28
+ ```
29
+ @archastro:registry=https://npm.pkg.github.com
30
+ ```
31
+
32
+ With that config, `npx @archastro/astroshot` fails with **E404** — npm never
33
+ asks the public registry for the package at all. Because this package bundles
34
+ everything scoped, nothing scoped is fetched at install time, so a plain
35
+ `npx astroshot` works regardless of scope configuration.
36
+
37
+ The scoped packages remain fully supported and unchanged; use them directly if
38
+ you prefer, or if you already have the scope pointed at the public registry.
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ # one-off
44
+ npx astroshot --help
45
+
46
+ # project dev dependency
47
+ npm install --save-dev astroshot
48
+ ```
49
+
50
+ Install the shared Chromium runtime once before browser-backed captures:
51
+
52
+ ```bash
53
+ npx astroshot install-browser
54
+ ```
55
+
56
+ On Linux CI images that also need Chromium's system libraries, add
57
+ `--with-deps`.
58
+
59
+ ## Peer dependencies you supply
60
+
61
+ This package deliberately does **not** pin your framework versions. Install the
62
+ peers for the modes you use:
63
+
64
+ | Mode | Peers |
65
+ |------|-------|
66
+ | `astroshot react` | `react`, `react-dom` (>=18) |
67
+ | `astroshot ink` | `ink@^7.1`, `react@^19` |
68
+ | `astroshot pty`, `astroshot movie` | none |
69
+
70
+ ```bash
71
+ npm install --save-dev ink@^7.1 react@^19
72
+ ```
73
+
74
+ `node-pty` is an **optional** dependency. PTY and movie-PTY capture need it;
75
+ every other mode works without it, so an environment that cannot build the
76
+ native addon still installs successfully.
77
+
78
+ ## Documentation
79
+
80
+ Full fixture APIs, the PTY action contract, manifest formats, and movie sources
81
+ are documented in the
82
+ [repository README](https://github.com/ArchAstro/astroshots#readme) and in
83
+ [`@archastro/astroshot`](https://www.npmjs.com/package/@archastro/astroshot).
84
+
85
+ Fixtures, imported modules, and PTY commands execute with the current user's
86
+ permissions. Review untrusted files before capture and never include
87
+ credentials or private customer data in fixtures or generated images.
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Unscoped `astroshot` entry point.
4
+ *
5
+ * This package exists so `npx astroshot ...` works with no registry flags. The
6
+ * @archastro packages ship *inside* this tarball through bundleDependencies, so
7
+ * a developer whose ~/.npmrc maps @archastro to a private registry can still
8
+ * install and run the CLI. See docs/UNSCOPED-CLI-DESIGN.md.
9
+ *
10
+ * The only job of this file is to hand every argument to the real CLI and
11
+ * faithfully return its stdout, stderr, exit code, and terminating signal.
12
+ */
13
+ import { spawnSync } from "node:child_process";
14
+ import fs from "node:fs";
15
+ import path from "node:path";
16
+ import process from "node:process";
17
+ import { fileURLToPath } from "node:url";
18
+
19
+ const packageRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
20
+ const CLI_RELATIVE_PATH = path.join(
21
+ "@archastro",
22
+ "astroshot",
23
+ "bin",
24
+ "astroshot.mjs",
25
+ );
26
+
27
+ /**
28
+ * Look for the bundled CLI next to this package first (the published layout
29
+ * puts it in ./node_modules), then walk up through parent node_modules
30
+ * directories so the workspace checkout resolves its symlinked sibling.
31
+ */
32
+ function resolveCli() {
33
+ const searched = [];
34
+ let directory = packageRoot;
35
+ for (;;) {
36
+ const candidate = path.join(directory, "node_modules", CLI_RELATIVE_PATH);
37
+ searched.push(candidate);
38
+ if (fs.existsSync(candidate)) return candidate;
39
+ const parent = path.dirname(directory);
40
+ if (parent === directory) break;
41
+ directory = parent;
42
+ }
43
+ return { searched };
44
+ }
45
+
46
+ const cli = resolveCli();
47
+ if (typeof cli !== "string") {
48
+ console.error(
49
+ "astroshot: the bundled @archastro/astroshot CLI is missing from this " +
50
+ "installation. Reinstall with `npm install astroshot`.\nLooked in:\n" +
51
+ cli.searched.map((entry) => ` ${entry}`).join("\n"),
52
+ );
53
+ process.exit(1);
54
+ }
55
+
56
+ // stdio: "inherit" hands the real file descriptors to the CLI, so stdout and
57
+ // stderr stay unbuffered, interleaved, and TTY-aware exactly as if the user had
58
+ // run @archastro/astroshot directly.
59
+ const result = spawnSync(process.execPath, [cli, ...process.argv.slice(2)], {
60
+ stdio: "inherit",
61
+ });
62
+
63
+ if (result.error) {
64
+ console.error(`astroshot could not start the CLI: ${result.error.message}`);
65
+ process.exit(1);
66
+ }
67
+ // Re-raise the child's signal so `kill`/Ctrl-C semantics survive the hop.
68
+ if (result.signal) {
69
+ process.kill(process.pid, result.signal);
70
+ }
71
+ process.exit(result.status ?? 1);
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ArchAstro
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,96 @@
1
+ # `@archastro/astroshot`
2
+
3
+ One command for deterministic React, Ink, and arbitrary terminal screenshots —
4
+ plus **journey movies** into `.astroshot/`.
5
+
6
+ ## Start here (no prerequisites)
7
+
8
+ ```bash
9
+ # Write a real .astroshot/ set: stills + movie poster/video + manifest.json.
10
+ # No Chromium download, no assets of your own; works with the app closed.
11
+ npx astroshot demo
12
+
13
+ # Check Node, watched folders, app install/run state, Chromium, and macOS
14
+ # Screen Recording. Each failure prints the exact fix command. Read-only.
15
+ npx astroshot doctor
16
+ ```
17
+
18
+ `demo` accepts `--feature <name>`, `--root <dir>`, and `--json`. `doctor`
19
+ accepts `--root <dir>`, `--json`, and `--skip-screen`, and exits non-zero when a
20
+ required check fails. Watched-folder coverage comes from the Astroshots app's
21
+ own live configuration, so an "empty tray" resolves to one of: first-launch
22
+ setup never completed, this project is outside every watched folder, or the
23
+ project is watched and the problem is elsewhere.
24
+
25
+ ## Capture
26
+
27
+ ```bash
28
+ npx astroshot init react
29
+ npx astroshot init ink
30
+ npx astroshot init pty
31
+
32
+ npx astroshot react ./react.shot.tsx -o ./react.png
33
+ npx astroshot ink ./ink.shot.tsx -o ./ink.png
34
+ npx astroshot pty ./pty.shot.yaml -o ./terminal.png
35
+ ```
36
+
37
+ ## Movies (`astroshot movie`)
38
+
39
+ Same binary. Agents: **run `astroshot movie which-source "…"` first**.
40
+
41
+ | You need to record… | `--source` |
42
+ |---------------------|------------|
43
+ | Web / SPA / agent-browser | `browser` |
44
+ | TUI / CLI / truecolor terminal | `pty` (never desktop of Terminal.app) |
45
+ | Native macOS app window | `desktop.window` |
46
+ | Your own PNG sequence | `frames` |
47
+
48
+ ```bash
49
+ astroshot movie --help
50
+ astroshot movie which-source "ratatui truecolor dashboard"
51
+ astroshot movie run --source browser --feature web --slug home --url https://example.com
52
+ astroshot movie run --source desktop.window --feature app --slug onboard \
53
+ --bundle-id com.example.App --duration-ms 4000
54
+ astroshot movie list-windows # macOS
55
+ ```
56
+
57
+ `desktop.window` uses macOS `/usr/sbin/screencapture` (already on the system)
58
+ plus a Swift window list shipped in the package — no separate capture binary
59
+ download. Requires Screen Recording permission for your terminal/IDE.
60
+
61
+ Use `react` for isolated browser components, `ink` for in-process Ink fixture
62
+ trees, and `pty` for executable terminal applications such as Ratatui, Bubble
63
+ Tea, Textual, and curses programs. `tui` remains an alias for `ink` for
64
+ compatibility.
65
+
66
+ Ink fixtures require project-local peers:
67
+
68
+ ```bash
69
+ npm install --save-dev ink@^7.1 react@^19
70
+ ```
71
+
72
+ Install the shared Chromium runtime once:
73
+
74
+ ```bash
75
+ npx astroshot install-browser
76
+ ```
77
+
78
+ Use `react batch <manifest>` or `ink batch <manifest>` for maintained fixture
79
+ sets. Run `<mode> --help` for mode-specific options.
80
+
81
+ Fixture types are available from the unified package:
82
+
83
+ ```tsx
84
+ import type { ReactShotFixture } from "@archastro/astroshot/react";
85
+ import type { InkShotFixture } from "@archastro/astroshot/ink";
86
+ ```
87
+
88
+ PTY YAML and JSON files launch `command` directly, without a shell. Their
89
+ `actions` can wait for visible text, send named keys or literal text, and
90
+ pause for bounded durations. Paths in `cwd` are relative to the fixture.
91
+ On Windows, point `command` at an `.exe`; `.cmd` and `.bat` files are rejected
92
+ because running them would introduce an implicit shell.
93
+
94
+ Fixtures, imported modules, and PTY commands execute with the current user's
95
+ permissions. Review untrusted files before capture and never include
96
+ credentials or private customer data in fixtures or generated images.
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import fs from "node:fs";
5
+ import { fileURLToPath } from "node:url";
6
+ import path from "node:path";
7
+
8
+ import { writeFixtureTemplate } from "./templates.mjs";
9
+ import { demoHelp, runDemo } from "./demo.mjs";
10
+ import { doctorHelp, runDoctor } from "./doctor.mjs";
11
+
12
+ function help() {
13
+ console.log(`astroshot — one CLI for React, Ink, PTY stills, and movies
14
+
15
+ Usage:
16
+ astroshot demo [--feature <name>] [--root <dir>]
17
+ astroshot doctor [--root <dir>] [--json]
18
+ astroshot init react [fixture.tsx] [--force]
19
+ astroshot init ink [fixture.tsx] [--force]
20
+ astroshot init pty [fixture.yaml] [--force]
21
+ astroshot react <fixture.tsx> -o <out.png> [options]
22
+ astroshot react shot <fixture.tsx> -o <out.png> [options]
23
+ astroshot react batch <manifest.yaml|json> [options]
24
+ astroshot ink <fixture.tsx> -o <out.png> [options]
25
+ astroshot ink batch <manifest.yaml|json> [options]
26
+ astroshot pty <fixture.yaml|json> -o <out.png> [options]
27
+ astroshot movie <command> [options]
28
+ astroshot install-browser [--with-deps]
29
+
30
+ Start here:
31
+ demo Write a complete .astroshot/ example set (no prerequisites)
32
+ doctor Check Node, watched folders, app, Chromium, permissions
33
+
34
+ Commands:
35
+ react Capture an isolated React component (still PNG)
36
+ ink Capture an Ink component fixture (still PNG)
37
+ pty Capture any executable in a pseudoterminal (still PNG)
38
+ movie Record a journey movie into .astroshot/ (poster + video)
39
+ init Generate a React, Ink, or PTY fixture template
40
+ install-browser Install the shared Chromium runtime
41
+
42
+ Movie sources (see "astroshot movie which-source"):
43
+ browser Web / agent-browser / Playwright viewport
44
+ pty Truecolor TUI/CLI (never screenshot Terminal.app)
45
+ desktop.window Native macOS app window (uses OS screencapture)
46
+ frames Push your own PNG/JPEG sequence
47
+
48
+ Compatibility: "astroshot tui" remains an alias for "astroshot ink".
49
+ Run "astroshot <mode> --help" for mode options.`);
50
+ }
51
+
52
+ function modeHelp(mode) {
53
+ if (mode === "react") {
54
+ console.log(`astroshot react — deterministic React component screenshots
55
+
56
+ Usage:
57
+ astroshot init react [fixture.tsx] [--force]
58
+ astroshot react <fixture.tsx> -o <out.png> [options]
59
+ astroshot react batch <manifest.yaml|json> [options]
60
+
61
+ Options:
62
+ -o, --out <path> Output PNG path
63
+ --root <dir> Package root for imports and aliases
64
+ --config <path> Fixture configuration path
65
+ --width <px> Override viewport width
66
+ --height <px> Override viewport height
67
+ --headed Show Chromium for debugging
68
+ -h, --help Show this help`);
69
+ return;
70
+ }
71
+
72
+ if (mode === "ink") {
73
+ console.log(`astroshot ink — deterministic Ink component screenshots
74
+
75
+ Usage:
76
+ astroshot init ink [fixture.tsx] [--force]
77
+ astroshot ink <fixture.tsx> -o <out.png> [options]
78
+ astroshot ink batch <manifest.yaml|json> [options]
79
+
80
+ Options:
81
+ -o, --out <path> Output PNG path
82
+ --cols <count> Override terminal columns
83
+ --rows <count> Override terminal rows
84
+ --scale <factor> Override PNG device scale factor
85
+ --out-dir <path> Override batch output directory
86
+ --headed Show Chromium for debugging
87
+ -h, --help Show this help`);
88
+ return;
89
+ }
90
+
91
+ console.log(`astroshot pty — screenshots of arbitrary terminal programs
92
+
93
+ Usage:
94
+ astroshot init pty [fixture.yaml] [--force]
95
+ astroshot pty <fixture.yaml|json> -o <out.png> [options]
96
+
97
+ Options:
98
+ -o, --out <path> Output PNG path
99
+ --cols <count> Override terminal columns
100
+ --rows <count> Override terminal rows
101
+ --scale <factor> Override PNG device scale factor
102
+ --headed Show Chromium for debugging
103
+ -h, --help Show this help`);
104
+ }
105
+
106
+ function engineBin(mode) {
107
+ if (mode === "movie") {
108
+ const entry = fileURLToPath(import.meta.resolve("@archastro/movie-harness"));
109
+ // package exports "." → dist/index.js → package root is two levels up from dist
110
+ const packageRoot = path.dirname(path.dirname(entry));
111
+ return path.join(packageRoot, "bin", "astroshot-movie.mjs");
112
+ }
113
+ const packageName =
114
+ mode === "react" ? "@archastro/react-shot" : "@archastro/tui-shot";
115
+ const executable = mode === "react" ? "react-shot.mjs" : "tui-shot.mjs";
116
+ const entry = fileURLToPath(import.meta.resolve(packageName));
117
+ const packageRoot = path.dirname(path.dirname(entry));
118
+ return path.join(packageRoot, "bin", executable);
119
+ }
120
+
121
+ function runEngine(mode, arguments_) {
122
+ const engineMode = mode === "react" ? "react" : "tui";
123
+ const engineArguments =
124
+ mode === "pty" ? ["pty", ...arguments_] : arguments_;
125
+ const normalized =
126
+ mode !== "pty" &&
127
+ engineArguments[0] &&
128
+ /\.[cm]?tsx?$/.test(engineArguments[0])
129
+ ? ["shot", ...engineArguments]
130
+ : engineArguments;
131
+ const result = spawnSync(
132
+ process.execPath,
133
+ [engineBin(engineMode), ...normalized],
134
+ { stdio: "inherit" },
135
+ );
136
+ if (result.error) {
137
+ console.error(`astroshot could not start ${mode} capture: ${result.error.message}`);
138
+ process.exit(1);
139
+ }
140
+ if (result.signal) process.kill(process.pid, result.signal);
141
+ process.exit(result.status ?? 1);
142
+ }
143
+
144
+ function initHelp() {
145
+ console.log(`astroshot init — generate a fixture template
146
+
147
+ Usage:
148
+ astroshot init react [fixture.tsx] [--force]
149
+ astroshot init ink [fixture.tsx] [--force]
150
+ astroshot init pty [fixture.yaml] [--force]
151
+
152
+ Defaults:
153
+ react.shot.tsx
154
+ ink.shot.tsx
155
+ pty.shot.yaml
156
+
157
+ Existing files are never replaced unless --force is passed.`);
158
+ }
159
+
160
+ function runInit(arguments_) {
161
+ if (
162
+ arguments_.length === 0 ||
163
+ arguments_[0] === "help" ||
164
+ arguments_[0] === "-h" ||
165
+ arguments_[0] === "--help"
166
+ ) {
167
+ initHelp();
168
+ process.exit(arguments_.length === 0 ? 1 : 0);
169
+ }
170
+
171
+ const mode = arguments_[0];
172
+ const rest = arguments_.slice(1);
173
+ const force = rest.includes("--force") || rest.includes("-f");
174
+ const unknownFlags = rest.filter(
175
+ (value) => value.startsWith("-") && value !== "--force" && value !== "-f",
176
+ );
177
+ if (unknownFlags.length) {
178
+ throw new Error(`Unknown init flag: ${unknownFlags[0]}`);
179
+ }
180
+ const positionals = rest.filter((value) => !value.startsWith("-"));
181
+ if (positionals.length > 1) {
182
+ throw new Error("init accepts at most one output path");
183
+ }
184
+ const result = writeFixtureTemplate({
185
+ mode,
186
+ outputPath: positionals[0],
187
+ force,
188
+ });
189
+ console.log(`Created ${result.label} fixture: ${result.absolutePath}`);
190
+ }
191
+
192
+ const [command, ...arguments_] = process.argv.slice(2);
193
+
194
+ if (command === "-v" || command === "--version") {
195
+ const packageJSON = JSON.parse(
196
+ fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"),
197
+ );
198
+ console.log(packageJSON.version);
199
+ process.exit(0);
200
+ }
201
+
202
+ if (!command || command === "help" || command === "-h" || command === "--help") {
203
+ help();
204
+ process.exit(command ? 0 : 1);
205
+ }
206
+
207
+ if (command === "demo") {
208
+ try {
209
+ process.exit(runDemo(arguments_));
210
+ } catch (error) {
211
+ console.error(error instanceof Error ? error.message : error);
212
+ console.error("");
213
+ console.error(demoHelp());
214
+ process.exit(1);
215
+ }
216
+ }
217
+
218
+ if (command === "doctor") {
219
+ try {
220
+ process.exit(runDoctor(arguments_));
221
+ } catch (error) {
222
+ console.error(error instanceof Error ? error.message : error);
223
+ console.error("");
224
+ console.error(doctorHelp());
225
+ process.exit(1);
226
+ }
227
+ }
228
+
229
+ if (command === "install-browser") {
230
+ runEngine("react", ["install-browser", ...arguments_]);
231
+ }
232
+
233
+ if (command === "init") {
234
+ try {
235
+ runInit(arguments_);
236
+ } catch (error) {
237
+ console.error(error instanceof Error ? error.message : error);
238
+ process.exit(1);
239
+ }
240
+ process.exit(0);
241
+ }
242
+
243
+ if (command === "react" || command === "ink" || command === "tui" || command === "pty") {
244
+ const canonicalMode = command === "tui" ? "ink" : command;
245
+ if (
246
+ arguments_.length === 0 ||
247
+ arguments_[0] === "help" ||
248
+ arguments_[0] === "-h" ||
249
+ arguments_[0] === "--help"
250
+ ) {
251
+ modeHelp(canonicalMode);
252
+ process.exit(arguments_.length === 0 ? 1 : 0);
253
+ }
254
+ runEngine(canonicalMode, arguments_);
255
+ }
256
+
257
+ if (command === "movie") {
258
+ // Always forward to movie-harness (including --help / which-source).
259
+ const result = spawnSync(
260
+ process.execPath,
261
+ [engineBin("movie"), ...arguments_],
262
+ { stdio: "inherit" },
263
+ );
264
+ if (result.error) {
265
+ console.error(
266
+ `astroshot could not start movie harness: ${result.error.message}`,
267
+ );
268
+ process.exit(1);
269
+ }
270
+ if (result.signal) process.kill(process.pid, result.signal);
271
+ process.exit(result.status ?? 1);
272
+ }
273
+
274
+ console.error(`Unknown command: ${command}`);
275
+ help();
276
+ process.exit(1);