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
@@ -0,0 +1,275 @@
1
+ /**
2
+ * `astroshot demo` — seed a real .astroshot/<feature>/ set with zero
3
+ * prerequisites.
4
+ *
5
+ * The payload is bundled PNG/WebM bytes under fixtures/demo/, so this command
6
+ * needs no managed Chromium, no ffmpeg, and no user-supplied assets. It only
7
+ * writes files, so it also works while the macOS app is closed; the caller is
8
+ * told where to look.
9
+ */
10
+ import fs from "node:fs";
11
+ import path from "node:path";
12
+ import { execFileSync } from "node:child_process";
13
+ import { fileURLToPath } from "node:url";
14
+
15
+ import {
16
+ evaluateWatchCoverage,
17
+ readWatchConfiguration,
18
+ } from "./mac-preferences.mjs";
19
+
20
+ const FIXTURES_DIR = fileURLToPath(new URL("../fixtures/demo/", import.meta.url));
21
+ export const DEFAULT_DEMO_FEATURE = "astroshot-demo";
22
+
23
+ export function demoHelp() {
24
+ return `astroshot demo — write a complete .astroshot/ example set (no prerequisites)
25
+
26
+ Usage:
27
+ astroshot demo [options]
28
+
29
+ Options:
30
+ --feature <name> Feature directory under .astroshot/ (default: ${DEFAULT_DEMO_FEATURE})
31
+ --root <dir> Worktree root (default: git root, else cwd)
32
+ --json Print the written paths as JSON
33
+ -h, --help Show this help
34
+
35
+ Writes two stills, one movie poster + video pair, and manifest.json using
36
+ bundled fixtures. No Chromium download, no ffmpeg, no assets of your own.
37
+ Re-running starts a fresh run and appends new numbered frames.
38
+
39
+ After it runs, open the Astroshots menu-bar icon → Shots. If nothing appears,
40
+ run "astroshot doctor".`;
41
+ }
42
+
43
+ function resolveRoot(explicitRoot) {
44
+ if (explicitRoot) return path.resolve(explicitRoot);
45
+ try {
46
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], {
47
+ encoding: "utf8",
48
+ stdio: ["ignore", "pipe", "ignore"],
49
+ }).trim();
50
+ } catch {
51
+ return process.cwd();
52
+ }
53
+ }
54
+
55
+ function assertKebabCase(value, label) {
56
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
57
+ throw new Error(
58
+ `${label} must be kebab-case [a-z0-9-]+, got ${JSON.stringify(value)}`,
59
+ );
60
+ }
61
+ }
62
+
63
+ function nextSequence(featureDirectory) {
64
+ let max = 0;
65
+ const entries = fs.existsSync(featureDirectory)
66
+ ? fs.readdirSync(featureDirectory)
67
+ : [];
68
+ for (const name of entries) {
69
+ const match = /^(\d{4})-/.exec(name);
70
+ if (match) max = Math.max(max, Number(match[1]));
71
+ }
72
+ return max + 1;
73
+ }
74
+
75
+ function writeAtomic(filePath, contents) {
76
+ const temporary = path.join(
77
+ path.dirname(filePath),
78
+ `.${path.basename(filePath)}.tmp.${process.pid}.${Date.now()}`,
79
+ );
80
+ fs.writeFileSync(temporary, contents);
81
+ fs.renameSync(temporary, filePath);
82
+ }
83
+
84
+ function copyAtomic(source, destination) {
85
+ const temporary = path.join(
86
+ path.dirname(destination),
87
+ `.${path.basename(destination)}.tmp.${process.pid}.${Date.now()}`,
88
+ );
89
+ fs.copyFileSync(source, temporary);
90
+ fs.renameSync(temporary, destination);
91
+ }
92
+
93
+ export function loadDemoFixtures(fixturesDirectory = FIXTURES_DIR) {
94
+ const indexPath = path.join(fixturesDirectory, "fixtures.json");
95
+ if (!fs.existsSync(indexPath)) {
96
+ throw new Error(
97
+ `bundled demo fixtures are missing at ${fixturesDirectory}. Reinstall @archastro/astroshot.`,
98
+ );
99
+ }
100
+ const index = JSON.parse(fs.readFileSync(indexPath, "utf8"));
101
+ if (!Array.isArray(index.shots) || index.shots.length === 0) {
102
+ throw new Error(`bundled demo fixtures are empty: ${indexPath}`);
103
+ }
104
+ for (const shot of index.shots) {
105
+ for (const asset of [shot.asset, shot.video].filter(Boolean)) {
106
+ const assetPath = path.join(fixturesDirectory, asset);
107
+ if (!fs.existsSync(assetPath)) {
108
+ throw new Error(`bundled demo asset is missing: ${assetPath}`);
109
+ }
110
+ }
111
+ }
112
+ return index;
113
+ }
114
+
115
+ /**
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.
121
+ */
122
+ export function writeDemo({
123
+ root,
124
+ feature = DEFAULT_DEMO_FEATURE,
125
+ fixturesDirectory = FIXTURES_DIR,
126
+ now = new Date(),
127
+ } = {}) {
128
+ assertKebabCase(feature, "feature");
129
+ const resolvedRoot = resolveRoot(root);
130
+ const featureDirectory = path.join(resolvedRoot, ".astroshot", feature);
131
+ const index = loadDemoFixtures(fixturesDirectory);
132
+
133
+ fs.mkdirSync(featureDirectory, { recursive: true });
134
+ const startSequence = nextSequence(featureDirectory);
135
+ const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
136
+ // The first sequence of this run keeps the id unique when two runs land in
137
+ // the same second from the same process.
138
+ const runId = `${feature}-${stamp}-${process.pid}-${String(startSequence).padStart(4, "0")}`;
139
+ const capturedAt = now.toISOString();
140
+
141
+ const shots = [];
142
+ const files = [];
143
+ index.shots.forEach((fixture, offset) => {
144
+ const sequence = String(startSequence + offset).padStart(4, "0");
145
+ const posterName = `${sequence}-${fixture.slug}.png`;
146
+ copyAtomic(path.join(fixturesDirectory, fixture.asset), path.join(featureDirectory, posterName));
147
+ files.push(path.join(featureDirectory, posterName));
148
+
149
+ const shot = {
150
+ id: sequence,
151
+ file: posterName,
152
+ slug: fixture.slug,
153
+ title: fixture.title,
154
+ description: fixture.description,
155
+ captured_at: capturedAt,
156
+ viewport: index.viewport,
157
+ };
158
+
159
+ if (fixture.video) {
160
+ const videoExtension = path.extname(fixture.video) || ".webm";
161
+ const videoName = `${sequence}-${fixture.slug}${videoExtension}`;
162
+ copyAtomic(path.join(fixturesDirectory, fixture.video), path.join(featureDirectory, videoName));
163
+ files.push(path.join(featureDirectory, videoName));
164
+ shot.kind = "movie";
165
+ shot.video = videoName;
166
+ shot.duration_ms = fixture.duration_ms;
167
+ shot.source = fixture.source ?? "frames";
168
+ if (Array.isArray(fixture.chapters)) shot.chapters = fixture.chapters;
169
+ }
170
+
171
+ shots.push(shot);
172
+ });
173
+
174
+ const manifestPath = path.join(featureDirectory, "manifest.json");
175
+ const manifest = {
176
+ version: 1,
177
+ feature,
178
+ run_id: runId,
179
+ status: "pass",
180
+ description:
181
+ "Synthetic proof set written by `astroshot demo` — stills plus one journey movie.",
182
+ shots,
183
+ };
184
+ writeAtomic(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
185
+ files.push(manifestPath);
186
+
187
+ return {
188
+ root: resolvedRoot,
189
+ feature,
190
+ featureDirectory,
191
+ manifestPath,
192
+ runId,
193
+ shots,
194
+ files,
195
+ };
196
+ }
197
+
198
+ function coverageAdvice(root) {
199
+ const configuration = readWatchConfiguration();
200
+ const coverage = evaluateWatchCoverage(root, configuration);
201
+ switch (coverage.state) {
202
+ case "inside-root":
203
+ return [
204
+ `Watched by Astroshots via ${coverage.matchedRoot}.`,
205
+ "Open the Astroshots menu-bar icon → Shots to see these frames.",
206
+ ];
207
+ case "setup-incomplete":
208
+ return [
209
+ "Astroshots has no watched folders yet, so it will not pick these up.",
210
+ "Fix: open Astroshots and choose a folder, or menu-bar icon → gear → Add folders…",
211
+ "Then re-check with: astroshot doctor",
212
+ ];
213
+ case "outside-roots":
214
+ return [
215
+ `This project is outside every watched folder (${coverage.roots.join(", ")}).`,
216
+ "Fix: Astroshots menu-bar icon → gear → Add folders… and add a parent of this project.",
217
+ "Then re-check with: astroshot doctor",
218
+ ];
219
+ case "unsupported":
220
+ return [
221
+ "The Astroshots app is macOS-only; the files above still follow the .astroshot contract.",
222
+ ];
223
+ default:
224
+ // Unknown — do not claim the user has no watched folders.
225
+ return [
226
+ "Could not read Astroshots' watched folders, so whether this project is",
227
+ "watched is unknown (the files above were still written correctly).",
228
+ "Check with: astroshot doctor",
229
+ ];
230
+ }
231
+ }
232
+
233
+ export function runDemo(argv, { log = console.log } = {}) {
234
+ const options = { feature: DEFAULT_DEMO_FEATURE, root: undefined, json: false };
235
+ for (let index = 0; index < argv.length; index += 1) {
236
+ const token = argv[index];
237
+ if (token === "-h" || token === "--help" || token === "help") {
238
+ log(demoHelp());
239
+ return 0;
240
+ }
241
+ if (token === "--json") {
242
+ options.json = true;
243
+ continue;
244
+ }
245
+ if (token === "--feature" || token === "--root") {
246
+ const value = argv[index + 1];
247
+ if (!value || value.startsWith("-")) {
248
+ throw new Error(`${token} requires a value`);
249
+ }
250
+ options[token === "--feature" ? "feature" : "root"] = value;
251
+ index += 1;
252
+ continue;
253
+ }
254
+ throw new Error(`Unknown demo argument: ${token}`);
255
+ }
256
+
257
+ const result = writeDemo(options);
258
+ if (options.json) {
259
+ log(JSON.stringify({ ...result, advice: coverageAdvice(result.root) }, null, 2));
260
+ return 0;
261
+ }
262
+
263
+ const movies = result.shots.filter((shot) => shot.kind === "movie").length;
264
+ const stills = result.shots.length - movies;
265
+ log(`astroshot demo → ${result.featureDirectory}`);
266
+ for (const file of result.files) {
267
+ log(` ${path.relative(result.root, file)}`);
268
+ }
269
+ log("");
270
+ log(
271
+ `Wrote ${stills} still${stills === 1 ? "" : "s"}, ${movies} movie${movies === 1 ? "" : "s"} (poster + video), and manifest.json.`,
272
+ );
273
+ for (const line of coverageAdvice(result.root)) log(line);
274
+ return 0;
275
+ }