@hraness/slopcamera 3.3.3 → 3.3.5
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 +60 -26
- package/apps/desktop/application/directing-contract.ts +15 -3
- package/apps/desktop/application/directing-plan.ts +2 -1
- package/apps/desktop/application/operation.ts +1 -1
- package/apps/desktop/cli/args.ts +62 -1
- package/apps/desktop/cli/capability-manifest.ts +4 -1
- package/apps/desktop/cli/cinema-renderer.ts +9 -6
- package/apps/desktop/cli/command-host-resources.ts +7 -0
- package/apps/desktop/cli/commands.ts +555 -2
- package/apps/desktop/cli/credits.ts +818 -0
- package/apps/desktop/cli/directing-blob.ts +17 -12
- package/apps/desktop/cli/directing-media.ts +14 -4
- package/apps/desktop/cli/directing-quote.ts +29 -11
- package/apps/desktop/cli/directing-service.ts +6 -2
- package/apps/desktop/cli/gateway-media-catalog.ts +57 -0
- package/apps/desktop/cli/gateway-media-service.ts +12 -2
- package/apps/desktop/cli/help.ts +51 -8
- package/apps/desktop/cli/main.ts +1 -1
- package/apps/desktop/cli/project-renderer.ts +23 -9
- package/apps/desktop/cli/spatial-scene-service.ts +1 -1
- package/apps/desktop/dist/cli/main.js +337 -257
- package/apps/desktop/html-overlay/spatial-splat-runtime.ts +6 -0
- package/dist/cli.js +14 -4
- package/dist/index-11cb717y.js +28 -0
- package/dist/{index-296qhpf2.js → index-qtn3s421.js} +1 -1
- package/dist/{index-0bbesn4m.js → index-sy1n4zfy.js} +2 -2
- package/dist/index.js +1 -1
- package/dist/operations.js +1 -1
- package/dist/workflow.js +1 -1
- package/docs/README.md +7 -3
- package/docs/studio.md +63 -3
- package/package.json +13 -7
- package/schema/drawing.schema.json +27 -0
- package/skills/slopcamera/SKILL.md +1 -0
- package/skills/slopcamera/references/directing-video.md +3 -3
- package/skills/slopcamera/references/educational-video.md +1 -1
- package/skills/slopcamera/references/install.md +1 -1
- package/skills/slopcamera/references/patent-drawings.md +53 -0
- package/skills/slopcamera/references/workflows-sdk.md +1 -1
- package/src/cli.ts +90 -2
- package/src/drawing-pdf.ts +210 -0
- package/src/drawing.ts +293 -0
- package/src/index.ts +19 -0
- package/src/render.ts +6 -3
- package/src/version.ts +1 -1
- package/dist/index-3e1pcxb5.js +0 -7
package/README.md
CHANGED
|
@@ -9,10 +9,21 @@ from retained sources.**
|
|
|
9
9
|
Describe a finished result to Codex, Claude, or another coding agent. Slopcamera gives
|
|
10
10
|
it a Bun CLI, TypeScript SDK, and version-matched Agent Skill to inspect sources,
|
|
11
11
|
direct cameras, edit a composition, and render the result. A separate MCP server
|
|
12
|
-
exposes
|
|
12
|
+
exposes fixed tools for diagrams, images, and scene inspection and planning. There is no Slopcamera account or hosted
|
|
13
13
|
project database.
|
|
14
14
|
|
|
15
|
-
[Install](#install-slopcamera) · [Make a first diagram](#make-your-first-diagram) · [Capabilities](#what-slopcamera-does) · [Documentation](docs
|
|
15
|
+
[Install](#install-slopcamera) · [Make a first diagram](#make-your-first-diagram) · [Capabilities](#what-slopcamera-does) · [Documentation](https://slopcamera.com/docs) · [slopcamera.com](https://slopcamera.com)
|
|
16
|
+
|
|
17
|
+
## Made with Slopcamera
|
|
18
|
+
|
|
19
|
+
[](https://slopcamera.com/docs/tutorials/first-native-film#inspect-the-finished-example)
|
|
20
|
+
|
|
21
|
+
Watch the [six-second product film](https://slopcamera.com/docs/tutorials/first-native-film#inspect-the-finished-example),
|
|
22
|
+
then change its [geometry, materials and camera](examples/showcase/native/product).
|
|
23
|
+
For 2D motion, try the [editable editorial animation](https://slopcamera.com/docs/tutorials/first-animation).
|
|
24
|
+
The [portrait geometry lesson](https://slopcamera.com/docs/how-to/educational-video#inspect-the-finished-example)
|
|
25
|
+
adds mathematical typesetting, a presenter and authored captions. Each guide pairs
|
|
26
|
+
the rendered output with source, runtime requirements and reproduction steps.
|
|
16
27
|
|
|
17
28
|
## Why Slopcamera
|
|
18
29
|
|
|
@@ -33,14 +44,14 @@ project database.
|
|
|
33
44
|
|
|
34
45
|
## Install Slopcamera
|
|
35
46
|
|
|
36
|
-
Slopcamera v3.3.
|
|
47
|
+
Slopcamera v3.3.4 admits image and video references in directing shots, adds a hosted tool API for agent platforms, and shows reproducible workflows across the site and docs.
|
|
37
48
|
|
|
38
49
|
Install [Bun 1.3.14 or newer](https://bun.sh), then install the verified
|
|
39
|
-
[Slopcamera v3.3.
|
|
50
|
+
[Slopcamera v3.3.4 release](https://github.com/hraness/slopcamera/releases/tag/v3.3.4)
|
|
40
51
|
from its canonical archive:
|
|
41
52
|
|
|
42
53
|
```sh
|
|
43
|
-
bun add --global https://github.com/hraness/slopcamera/releases/download/v3.3.
|
|
54
|
+
bun add --global https://github.com/hraness/slopcamera/releases/download/v3.3.4/hraness-slopcamera-3.3.4.tgz
|
|
44
55
|
slopcamera skill install --target agents
|
|
45
56
|
```
|
|
46
57
|
|
|
@@ -98,7 +109,7 @@ change an existing release.
|
|
|
98
109
|
|
|
99
110
|
## Make your first diagram
|
|
100
111
|
|
|
101
|
-
After
|
|
112
|
+
After installing Slopcamera, this local task needs no model account. In a new directory,
|
|
102
113
|
create the included diagram, check it, and render it:
|
|
103
114
|
|
|
104
115
|
```sh
|
|
@@ -115,10 +126,11 @@ You now have `example-flow.tldr`, `example-flow.light.svg`,
|
|
|
115
126
|
editable, and the `.tldr` file is editable tldraw interchange. Rendering again
|
|
116
127
|
replaces those five derived files.
|
|
117
128
|
|
|
118
|
-
Follow [Your first diagram](docs/tutorials/first-diagram
|
|
119
|
-
see the result. For a moving 3D subject, use [Directed scenes](docs/spatial-scenes.md).
|
|
120
|
-
For detailed native 3D
|
|
121
|
-
[Your first native film](docs/tutorials/first-native-film.md).
|
|
129
|
+
Follow [Your first diagram](https://slopcamera.com/docs/tutorials/first-diagram) to change a label and
|
|
130
|
+
see the result. To animate type and SVG, [make your first animation](https://slopcamera.com/docs/tutorials/first-animation). For a moving 3D subject, use [Directed scenes](docs/spatial-scenes.md).
|
|
131
|
+
For detailed native 3D, follow
|
|
132
|
+
[Your first native film](docs/tutorials/first-native-film.md). To generate editable
|
|
133
|
+
architecture from dimensions, [build a parametric design](https://slopcamera.com/docs/how-to/parametric-design).
|
|
122
134
|
|
|
123
135
|
## What Slopcamera does
|
|
124
136
|
|
|
@@ -134,12 +146,12 @@ GPU path. Spark admits saved splat worlds for local camera direction.
|
|
|
134
146
|
> mount the product diagram on a screen in the scene, and show me contact frames
|
|
135
147
|
> before rendering the video.
|
|
136
148
|
|
|
137
|
-
The portable GLB
|
|
138
|
-
|
|
149
|
+
The portable GLB profiles have defined geometry and material subsets. The
|
|
150
|
+
rigged/morph profile in v3.3.1 admits bounded skins and morph targets. Saved splats
|
|
139
151
|
capture appearance; they do not establish collision geometry or editable native
|
|
140
152
|
meshes. See [Directed scenes](docs/spatial-scenes.md).
|
|
141
153
|
|
|
142
|
-
|
|
154
|
+
Slopcamera v3.3.1 includes the cinematic planning loop: a `slopcamera.spatial-direction`
|
|
143
155
|
document describes beats, actions, camera coverage, and look intents, compiles
|
|
144
156
|
into proposed performance/camera/cinema/material-lighting/shot documents, and
|
|
145
157
|
fans out into bounded galleries per axis. Declared effects bind into renders,
|
|
@@ -160,7 +172,7 @@ See [Direct a cinematic world](docs/how-to/direct-cinematic-worlds.md) and the
|
|
|
160
172
|
|
|
161
173
|
### Film native worlds and educational animation
|
|
162
174
|
|
|
163
|
-
|
|
175
|
+
Slopcamera can direct Blender for detailed sets, materials,
|
|
164
176
|
lighting, skinned characters, cloth and liquid caches; CadQuery for parametric
|
|
165
177
|
solids and STEP; and Manim Community for mathematical animation. Seven editable
|
|
166
178
|
starters include a product, character, shaded street, cloth, liquid, CAD bracket,
|
|
@@ -181,11 +193,19 @@ and [Make an educational video](docs/how-to/educational-video.md).
|
|
|
181
193
|
### Build diagrams and motion graphics
|
|
182
194
|
|
|
183
195
|
Create editable diagrams with tldraw, SVG, and PNG outputs, or turn raster artwork
|
|
184
|
-
into SVG locally with VTracer.
|
|
196
|
+
into SVG locally with VTracer. [Compare a color trace and duotone](https://slopcamera.com/docs/how-to/vectorize-images)
|
|
197
|
+
from the same original raster. Animate graphic layers with HTML, SVG, Motion,
|
|
185
198
|
p5, Two, Paper Shaders, or Three.js. Outputs can stand alone or join a video
|
|
186
199
|
project. The optional vgpu example renders programmable WebGPU passes into
|
|
187
200
|
retained raster frames for use on a world-space screen.
|
|
188
201
|
|
|
202
|
+
[Render motion graphics from HTML](https://slopcamera.com/docs/how-to/render-motion-graphics)
|
|
203
|
+
connects original examples across all seven profiles to editable source and exact
|
|
204
|
+
render requests. Try the [editorial title](examples/showcase/html/editorial.json),
|
|
205
|
+
[kinetic typography](examples/showcase/html/kinetic-title.json), or
|
|
206
|
+
[Three.js island with original music](examples/showcase/html/island-pulse.json).
|
|
207
|
+
These source examples live in the current repository; `html render` ships in v3.2.8.
|
|
208
|
+
|
|
189
209
|
Use `slopcamera html catalog` to inspect the admitted local creative tools. The
|
|
190
210
|
[creative toolkit reference](docs/html-overlay-creative-toolkit.md) distinguishes
|
|
191
211
|
available profiles from upstream possibilities. vgpu does not enable shared GPU
|
|
@@ -194,11 +214,19 @@ textures or a Three WebGPU renderer inside the current WebGL2/Spark profile.
|
|
|
194
214
|
### Generate and direct media
|
|
195
215
|
|
|
196
216
|
Discover image, video, speech, and transcription models through your own Vercel
|
|
197
|
-
AI Gateway access.
|
|
217
|
+
AI Gateway access. Set `AI_GATEWAY_API_KEY` in the local process environment;
|
|
218
|
+
the Vercel CLI is optional. Generate images from text and references, add a voiceover,
|
|
198
219
|
transcribe sound, or create video shots using the selected model's supported
|
|
199
220
|
inputs. Availability and pricing come from the live catalog.
|
|
200
221
|
|
|
201
|
-
|
|
222
|
+
Without a Gateway credential, prepaid Hraness Credits run prompt-only image
|
|
223
|
+
generation through the hosted API: `slopcamera credits topup` prints a hosted
|
|
224
|
+
checkout, `slopcamera credits wait` installs the returned device token, and
|
|
225
|
+
`slopcamera ai image generate --hosted` bills that wallet instead of your own
|
|
226
|
+
Gateway key. A stored token also routes `ai image generate` to the hosted API
|
|
227
|
+
automatically when no Gateway credential is configured.
|
|
228
|
+
|
|
229
|
+
The released CLI also provides `direct` shot recipes: retain a film
|
|
202
230
|
budget across attempts, review each take before accepting it, and use an accepted
|
|
203
231
|
clip's last decoded frame as the next shot's reference. Changed predecessors
|
|
204
232
|
invalidate affected continuations while earlier paid results remain retained.
|
|
@@ -220,8 +248,11 @@ from the same edit.
|
|
|
220
248
|
> Edit my product demo: cut the pauses, zoom into each important click, keep the
|
|
221
249
|
> speaker framed, add captions and `logo.svg`, and show a preview before export.
|
|
222
250
|
|
|
223
|
-
|
|
224
|
-
|
|
251
|
+
The released and current CLIs edit existing recording bundles; they do not
|
|
252
|
+
capture new recordings. Bundles can carry clicks, cursor movement, key activity,
|
|
253
|
+
and focused-input information. Automatic screen-action zooms need this metadata.
|
|
254
|
+
Standalone media imports need an existing project; `html render`, `studio assemble`,
|
|
255
|
+
or `direct assemble` can create one. Read [`PRIVACY.md`](PRIVACY.md) before editing
|
|
225
256
|
sensitive material.
|
|
226
257
|
|
|
227
258
|
Start with [Edit a video](docs/how-to/edit-video.md) or inspect a reusable recipe:
|
|
@@ -236,7 +267,7 @@ slopcamera workflows show social-variants --json
|
|
|
236
267
|
Keep the source that owns each creative decision. A native scene owns a rig or
|
|
237
268
|
simulation; a portable scene owns supported geometry, cameras, and media surfaces;
|
|
238
269
|
a diagram owns its objects and labels; a video project owns cuts and delivery.
|
|
239
|
-
|
|
270
|
+
Slopcamera connects these through explicit assets and rendered derivatives.
|
|
240
271
|
|
|
241
272
|
1. **Prepare the sources.** Import footage and assets or author a scene, diagram,
|
|
242
273
|
or native program. Inspect available tools before choosing an engine.
|
|
@@ -274,16 +305,19 @@ declarative graphs, approvals, and resuming work.
|
|
|
274
305
|
- **Trusted code is not sandboxed.** Native Python and caller-authored Bun
|
|
275
306
|
workflows run with the current user's access. Hashes and receipts identify
|
|
276
307
|
observed inputs and outputs; they do not make arbitrary code hermetic.
|
|
277
|
-
- **MCP is a subset.** Its fixed tools check and render diagrams, plan and audit
|
|
278
|
-
scenes, and run
|
|
279
|
-
local CLI operation and never mutates project state.
|
|
308
|
+
- **MCP is a subset.** Its 17 fixed tools check and render diagrams, plan and audit
|
|
309
|
+
scenes, and run six bounded portable operation codes in v3.3.1. It does not
|
|
310
|
+
expose every local CLI operation and never mutates project state.
|
|
280
311
|
|
|
281
312
|
## Design and trust
|
|
282
313
|
|
|
283
314
|
There is no Slopcamera account, hosted project database, or browser generation service.
|
|
284
315
|
Ordinary editing and rendering remain local. Gateway generation and selected
|
|
285
316
|
cloud analysis use credentials from the local process and request explicit
|
|
286
|
-
acknowledgement before uploading named media.
|
|
317
|
+
acknowledgement before uploading named media. The optional hosted API runs each
|
|
318
|
+
paid image call in an ephemeral workspace against a Hraness Credits device token
|
|
319
|
+
and keeps no credentials or projects; the device token lives in owner-only files
|
|
320
|
+
under the CLI state root and never reaches argv or receipts. This website never accepts a
|
|
287
321
|
Gateway credential. Native Python requires separate authorization. Custom Bun
|
|
288
322
|
workflow modules execute when loaded, including during check and plan; review
|
|
289
323
|
their source first. Both have the current user's access, including potential
|
|
@@ -295,7 +329,7 @@ Native tools, providers, codecs, and GPU drivers can affect results, so retained
|
|
|
295
329
|
source identity alone does not promise identical pixels on another machine.
|
|
296
330
|
|
|
297
331
|
See [Architecture](docs/architecture.md), [`SECURITY.md`](SECURITY.md),
|
|
298
|
-
[`PRIVACY.md`](PRIVACY.md), and [`NOTICE`](NOTICE) for the detailed boundaries.
|
|
332
|
+
[`PRIVACY.md`](PRIVACY.md), and [`NOTICE.md`](NOTICE.md) for the detailed boundaries.
|
|
299
333
|
|
|
300
334
|
## Documentation
|
|
301
335
|
|
|
@@ -347,7 +381,7 @@ changing a package or runtime boundary. Report vulnerabilities through
|
|
|
347
381
|
|
|
348
382
|
## License
|
|
349
383
|
|
|
350
|
-
[MIT](LICENSE), with third-party notices in [`NOTICE`](NOTICE).
|
|
384
|
+
[MIT](LICENSE), with third-party notices in [`NOTICE.md`](NOTICE.md).
|
|
351
385
|
|
|
352
386
|
## Optional support
|
|
353
387
|
|
|
@@ -20,6 +20,10 @@ const MoneySchema = z.number().int().safe().positive();
|
|
|
20
20
|
const TextSchema = z.string().min(1).max(100_000).refine(value => value.trim().length > 0 && !/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/u.test(value), "Text must be nonempty and contain no disallowed control characters.");
|
|
21
21
|
const ImageSchema = GatewayMediaSourceReferenceSchema.refine(source => source.mediaType.startsWith("image/") && source.bytes <= 50 * 1024 * 1024, "A directing frame must be an image of at most 50 MiB.");
|
|
22
22
|
const VideoSourceSchema = GatewayMediaSourceReferenceSchema.refine(source => source.mediaType.startsWith("video/") && source.facts?.durationSeconds !== undefined, "Endpoint video sources require a measured duration.");
|
|
23
|
+
const ReferenceSchema = GatewayMediaSourceReferenceSchema.refine(source =>
|
|
24
|
+
(source.mediaType.startsWith("image/") && source.bytes <= 50 * 1024 * 1024)
|
|
25
|
+
|| (source.mediaType.startsWith("video/") && source.bytes <= 256 * 1024 * 1024), "A directing reference must be an image of at most 50 MiB or a video of at most 256 MiB.");
|
|
26
|
+
const DIRECTING_REFERENCE_LIMITS = Object.freeze({ count: 8, totalBytes: 512 * 1024 * 1024 });
|
|
23
27
|
|
|
24
28
|
function capture(input: unknown, name: string, maximumBytes: number): unknown {
|
|
25
29
|
return createBoundedJsonValueSnapshot(input, maximumBytes, name, { maximumDepth: 32, maximumValues: 1_000_000 }).value;
|
|
@@ -39,8 +43,13 @@ export const DirectingShotSchema = z.strictObject({
|
|
|
39
43
|
z.strictObject({ kind: z.literal("shot-end"), shotId: SlugSchema }),
|
|
40
44
|
]).optional(),
|
|
41
45
|
lastFrame: ImageSchema.optional(),
|
|
46
|
+
references: z.array(ReferenceSchema).min(1).max(DIRECTING_REFERENCE_LIMITS.count).optional(),
|
|
42
47
|
}).superRefine((shot, context) => {
|
|
43
48
|
if (shot.lastFrame !== undefined && shot.firstFrame === undefined) context.addIssue({ code: "custom", message: "A last frame requires an authored first-frame reference." });
|
|
49
|
+
const references = shot.references ?? [];
|
|
50
|
+
if (references.length > 0 && (shot.firstFrame !== undefined || shot.lastFrame !== undefined)) context.addIssue({ code: "custom", message: "Directing references are mutually exclusive with frame conditioning." });
|
|
51
|
+
if (references.reduce((total, reference) => total + reference.bytes, 0) > DIRECTING_REFERENCE_LIMITS.totalBytes) context.addIssue({ code: "custom", message: "Directing references exceed the 512 MiB aggregate bound." });
|
|
52
|
+
if (new Set(references.map(reference => `${reference.path}${reference.sha256}`)).size !== references.length) context.addIssue({ code: "custom", message: "Directing references must be distinct retained media." });
|
|
44
53
|
});
|
|
45
54
|
|
|
46
55
|
export const DirectingRecipeSchema = z.preprocess(input => capture(input, "directing recipe", 8 * 1024 * 1024), z.strictObject({
|
|
@@ -101,7 +110,7 @@ const ReviewSchema = z.strictObject({ decision: z.enum(["accepted", "rejected"])
|
|
|
101
110
|
const FailureSchema = z.string().regex(/^[a-z][a-z0-9-]{0,63}$/u);
|
|
102
111
|
const VideoRequestSchema = z.strictObject({ operation: z.literal("video"), request: GatewayVideoOperationInputSchema }).superRefine((input, context) => {
|
|
103
112
|
const request = input.request;
|
|
104
|
-
if (request.providerOptions !== undefined || request.
|
|
113
|
+
if (request.providerOptions !== undefined || request.generateAudio !== undefined || (request.n ?? 1) !== 1 || (request.maxVideosPerCall ?? 1) !== 1) context.addIssue({ code: "custom", message: "Directing uses exactly one video with authored conditioning and no provider options." });
|
|
105
114
|
});
|
|
106
115
|
const AttemptBase = {
|
|
107
116
|
id: AttemptIdSchema,
|
|
@@ -149,7 +158,7 @@ export function directingRequestId(input: {
|
|
|
149
158
|
return `gateway_${canonicalJsonSha256({ domain: "slopcamera.directing-request/v1", id, attemptId, recipeSha256, shotSha256, request })}`;
|
|
150
159
|
}
|
|
151
160
|
|
|
152
|
-
/** Verify the submitted request against the authored recipe and retained
|
|
161
|
+
/** Verify the submitted request against the authored recipe and retained conditioning. */
|
|
153
162
|
export function directingRequestMatchesShot(attempt: DirectingAttempt, shot: DirectingShot, firstFrame: GatewayMediaSourceReference | undefined): boolean {
|
|
154
163
|
const request = attempt.request.request;
|
|
155
164
|
for (const key of ["prompt", "model", "durationSeconds", "resolution", "aspectRatio", "fps", "seed"] as const) if (request[key] !== shot[key]) return false;
|
|
@@ -157,7 +166,10 @@ export function directingRequestMatchesShot(attempt: DirectingAttempt, shot: Dir
|
|
|
157
166
|
const actualFirst = request.promptImage ?? frames.find(frame => frame.frameType === "first_frame")?.source;
|
|
158
167
|
const actualLast = frames.find(frame => frame.frameType === "last_frame")?.source;
|
|
159
168
|
const equal = (left: GatewayMediaSourceReference | undefined, right: GatewayMediaSourceReference | undefined) => left === undefined || right === undefined ? left === right : sameDirectingSource(left, right);
|
|
160
|
-
|
|
169
|
+
const actualReferences = request.references ?? [];
|
|
170
|
+
return equal(firstFrame, actualFirst) && equal(shot.lastFrame, actualLast)
|
|
171
|
+
&& actualReferences.length === (shot.references ?? []).length
|
|
172
|
+
&& (shot.references ?? []).every((reference, index) => sameDirectingSource(reference, actualReferences[index]!));
|
|
161
173
|
}
|
|
162
174
|
|
|
163
175
|
export const DirectingStateSchema = z.preprocess(input => capture(input, "directing state", DIRECTING_LIMITS.stateBytes), z.strictObject({
|
|
@@ -28,6 +28,7 @@ export interface ResolvedDirectingShot {
|
|
|
28
28
|
readonly dependencies: DirectingDependency[];
|
|
29
29
|
readonly firstFrame?: GatewayMediaSourceReference;
|
|
30
30
|
readonly lastFrame?: GatewayMediaSourceReference;
|
|
31
|
+
readonly references?: readonly GatewayMediaSourceReference[];
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
function resolveShot(shot: DirectingShot, accepted: ReadonlyMap<string, CompletedDirectingAttempt>): ResolvedDirectingShot | undefined {
|
|
@@ -43,7 +44,7 @@ function resolveShot(shot: DirectingShot, accepted: ReadonlyMap<string, Complete
|
|
|
43
44
|
boundDependencies.push({ ...dependency, shotSha256: predecessor.shotSha256 });
|
|
44
45
|
firstFrame = predecessor.endpoint.image;
|
|
45
46
|
}
|
|
46
|
-
return { shot, shotSha256: directingBoundShotSha256(shot, boundDependencies), dependencies, ...(firstFrame === undefined ? {} : { firstFrame }), ...(shot.lastFrame === undefined ? {} : { lastFrame: shot.lastFrame }) };
|
|
47
|
+
return { shot, shotSha256: directingBoundShotSha256(shot, boundDependencies), dependencies, ...(firstFrame === undefined ? {} : { firstFrame }), ...(shot.lastFrame === undefined ? {} : { lastFrame: shot.lastFrame }), ...(shot.references === undefined ? {} : { references: shot.references }) };
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
function acceptedSelections(state: DirectingState): Map<string, CompletedDirectingAttempt> {
|
|
@@ -4,7 +4,7 @@ import type { z } from "zod";
|
|
|
4
4
|
|
|
5
5
|
import type { ApplicationContext } from "./context";
|
|
6
6
|
|
|
7
|
-
export const SLOPCAMERA_APPLICATION_TOOL_VERSION = "slopcamera-3.3.
|
|
7
|
+
export const SLOPCAMERA_APPLICATION_TOOL_VERSION = "slopcamera-3.3.5" as const;
|
|
8
8
|
|
|
9
9
|
export const OPERATION_KINDS = [
|
|
10
10
|
"scene.audit",
|
package/apps/desktop/cli/args.ts
CHANGED
|
@@ -217,6 +217,7 @@ export type CliCommand =
|
|
|
217
217
|
readonly allowCloudUpload: boolean;
|
|
218
218
|
readonly aspectRatio: string | undefined;
|
|
219
219
|
readonly count: number;
|
|
220
|
+
readonly hosted: boolean;
|
|
220
221
|
readonly images: readonly string[];
|
|
221
222
|
readonly kind: "ai-image-generate";
|
|
222
223
|
readonly mask: string | undefined;
|
|
@@ -334,6 +335,14 @@ export type CliCommand =
|
|
|
334
335
|
} & JsonOption)
|
|
335
336
|
| ({ readonly kind: "menubar"; readonly action: "run" | "install" | "uninstall" | "status"; readonly mode: "foreground" | "background" } & JsonOption)
|
|
336
337
|
| ({ readonly kind: "outputs" } & JsonOption)
|
|
338
|
+
| ({ readonly kind: "credits-status" } & JsonOption)
|
|
339
|
+
| ({
|
|
340
|
+
readonly email: string | undefined;
|
|
341
|
+
readonly kind: "credits-topup";
|
|
342
|
+
readonly pack: string | undefined;
|
|
343
|
+
} & JsonOption)
|
|
344
|
+
| ({ readonly kind: "credits-wait"; readonly timeout: string } & JsonOption)
|
|
345
|
+
| ({ readonly kind: "credits-forget" } & JsonOption)
|
|
337
346
|
| ({ readonly kind: "recordings-list"; readonly limit: number } & JsonOption)
|
|
338
347
|
| ({ readonly kind: "projects-list"; readonly limit: number } & JsonOption)
|
|
339
348
|
| ({
|
|
@@ -997,6 +1006,7 @@ function parseAiImage(argv: readonly string[]): CliCommand {
|
|
|
997
1006
|
"--allow-cloud-upload": "flag",
|
|
998
1007
|
"--aspect-ratio": "value",
|
|
999
1008
|
"--count": "value",
|
|
1009
|
+
"--hosted": "flag",
|
|
1000
1010
|
"--image": "repeat",
|
|
1001
1011
|
"--mask": "value",
|
|
1002
1012
|
"--max-per-call": "value",
|
|
@@ -1031,6 +1041,7 @@ function parseAiImage(argv: readonly string[]): CliCommand {
|
|
|
1031
1041
|
allowCloudUpload: optionFlag(parsed, "--allow-cloud-upload"),
|
|
1032
1042
|
aspectRatio: optionString(parsed, "--aspect-ratio"),
|
|
1033
1043
|
count,
|
|
1044
|
+
hosted: optionFlag(parsed, "--hosted"),
|
|
1034
1045
|
images,
|
|
1035
1046
|
json: optionFlag(parsed, "--json"),
|
|
1036
1047
|
kind: "ai-image-generate",
|
|
@@ -1345,6 +1356,55 @@ function parseAiTranscribe(argv: readonly string[]): CliCommand {
|
|
|
1345
1356
|
};
|
|
1346
1357
|
}
|
|
1347
1358
|
|
|
1359
|
+
function parseCredits(argv: readonly string[]): CliCommand {
|
|
1360
|
+
const action = argv[0];
|
|
1361
|
+
if (action === "status") {
|
|
1362
|
+
const parsed = parseOptions(argv.slice(1), JSON_SPEC);
|
|
1363
|
+
exactPositionals(parsed, 0, "slopcamera credits status [--json]");
|
|
1364
|
+
return { kind: "credits-status", json: optionFlag(parsed, "--json") };
|
|
1365
|
+
}
|
|
1366
|
+
if (action === "topup") {
|
|
1367
|
+
const parsed = parseOptions(argv.slice(1), {
|
|
1368
|
+
...JSON_SPEC,
|
|
1369
|
+
"--email": "value",
|
|
1370
|
+
"--pack": "value",
|
|
1371
|
+
});
|
|
1372
|
+
exactPositionals(parsed, 0, "slopcamera credits topup [--pack <id>] [--email <address>] [--json]");
|
|
1373
|
+
const email = optionString(parsed, "--email");
|
|
1374
|
+
if (email !== undefined && (email.length > 320 || /[\s\u0000-\u001f\u007f]/u.test(email))) {
|
|
1375
|
+
fail("--email must be a nonempty email address without whitespace or control characters.");
|
|
1376
|
+
}
|
|
1377
|
+
const pack = optionString(parsed, "--pack");
|
|
1378
|
+
if (pack !== undefined && !/^[a-z0-9][a-z0-9_-]{0,63}$/u.test(pack)) {
|
|
1379
|
+
fail("--pack must be a pack id such as p10, p25, p50, or p100.");
|
|
1380
|
+
}
|
|
1381
|
+
return {
|
|
1382
|
+
email,
|
|
1383
|
+
json: optionFlag(parsed, "--json"),
|
|
1384
|
+
kind: "credits-topup",
|
|
1385
|
+
pack,
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
if (action === "wait") {
|
|
1389
|
+
const parsed = parseOptions(argv.slice(1), {
|
|
1390
|
+
...JSON_SPEC,
|
|
1391
|
+
"--timeout": "value",
|
|
1392
|
+
});
|
|
1393
|
+
exactPositionals(parsed, 0, "slopcamera credits wait [--timeout <time>] [--json]");
|
|
1394
|
+
return {
|
|
1395
|
+
kind: "credits-wait",
|
|
1396
|
+
json: optionFlag(parsed, "--json"),
|
|
1397
|
+
timeout: optionString(parsed, "--timeout") ?? "30m",
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
if (action === "forget") {
|
|
1401
|
+
const parsed = parseOptions(argv.slice(1), JSON_SPEC);
|
|
1402
|
+
exactPositionals(parsed, 0, "slopcamera credits forget [--json]");
|
|
1403
|
+
return { kind: "credits-forget", json: optionFlag(parsed, "--json") };
|
|
1404
|
+
}
|
|
1405
|
+
fail("Usage: slopcamera credits <status|topup|wait|forget> [options]");
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1348
1408
|
function parseAi(argv: readonly string[]): CliCommand {
|
|
1349
1409
|
switch (argv[0]) {
|
|
1350
1410
|
case "models": return parseAiModels(argv.slice(1));
|
|
@@ -3231,7 +3291,7 @@ function parseDirectingArgs(argv: readonly string[]): DirectingCommand {
|
|
|
3231
3291
|
const required = (name: string): string => optionString(parsed, name) ?? fail(`direct ${action} requires ${name}.`);
|
|
3232
3292
|
const common = { kind: "directing" as const, json: optionFlag(parsed, "--json") };
|
|
3233
3293
|
if (action === "anchor") {
|
|
3234
|
-
exactPositionals(parsed, 0, "slopcamera direct anchor --input <
|
|
3294
|
+
exactPositionals(parsed, 0, "slopcamera direct anchor --input <media>");
|
|
3235
3295
|
return { ...common, action, input: required("--input") };
|
|
3236
3296
|
}
|
|
3237
3297
|
const [value] = exactPositionals(parsed, 1, `slopcamera direct ${action} <${["init", "plan", "start"].includes(action) ? "recipe.json" : "direct-id"}>`);
|
|
@@ -3589,6 +3649,7 @@ export function parseCliArgs(argv: readonly string[]): CliCommand {
|
|
|
3589
3649
|
case "runs": return parseRuns(argv.slice(1));
|
|
3590
3650
|
case "doctor": return parseDoctor(argv.slice(1));
|
|
3591
3651
|
case "ai": return parseAi(argv.slice(1));
|
|
3652
|
+
case "credits": return parseCredits(argv.slice(1));
|
|
3592
3653
|
case "media": return parseMedia(argv.slice(1));
|
|
3593
3654
|
case "recordings": return parseRecordings(argv.slice(1));
|
|
3594
3655
|
case "projects": return parseProjects(argv.slice(1));
|
|
@@ -110,9 +110,12 @@ const capabilityModules = Object.freeze([
|
|
|
110
110
|
{ effect: "pure", name: "capabilities" },
|
|
111
111
|
{ effect: "local-read", name: "diagram check" },
|
|
112
112
|
{ effect: "local-derived-write", name: "diagram render" },
|
|
113
|
+
{ effect: "local-derived-write", name: "diagram sheets init" },
|
|
114
|
+
{ effect: "local-read", name: "diagram sheets check" },
|
|
115
|
+
{ effect: "local-derived-write", name: "diagram sheets render" },
|
|
113
116
|
{ effect: "local-derived-write", name: "image vectorize" },
|
|
114
117
|
],
|
|
115
|
-
description: "Portable diagrams, local vectorization, and the bounded semantic operation projection.",
|
|
118
|
+
description: "Portable diagrams, CLI/SDK drawing sheets, local vectorization, and the bounded semantic operation projection.",
|
|
116
119
|
moduleId: "slopcamera.capability.diagram-image",
|
|
117
120
|
operationKeys: diagramImageOperations,
|
|
118
121
|
profiles: [{
|
|
@@ -142,11 +142,11 @@ function sliceChain(slice: CinemaMediaSlice, inputIndex: ReadonlyMap<string, num
|
|
|
142
142
|
const inputDurationUs = slice.fileRange.endUs - slice.fileRange.startUs;
|
|
143
143
|
const outputDurationUs = slice.outputRange.endUs - slice.outputRange.startUs;
|
|
144
144
|
return [
|
|
145
|
-
`[${inputSpecifier(input, slice.streamIndex)}]`,
|
|
146
|
-
`trim=start=${seconds(slice.fileRange.startUs)}:end=${seconds(slice.fileRange.endUs)}`,
|
|
145
|
+
`[${inputSpecifier(input, slice.streamIndex)}]trim=start=${seconds(slice.fileRange.startUs)}:end=${seconds(slice.fileRange.endUs)}`,
|
|
147
146
|
"settb=AVTB",
|
|
148
|
-
//
|
|
149
|
-
|
|
147
|
+
// Concat requires each member to start at zero. Position the complete
|
|
148
|
+
// window once after joining, so internal slice boundaries stay contiguous.
|
|
149
|
+
`setpts=(PTS-STARTPTS)*${decimal(outputDurationUs / inputDurationUs)}`,
|
|
150
150
|
"format=rgba",
|
|
151
151
|
].join(",");
|
|
152
152
|
}
|
|
@@ -173,7 +173,7 @@ function windowChain(
|
|
|
173
173
|
: `scale=w=${outputWidth}:h=${outputHeight}:force_original_aspect_ratio=decrease,pad=${outputWidth}:${outputHeight}:(ow-iw)/2:(oh-ih)/2:color=black@0`;
|
|
174
174
|
const joined = label(context, prefix);
|
|
175
175
|
parts.push(
|
|
176
|
-
`${members.map(member => `[${member}]`).join("")}concat=n=${members.length}:v=1:a=0,${geometry},format=rgba${gradeFilter === undefined ? "" : `,${gradeFilter}`}[${joined}]`,
|
|
176
|
+
`${members.map(member => `[${member}]`).join("")}concat=n=${members.length}:v=1:a=0,setpts=PTS+${seconds(slices[0]!.outputRange.startUs)}/TB,${geometry},format=rgba${gradeFilter === undefined ? "" : `,${gradeFilter}`}[${joined}]`,
|
|
177
177
|
);
|
|
178
178
|
context.filters.push(...parts);
|
|
179
179
|
return joined;
|
|
@@ -255,8 +255,11 @@ function transitionWindowChain(
|
|
|
255
255
|
stream = windowChain(context, slices, shot.fit, grade, outputWidth, outputHeight, prefix);
|
|
256
256
|
}
|
|
257
257
|
const zeroed = label(context, `${prefix}_zero`);
|
|
258
|
+
// A dip/flash reveals only the tail of the incoming handle. Its hidden
|
|
259
|
+
// prefix still consumes source time, so the tail must meet the shot body
|
|
260
|
+
// without skipping the last part of the handle at the transition boundary.
|
|
258
261
|
context.filters.push(
|
|
259
|
-
`[${stream}]setpts=PTS-STARTPTS
|
|
262
|
+
`[${stream}]setpts=PTS-STARTPTS${limitUs === undefined ? "" : `,trim=start=${seconds(durationUs - limitUs)}:end=${seconds(durationUs)},setpts=PTS-STARTPTS`},fps=${context.rate}[${zeroed}]`,
|
|
260
263
|
);
|
|
261
264
|
return zeroed;
|
|
262
265
|
};
|
|
@@ -239,6 +239,13 @@ export function commandHostResourceClaims(
|
|
|
239
239
|
case "ai-models-list":
|
|
240
240
|
case "ai-models-show":
|
|
241
241
|
return claims(coordinator, ["network"]);
|
|
242
|
+
case "credits-status":
|
|
243
|
+
case "credits-topup":
|
|
244
|
+
case "credits-wait":
|
|
245
|
+
// Bounded hosted-service calls; `wait` polls one claim until payment.
|
|
246
|
+
return claims(coordinator, ["network"]);
|
|
247
|
+
case "credits-forget":
|
|
248
|
+
return [];
|
|
242
249
|
case "ai-provider-options-inspect":
|
|
243
250
|
case "align-apply":
|
|
244
251
|
case "code-init":
|