@archastro/astroshot 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,6 +13,10 @@ npx astroshot demo
13
13
  # Check Node, watched folders, app install/run state, Chromium, and macOS
14
14
  # Screen Recording. Each failure prints the exact fix command. Read-only.
15
15
  npx astroshot doctor
16
+
17
+ # Review the stream in your terminal (Ghostty, kitty, or WezTerm draw the
18
+ # pictures; ffmpeg plays movies). Same review.json as the macOS app.
19
+ npx astroshot review
16
20
  ```
17
21
 
18
22
  `demo` accepts `--feature <name>`, `--root <dir>`, and `--json`. `doctor`
package/bin/astroshot.mjs CHANGED
@@ -8,12 +8,14 @@ import path from "node:path";
8
8
  import { writeFixtureTemplate } from "./templates.mjs";
9
9
  import { demoHelp, runDemo } from "./demo.mjs";
10
10
  import { doctorHelp, runDoctor } from "./doctor.mjs";
11
+ import { readWatchConfiguration } from "./mac-preferences.mjs";
11
12
 
12
13
  function help() {
13
14
  console.log(`astroshot — one CLI for React, Ink, PTY stills, and movies
14
15
 
15
16
  Usage:
16
- astroshot demo [--feature <name>] [--root <dir>]
17
+ astroshot review [<dir>...] [--root <dir>] [--no-graphics]
18
+ astroshot demo [--feature <name>] [--root <dir>] [--dry-run] [--clean]
17
19
  astroshot doctor [--root <dir>] [--json]
18
20
  astroshot init react [fixture.tsx] [--force]
19
21
  astroshot init ink [fixture.tsx] [--force]
@@ -28,6 +30,7 @@ Usage:
28
30
  astroshot install-browser [--with-deps]
29
31
 
30
32
  Start here:
33
+ review Review the .astroshot/ stream in your terminal (Kitty graphics)
31
34
  demo Write a complete .astroshot/ example set (no prerequisites)
32
35
  doctor Check Node, watched folders, app, Chromium, permissions
33
36
 
@@ -104,6 +107,11 @@ Options:
104
107
  }
105
108
 
106
109
  function engineBin(mode) {
110
+ if (mode === "review") {
111
+ const entry = fileURLToPath(import.meta.resolve("@archastro/astroshot-review"));
112
+ const packageRoot = path.dirname(path.dirname(entry));
113
+ return path.join(packageRoot, "bin", "astroshot-review.mjs");
114
+ }
107
115
  if (mode === "movie") {
108
116
  const entry = fileURLToPath(import.meta.resolve("@archastro/movie-harness"));
109
117
  // package exports "." → dist/index.js → package root is two levels up from dist
@@ -189,6 +197,49 @@ function runInit(arguments_) {
189
197
  console.log(`Created ${result.label} fixture: ${result.absolutePath}`);
190
198
  }
191
199
 
200
+ /**
201
+ * `astroshot review`: the terminal tray. Without explicit roots it watches
202
+ * the same folders as the Astroshots app so both surfaces show one stream.
203
+ */
204
+ function runReview(arguments_) {
205
+ // `astroshot review help` mirrors the other modes; anything else is
206
+ // forwarded verbatim so a folder literally named "help" still works.
207
+ const wantsHelp =
208
+ arguments_[0] === "help" ||
209
+ arguments_.includes("-h") ||
210
+ arguments_.includes("--help");
211
+ const forwarded = wantsHelp ? ["--help"] : [...arguments_];
212
+ const hasRoots =
213
+ forwarded.includes("--root") ||
214
+ forwarded.some((value) => !value.startsWith("-"));
215
+ if (!hasRoots && !wantsHelp) {
216
+ const configuration = readWatchConfiguration();
217
+ if (configuration.available && configuration.roots.length > 0) {
218
+ for (const root of configuration.roots) forwarded.push("--root", root);
219
+ forwarded.push("--roots-source", "app");
220
+ }
221
+ }
222
+ let engine;
223
+ try {
224
+ engine = engineBin("review");
225
+ } catch (error) {
226
+ console.error(
227
+ `astroshot could not find the review tray engine (@archastro/astroshot-review): ${error instanceof Error ? error.message : error}`,
228
+ );
229
+ console.error("Reinstall @archastro/astroshot (or the unscoped astroshot package) and retry.");
230
+ process.exit(1);
231
+ }
232
+ const result = spawnSync(process.execPath, [engine, ...forwarded], {
233
+ stdio: "inherit",
234
+ });
235
+ if (result.error) {
236
+ console.error(`astroshot could not start the review tray: ${result.error.message}`);
237
+ process.exit(1);
238
+ }
239
+ if (result.signal) process.kill(process.pid, result.signal);
240
+ process.exit(result.status ?? 1);
241
+ }
242
+
192
243
  const [command, ...arguments_] = process.argv.slice(2);
193
244
 
194
245
  if (command === "-v" || command === "--version") {
@@ -254,6 +305,10 @@ if (command === "react" || command === "ink" || command === "tui" || command ===
254
305
  runEngine(canonicalMode, arguments_);
255
306
  }
256
307
 
308
+ if (command === "review" || command === "tray") {
309
+ runReview(arguments_);
310
+ }
311
+
257
312
  if (command === "movie") {
258
313
  // Always forward to movie-harness (including --help / which-source).
259
314
  const result = spawnSync(
package/bin/demo.mjs CHANGED
@@ -30,6 +30,8 @@ Options:
30
30
  --feature <name> Feature directory under .astroshot/ (default: ${DEFAULT_DEMO_FEATURE})
31
31
  --root <dir> Worktree root (default: git root, else cwd)
32
32
  --json Print the written paths as JSON
33
+ --dry-run Print planned paths without writing
34
+ --clean Accepted; does not delete yet
33
35
  -h, --help Show this help
34
36
 
35
37
  Writes two stills, one movie poster + video pair, and manifest.json using
@@ -112,14 +114,24 @@ export function loadDemoFixtures(fixturesDirectory = FIXTURES_DIR) {
112
114
  return index;
113
115
  }
114
116
 
117
+ function publicDemoResult(plan) {
118
+ return {
119
+ root: plan.root,
120
+ feature: plan.feature,
121
+ featureDirectory: plan.featureDirectory,
122
+ manifestPath: plan.manifestPath,
123
+ runId: plan.runId,
124
+ shots: plan.shots,
125
+ files: plan.files,
126
+ };
127
+ }
128
+
115
129
  /**
116
- * Write the demo set and return the paths, without printing anything.
117
- *
118
- * Each invocation is its own run: a new `run_id` with a fresh shot list, while
119
- * earlier numbered frames stay on disk as prior-run evidence. That matches the
120
- * documented lifecycle used by astroshot-capture and the movie harness.
130
+ * Read-only plan of the next demo write: same root/feature resolve, kebab-case
131
+ * check, fixtures, next sequence, and file names (including manifest.json).
132
+ * Does not mkdir or write.
121
133
  */
122
- export function writeDemo({
134
+ export function planDemo({
123
135
  root,
124
136
  feature = DEFAULT_DEMO_FEATURE,
125
137
  fixturesDirectory = FIXTURES_DIR,
@@ -130,7 +142,6 @@ export function writeDemo({
130
142
  const featureDirectory = path.join(resolvedRoot, ".astroshot", feature);
131
143
  const index = loadDemoFixtures(fixturesDirectory);
132
144
 
133
- fs.mkdirSync(featureDirectory, { recursive: true });
134
145
  const startSequence = nextSequence(featureDirectory);
135
146
  const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
136
147
  // The first sequence of this run keeps the id unique when two runs land in
@@ -140,11 +151,16 @@ export function writeDemo({
140
151
 
141
152
  const shots = [];
142
153
  const files = [];
154
+ const copies = [];
143
155
  index.shots.forEach((fixture, offset) => {
144
156
  const sequence = String(startSequence + offset).padStart(4, "0");
145
157
  const posterName = `${sequence}-${fixture.slug}.png`;
146
- copyAtomic(path.join(fixturesDirectory, fixture.asset), path.join(featureDirectory, posterName));
147
- files.push(path.join(featureDirectory, posterName));
158
+ const posterPath = path.join(featureDirectory, posterName);
159
+ copies.push({
160
+ source: path.join(fixturesDirectory, fixture.asset),
161
+ destination: posterPath,
162
+ });
163
+ files.push(posterPath);
148
164
 
149
165
  const shot = {
150
166
  id: sequence,
@@ -159,8 +175,12 @@ export function writeDemo({
159
175
  if (fixture.video) {
160
176
  const videoExtension = path.extname(fixture.video) || ".webm";
161
177
  const videoName = `${sequence}-${fixture.slug}${videoExtension}`;
162
- copyAtomic(path.join(fixturesDirectory, fixture.video), path.join(featureDirectory, videoName));
163
- files.push(path.join(featureDirectory, videoName));
178
+ const videoPath = path.join(featureDirectory, videoName);
179
+ copies.push({
180
+ source: path.join(fixturesDirectory, fixture.video),
181
+ destination: videoPath,
182
+ });
183
+ files.push(videoPath);
164
184
  shot.kind = "movie";
165
185
  shot.video = videoName;
166
186
  shot.duration_ms = fixture.duration_ms;
@@ -181,7 +201,6 @@ export function writeDemo({
181
201
  "Synthetic proof set written by `astroshot demo` — stills plus one journey movie.",
182
202
  shots,
183
203
  };
184
- writeAtomic(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
185
204
  files.push(manifestPath);
186
205
 
187
206
  return {
@@ -189,12 +208,31 @@ export function writeDemo({
189
208
  feature,
190
209
  featureDirectory,
191
210
  manifestPath,
211
+ manifest,
192
212
  runId,
193
213
  shots,
194
214
  files,
215
+ copies,
195
216
  };
196
217
  }
197
218
 
219
+ /**
220
+ * Write the demo set and return the paths, without printing anything.
221
+ *
222
+ * Each invocation is its own run: a new `run_id` with a fresh shot list, while
223
+ * earlier numbered frames stay on disk as prior-run evidence. That matches the
224
+ * documented lifecycle used by astroshot-capture and the movie harness.
225
+ */
226
+ export function writeDemo(options = {}) {
227
+ const plan = planDemo(options);
228
+ fs.mkdirSync(plan.featureDirectory, { recursive: true });
229
+ for (const { source, destination } of plan.copies) {
230
+ copyAtomic(source, destination);
231
+ }
232
+ writeAtomic(plan.manifestPath, `${JSON.stringify(plan.manifest, null, 2)}\n`);
233
+ return publicDemoResult(plan);
234
+ }
235
+
198
236
  function coverageAdvice(root) {
199
237
  const configuration = readWatchConfiguration();
200
238
  const coverage = evaluateWatchCoverage(root, configuration);
@@ -231,7 +269,13 @@ function coverageAdvice(root) {
231
269
  }
232
270
 
233
271
  export function runDemo(argv, { log = console.log } = {}) {
234
- const options = { feature: DEFAULT_DEMO_FEATURE, root: undefined, json: false };
272
+ const options = {
273
+ feature: DEFAULT_DEMO_FEATURE,
274
+ root: undefined,
275
+ json: false,
276
+ dryRun: false,
277
+ clean: false,
278
+ };
235
279
  for (let index = 0; index < argv.length; index += 1) {
236
280
  const token = argv[index];
237
281
  if (token === "-h" || token === "--help" || token === "help") {
@@ -242,6 +286,14 @@ export function runDemo(argv, { log = console.log } = {}) {
242
286
  options.json = true;
243
287
  continue;
244
288
  }
289
+ if (token === "--dry-run") {
290
+ options.dryRun = true;
291
+ continue;
292
+ }
293
+ if (token === "--clean") {
294
+ options.clean = true;
295
+ continue;
296
+ }
245
297
  if (token === "--feature" || token === "--root") {
246
298
  const value = argv[index + 1];
247
299
  if (!value || value.startsWith("-")) {
@@ -254,18 +306,22 @@ export function runDemo(argv, { log = console.log } = {}) {
254
306
  throw new Error(`Unknown demo argument: ${token}`);
255
307
  }
256
308
 
257
- const result = writeDemo(options);
309
+ const result = options.dryRun
310
+ ? publicDemoResult(planDemo(options))
311
+ : writeDemo(options);
258
312
  if (options.json) {
259
313
  log(JSON.stringify({ ...result, advice: coverageAdvice(result.root) }, null, 2));
260
314
  return 0;
261
315
  }
262
316
 
263
- const movies = result.shots.filter((shot) => shot.kind === "movie").length;
264
- const stills = result.shots.length - movies;
265
317
  log(`astroshot demo → ${result.featureDirectory}`);
266
318
  for (const file of result.files) {
267
319
  log(` ${path.relative(result.root, file)}`);
268
320
  }
321
+ if (options.dryRun) return 0;
322
+
323
+ const movies = result.shots.filter((shot) => shot.kind === "movie").length;
324
+ const stills = result.shots.length - movies;
269
325
  log("");
270
326
  log(
271
327
  `Wrote ${stills} still${stills === 1 ? "" : "s"}, ${movies} movie${movies === 1 ? "" : "s"} (poster + video), and manifest.json.`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archastro/astroshot",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "One CLI for deterministic React, terminal, and movie UI captures",
5
5
  "keywords": [
6
6
  "react",
@@ -72,8 +72,9 @@
72
72
  "registry": "https://registry.npmjs.org/"
73
73
  },
74
74
  "dependencies": {
75
- "@archastro/movie-harness": "0.2.1",
76
- "@archastro/react-shot": "0.2.1",
77
- "@archastro/tui-shot": "0.2.1"
75
+ "@archastro/astroshot-review": "0.2.2",
76
+ "@archastro/movie-harness": "0.2.2",
77
+ "@archastro/react-shot": "0.2.2",
78
+ "@archastro/tui-shot": "0.2.2"
78
79
  }
79
80
  }