@hraness/slopcamera 3.5.0 → 3.7.0
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 +42 -26
- package/apps/desktop/README.md +1 -1
- package/apps/desktop/application/html-overlay-renderer.ts +6 -0
- package/apps/desktop/application/operation.ts +1 -1
- package/apps/desktop/cli/args.ts +63 -1
- package/apps/desktop/cli/command-host-resources.ts +5 -0
- package/apps/desktop/cli/commands.ts +27 -1
- package/apps/desktop/cli/help.ts +43 -21
- package/apps/desktop/cli/html-film-init.ts +158 -0
- package/apps/desktop/cli/html-film-names.ts +17 -0
- package/apps/desktop/cli/html-film.ts +471 -0
- package/apps/desktop/cli/html-overlay-renderer.ts +29 -2
- package/apps/desktop/cli/html-scene.ts +3 -3
- package/apps/desktop/cli/main.ts +1 -0
- package/apps/desktop/cli/menubar-status.ts +156 -0
- package/apps/desktop/cli/menubar.ts +88 -136
- package/apps/desktop/cli/portable-surface.ts +55 -1
- package/apps/desktop/dist/cli/main.js +353 -328
- package/apps/desktop/html-overlay/film.ts +418 -0
- package/dist/cli.js +19 -9
- package/dist/generate.js +1 -1
- package/dist/{index-8bhp7cj2.js → index-842t02pp.js} +1 -1
- package/dist/{index-jab2mg0s.js → index-nv3byqa1.js} +20 -4
- package/dist/index-p74xy76d.js +3 -0
- package/dist/{index-24w75s6a.js → index-y8spxs77.js} +10 -7
- package/dist/index.js +1 -1
- package/dist/operations.js +1 -1
- package/dist/workflow.js +1 -1
- package/docs/README.md +5 -5
- package/examples/html/launch-film/README.md +51 -0
- package/examples/html/launch-film/build.ts +158 -0
- package/examples/html/launch-film/film.css +593 -0
- package/examples/html/launch-film/film.html +85 -0
- package/examples/html/launch-film/film.js +410 -0
- package/examples/html/launch-film/film.json +63 -0
- package/examples/html/launch-film/mockups.tsx +74 -0
- package/examples/html/launch-film/timeline.ts +57 -0
- package/package.json +14 -8
- package/skills/slopcamera/SKILL.md +3 -2
- package/skills/slopcamera/references/install.md +3 -3
- package/skills/slopcamera/references/launch-films.md +154 -0
- package/skills/slopcamera/references/visual-communication.md +1 -1
- package/src/cli.ts +138 -19
- package/src/generate.ts +44 -1
- package/src/generation-pricing.ts +237 -0
- package/src/icon-set.ts +1086 -0
- package/src/icon.ts +396 -59
- package/src/index.ts +1 -0
- package/src/operations.ts +52 -0
- package/src/support.ts +6 -0
- package/src/version.ts +1 -1
- package/dist/index-77fjfg9f.js +0 -3
package/README.md
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
#
|
|
1
|
+
# SlopCamera
|
|
2
2
|
|
|
3
|
-
[](https://slopcamera.com)
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**SlopCamera is a media studio for coding agents. Codex, Claude Code, and other
|
|
6
6
|
agents make images, diagrams, animation, 3D scenes, and edited video from source
|
|
7
7
|
files they can keep revising.**
|
|
8
8
|
|
|
9
9
|
Describe the result you want to Codex, Claude Code, or another coding agent.
|
|
10
|
-
|
|
10
|
+
SlopCamera gives the agent a Bun CLI, a TypeScript SDK, and an Agent Skill matched
|
|
11
11
|
to the CLI version, so it can inspect sources, set up cameras, edit a composition,
|
|
12
12
|
and render. `slopcamera mcp` runs an MCP server with a smaller, fixed set of diagram,
|
|
13
|
-
image, and scene tools. Projects stay in local files, and there is no
|
|
13
|
+
image, and scene tools. Projects stay in local files, and there is no SlopCamera
|
|
14
14
|
account. Generation uses your own Vercel AI Gateway account or, for prompt-only
|
|
15
15
|
images, prepaid Hraness Credits.
|
|
16
16
|
|
|
17
17
|
[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)
|
|
18
18
|
|
|
19
|
-
## Made with
|
|
19
|
+
## Made with SlopCamera
|
|
20
20
|
|
|
21
21
|
[](https://slopcamera.com/docs/tutorials/first-native-film#inspect-the-finished-example)
|
|
22
22
|
|
|
@@ -27,7 +27,7 @@ The [portrait geometry lesson](https://slopcamera.com/docs/how-to/educational-vi
|
|
|
27
27
|
adds mathematical typesetting, a presenter and authored captions. Each guide pairs
|
|
28
28
|
the rendered output with source, runtime requirements and reproduction steps.
|
|
29
29
|
|
|
30
|
-
## Why
|
|
30
|
+
## Why SlopCamera
|
|
31
31
|
|
|
32
32
|
- **Keep creative work editable.** Native scenes, portable scenes, diagrams,
|
|
33
33
|
and video projects retain their own sources and settings. Rendered frames
|
|
@@ -45,16 +45,29 @@ the rendered output with source, runtime requirements and reproduction steps.
|
|
|
45
45
|
instead run on the hosted API with prepaid Hraness Credits. Native Python
|
|
46
46
|
authoring requires separate trust because it runs as your current user.
|
|
47
47
|
|
|
48
|
-
##
|
|
48
|
+
## Compared with other tools
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
- [Remotion](https://www.remotion.dev/) renders React components to video and can
|
|
51
|
+
render on AWS Lambda. It is source-available and needs a paid license for
|
|
52
|
+
companies of four or more people.
|
|
53
|
+
- [HyperFrames](https://github.com/heygen-com/hyperframes) renders HTML to MP4
|
|
54
|
+
under Apache 2.0, with optional HeyGen-hosted rendering.
|
|
55
|
+
- [MCP for Blender](https://github.com/ahujasid/mcp-for-blender), formerly Blender
|
|
56
|
+
MCP, lets an agent drive a running Blender interactively.
|
|
57
|
+
|
|
58
|
+
SlopCamera renders HTML motion too, and adds diagrams, Three.js scenes, headless
|
|
59
|
+
Blender, CadQuery, and Manim renders, and edits of your own footage in one local
|
|
60
|
+
project. It has no cloud rendering and a much smaller community. See
|
|
61
|
+
[the full comparison](https://slopcamera.com/docs/explanation/why-slopcamera#compared-with-other-tools).
|
|
62
|
+
|
|
63
|
+
## Install SlopCamera
|
|
51
64
|
|
|
52
65
|
Install [Bun 1.3.14 or newer](https://bun.sh), then install the verified
|
|
53
|
-
[
|
|
66
|
+
[SlopCamera v3.6.0 release](https://github.com/hraness/slopcamera/releases/tag/v3.6.0)
|
|
54
67
|
from its canonical archive:
|
|
55
68
|
|
|
56
69
|
```sh
|
|
57
|
-
bun add --global https://github.com/hraness/slopcamera/releases/download/v3.
|
|
70
|
+
bun add --global https://github.com/hraness/slopcamera/releases/download/v3.6.0/hraness-slopcamera-3.6.0.tgz
|
|
58
71
|
slopcamera skill install --target agents
|
|
59
72
|
```
|
|
60
73
|
|
|
@@ -90,8 +103,9 @@ Native engines install separately. The [source-install guide](docs/how-to/use-cu
|
|
|
90
103
|
explains workspace placement and durable-run identity; the
|
|
91
104
|
[capability reference](docs/reference/capabilities.md) names runtime requirements.
|
|
92
105
|
|
|
93
|
-
|
|
94
|
-
|
|
106
|
+
SlopCamera is a CLI. On macOS, an optional unbundled menu-bar companion shows
|
|
107
|
+
what SlopCamera is rendering, how the last job ended, your last known credits
|
|
108
|
+
balance and the newest files in the agent outputs directory; `slopcamera menubar` runs a prebuilt
|
|
95
109
|
companion directly and does not install or launch an application bundle. The
|
|
96
110
|
companion is a thin product binary over the shared
|
|
97
111
|
[desktop-foundation](https://github.com/hraness/desktop-foundation) crate, pinned
|
|
@@ -105,14 +119,14 @@ when working from source.
|
|
|
105
119
|
[Atet v3.2.3](https://github.com/hraness/atet/releases/tag/v3.2.3) and its
|
|
106
120
|
[original archive](https://github.com/hraness/atet/releases/download/v3.2.3/hraness-atet-3.2.3.tgz)
|
|
107
121
|
remain historical publication evidence. Those immutable bytes install Atet,
|
|
108
|
-
not
|
|
122
|
+
not SlopCamera. Renaming the repository does not create a renamed package or
|
|
109
123
|
change an existing release.
|
|
110
124
|
|
|
111
125
|
</details>
|
|
112
126
|
|
|
113
127
|
## Make your first diagram
|
|
114
128
|
|
|
115
|
-
After installing
|
|
129
|
+
After installing SlopCamera, this local task needs no model account. In a new directory,
|
|
116
130
|
create the included diagram, check it, and render it:
|
|
117
131
|
|
|
118
132
|
```sh
|
|
@@ -135,10 +149,12 @@ For detailed native 3D, follow
|
|
|
135
149
|
[Your first native film](docs/tutorials/first-native-film.md). To generate editable
|
|
136
150
|
architecture from dimensions, [build a parametric design](https://slopcamera.com/docs/how-to/parametric-design).
|
|
137
151
|
|
|
138
|
-
## What
|
|
152
|
+
## What SlopCamera does
|
|
139
153
|
|
|
140
|
-
|
|
154
|
+
SlopCamera lets your coding agent make images, diagrams, animation, 3D scenes,
|
|
141
155
|
and video from source files it can keep revising.
|
|
156
|
+
Direct cel animation, pixel art, math explainers, and period film looks with the
|
|
157
|
+
[style direction guide](docs/how-to/direct-visual-styles.md).
|
|
142
158
|
|
|
143
159
|
### Author scenes and direct cameras
|
|
144
160
|
|
|
@@ -157,7 +173,7 @@ rigged/morph profile in v3.3.1 admits bounded skins and morph targets. Saved spl
|
|
|
157
173
|
capture appearance; they do not establish collision geometry or editable native
|
|
158
174
|
meshes. See [Directed scenes](docs/spatial-scenes.md).
|
|
159
175
|
|
|
160
|
-
|
|
176
|
+
SlopCamera v3.3.1 introduced the cinematic planning loop: a `slopcamera.spatial-direction`
|
|
161
177
|
document describes beats, actions, camera coverage, and look intents, compiles
|
|
162
178
|
into proposed performance/camera/cinema/material-lighting/shot documents, and
|
|
163
179
|
fans out into bounded galleries per axis. Declared effects bind into renders,
|
|
@@ -173,12 +189,12 @@ slopcamera workflows plan cinematic-world --input pack-input.json --json
|
|
|
173
189
|
The `cinematic-world` workflow consumes one inert recipe pack — a bounded,
|
|
174
190
|
content-addressed JSON document an agent can author — and stops before
|
|
175
191
|
selection. Registration and selection stay explicit `scene project` operations.
|
|
176
|
-
See [
|
|
192
|
+
See [Plan camera moves, lighting, and effects for a 3D scene](docs/how-to/direct-cinematic-worlds.md) and the
|
|
177
193
|
[extension architecture](docs/extension-architecture.md).
|
|
178
194
|
|
|
179
195
|
### Film native worlds and educational animation
|
|
180
196
|
|
|
181
|
-
|
|
197
|
+
SlopCamera can direct Blender for detailed sets, materials,
|
|
182
198
|
lighting, skinned characters, cloth and liquid caches; CadQuery for parametric
|
|
183
199
|
solids and STEP; and Manim Community for mathematical animation. Seven editable
|
|
184
200
|
starters include a product, character, shaded street, cloth, liquid, CAD bracket,
|
|
@@ -194,7 +210,7 @@ camera clock for reuse. Supported GLB derivatives and calibrated cameras can
|
|
|
194
210
|
cross between native and portable scenes; rigs, solvers, and procedural materials
|
|
195
211
|
remain native. Blender and Python environments are installed separately, and
|
|
196
212
|
source execution requires explicit current-user trust. See [Native film studio](docs/studio.md)
|
|
197
|
-
and [Make
|
|
213
|
+
and [Make a math explainer video with Manim](docs/how-to/educational-video.md).
|
|
198
214
|
|
|
199
215
|
### Build diagrams and motion graphics
|
|
200
216
|
|
|
@@ -268,12 +284,12 @@ slopcamera workflows list --json
|
|
|
268
284
|
slopcamera workflows show social-variants --json
|
|
269
285
|
```
|
|
270
286
|
|
|
271
|
-
## How
|
|
287
|
+
## How SlopCamera works
|
|
272
288
|
|
|
273
289
|
Keep the source that owns each creative decision. A native scene owns a rig or
|
|
274
290
|
simulation; a portable scene owns supported geometry, cameras, and media surfaces;
|
|
275
291
|
a diagram owns its objects and labels; a video project owns cuts and delivery.
|
|
276
|
-
|
|
292
|
+
SlopCamera connects these through explicit assets and rendered derivatives.
|
|
277
293
|
|
|
278
294
|
1. **Prepare the sources.** Import footage and assets or author a scene, diagram,
|
|
279
295
|
or native program. Inspect available tools before choosing an engine.
|
|
@@ -317,7 +333,7 @@ declarative graphs, approvals, and resuming work.
|
|
|
317
333
|
|
|
318
334
|
## Design and trust
|
|
319
335
|
|
|
320
|
-
There is no
|
|
336
|
+
There is no SlopCamera account, hosted project database, or browser generation service.
|
|
321
337
|
Ordinary editing and rendering remain local. Gateway generation and selected
|
|
322
338
|
cloud analysis use credentials from the local process and request explicit
|
|
323
339
|
acknowledgement before uploading named media. The optional hosted API runs each
|
|
@@ -348,7 +364,7 @@ vision](https://algal.computer/docs/vision/) states the bet behind it.
|
|
|
348
364
|
## Documentation
|
|
349
365
|
|
|
350
366
|
- **Learn:** [Your first diagram](docs/tutorials/first-diagram.md) · [Your first native film](docs/tutorials/first-native-film.md).
|
|
351
|
-
- **Make a result:** [Edit video](docs/how-to/edit-video.md) · [Generate media](docs/how-to/generate-media.md) · [Run workflows](docs/how-to/run-workflows.md) · [Educational video](docs/how-to/educational-video.md) · [
|
|
367
|
+
- **Make a result:** [Edit video](docs/how-to/edit-video.md) · [Generate media](docs/how-to/generate-media.md) · [Run workflows](docs/how-to/run-workflows.md) · [Educational video](docs/how-to/educational-video.md) · [Plan camera moves, lighting, and effects for a 3D scene](docs/how-to/direct-cinematic-worlds.md).
|
|
352
368
|
- **Look up support:** [Capabilities and release availability](docs/reference/capabilities.md) · [SDK entrypoints](docs/reference/sdk.md) · [Creative tools](docs/html-overlay-creative-toolkit.md).
|
|
353
369
|
- **Understand the system:** [Architecture](docs/architecture.md) · [Native studio](docs/studio.md) · [Directed scenes](docs/spatial-scenes.md) · [Extension architecture](docs/extension-architecture.md).
|
|
354
370
|
|
|
@@ -412,4 +428,4 @@ an acknowledgment; the unacknowledged offer expires after ten minutes and does n
|
|
|
412
428
|
consume the weekly shown cooldown. `support dismiss` opts out across the local suite;
|
|
413
429
|
`support snooze` pauses for 30 days. `HRANESS_SUPPORT_AUDIENCE=off` disables ambient
|
|
414
430
|
notices and claimed offers. Payment is an explicit human choice on the shared support
|
|
415
|
-
page;
|
|
431
|
+
page; SlopCamera has no product-updates mailing list or account-status lookup.
|
package/apps/desktop/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# SlopCamera local host and CLI
|
|
2
2
|
|
|
3
3
|
Slopcamera reads finished recording bundles from repository-local directories, combines independent camera and audio takes on one typed project clock, then applies non-destructive edits through the `slopcamera` CLI. The CLI is the product surface for agents. An optional unbundled `slopcamera menubar` companion surfaces the agent outputs directory in the macOS menu bar; it carries no capture or editing authority.
|
|
4
4
|
|
|
@@ -19,6 +19,12 @@ export interface HtmlOverlayFrameRenderRequest {
|
|
|
19
19
|
readonly browserRuntime: HtmlOverlayBrowserRuntimeBinding;
|
|
20
20
|
readonly outputDirectory: string;
|
|
21
21
|
readonly resources: readonly BoundHtmlOverlayResource[];
|
|
22
|
+
/**
|
|
23
|
+
* Optional ascending subset of frame indexes to capture, for stills and
|
|
24
|
+
* previews. Omitted means every frame. Captured files keep their absolute
|
|
25
|
+
* frame index in the name, so `frame-%08d.png` stays addressable.
|
|
26
|
+
*/
|
|
27
|
+
readonly frames?: readonly number[];
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
export interface HtmlOverlayFrameRenderResult {
|
|
@@ -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.
|
|
7
|
+
export const SLOPCAMERA_APPLICATION_TOOL_VERSION = "slopcamera-3.7.0" as const;
|
|
8
8
|
|
|
9
9
|
export const OPERATION_KINDS = [
|
|
10
10
|
"scene.audit",
|
package/apps/desktop/cli/args.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CliError } from "./errors";
|
|
2
2
|
import { MAX_EVENT_QUERY_LIMIT } from "./query-limits";
|
|
3
|
+
import { HTML_FILM_CUTS, type HtmlFilmCommand, type HtmlFilmCut } from "./html-film-names";
|
|
3
4
|
import { STUDIO_TEMPLATES, type StudioTemplate } from "./studio-template-names";
|
|
4
5
|
import {
|
|
5
6
|
slopcameraGalleryKinds,
|
|
@@ -121,6 +122,7 @@ export type SpatialProjectCommand = JsonOption & {
|
|
|
121
122
|
|
|
122
123
|
export type CliCommand =
|
|
123
124
|
| { readonly kind: "html-render"; readonly input: string; readonly dryRun: boolean; readonly json: boolean }
|
|
125
|
+
| HtmlFilmCommand
|
|
124
126
|
| StudioCommand
|
|
125
127
|
| DirectingCommand
|
|
126
128
|
| SpatialWorldCommand
|
|
@@ -731,6 +733,63 @@ function fail(message: string): never {
|
|
|
731
733
|
throw new CliError("usage", message);
|
|
732
734
|
}
|
|
733
735
|
|
|
736
|
+
const HTML_STILL_USAGE = "Use html still --input <scene.json> --at <seconds>[,<seconds>...] --output <dir> [--json].";
|
|
737
|
+
const HTML_PREVIEW_USAGE = "Use html preview --input <scene.json> --output <dir> [--every <seconds>] [--json].";
|
|
738
|
+
const HTML_DELIVER_USAGE = "Use html deliver <export.json> --basename <name> --poster-at <seconds> --social-at <seconds> [--cuts 1:1,9:16] [--per-beat-clips] [--beats <beats.json>] [--output <dir>] [--json].";
|
|
739
|
+
|
|
740
|
+
function secondsList(value: string | undefined, usage: string): readonly number[] {
|
|
741
|
+
if (value === undefined) fail(usage);
|
|
742
|
+
return value.split(",").map(part => {
|
|
743
|
+
if (!/^(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/u.test(part.trim())) fail(`Times must be seconds such as 3 or 12.5; got ${part}.`);
|
|
744
|
+
return Number(part.trim());
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function parseHtmlFilmArgs(action: "still" | "preview" | "deliver", argv: readonly string[]): HtmlFilmCommand {
|
|
749
|
+
if (action === "still") {
|
|
750
|
+
const parsed = parseOptions(argv, { "--input": "value", "--at": "value", "--output": "value", "--json": "flag" });
|
|
751
|
+
const input = optionString(parsed, "--input");
|
|
752
|
+
const output = optionString(parsed, "--output");
|
|
753
|
+
if (input === undefined || output === undefined || parsed.positionals.length !== 0) fail(HTML_STILL_USAGE);
|
|
754
|
+
return { kind: "html-film", action, input, output, at: secondsList(optionString(parsed, "--at"), HTML_STILL_USAGE), json: optionFlag(parsed, "--json") };
|
|
755
|
+
}
|
|
756
|
+
if (action === "preview") {
|
|
757
|
+
const parsed = parseOptions(argv, { "--input": "value", "--every": "value", "--output": "value", "--json": "flag" });
|
|
758
|
+
const input = optionString(parsed, "--input");
|
|
759
|
+
const output = optionString(parsed, "--output");
|
|
760
|
+
if (input === undefined || output === undefined || parsed.positionals.length !== 0) fail(HTML_PREVIEW_USAGE);
|
|
761
|
+
const every = optionString(parsed, "--every");
|
|
762
|
+
const [seconds] = every === undefined ? [2] : secondsList(every, HTML_PREVIEW_USAGE);
|
|
763
|
+
if (seconds === undefined || seconds <= 0) fail("--every must be more than 0 seconds.");
|
|
764
|
+
return { kind: "html-film", action, input, output, every: seconds, json: optionFlag(parsed, "--json") };
|
|
765
|
+
}
|
|
766
|
+
const parsed = parseOptions(argv, {
|
|
767
|
+
"--basename": "value", "--poster-at": "value", "--social-at": "value", "--cuts": "value",
|
|
768
|
+
"--per-beat-clips": "flag", "--beats": "value", "--output": "value", "--json": "flag",
|
|
769
|
+
});
|
|
770
|
+
const basename = optionString(parsed, "--basename");
|
|
771
|
+
if (parsed.positionals.length !== 1 || basename === undefined) fail(HTML_DELIVER_USAGE);
|
|
772
|
+
const single = (name: string) => {
|
|
773
|
+
const values = secondsList(optionString(parsed, name), HTML_DELIVER_USAGE);
|
|
774
|
+
if (values.length !== 1) fail(`${name} takes one time in seconds.`);
|
|
775
|
+
return values[0]!;
|
|
776
|
+
};
|
|
777
|
+
const cutsInput = optionString(parsed, "--cuts");
|
|
778
|
+
const cuts = cutsInput === undefined ? [] : cutsInput.split(",").map(cut => {
|
|
779
|
+
if (!(HTML_FILM_CUTS as readonly string[]).includes(cut)) fail(`--cuts takes ${HTML_FILM_CUTS.join(", ")}; got ${cut}.`);
|
|
780
|
+
return cut as HtmlFilmCut;
|
|
781
|
+
});
|
|
782
|
+
const beats = optionString(parsed, "--beats");
|
|
783
|
+
const output = optionString(parsed, "--output");
|
|
784
|
+
return {
|
|
785
|
+
kind: "html-film", action, exportPath: parsed.positionals[0]!, basename,
|
|
786
|
+
posterAt: single("--poster-at"), socialAt: single("--social-at"), cuts,
|
|
787
|
+
perBeatClips: optionFlag(parsed, "--per-beat-clips"),
|
|
788
|
+
...(beats === undefined ? {} : { beats }), ...(output === undefined ? {} : { output }),
|
|
789
|
+
json: optionFlag(parsed, "--json"),
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
|
|
734
793
|
function parseOptions(argv: readonly string[], spec: OptionSpec): ParsedOptions {
|
|
735
794
|
const options: Record<string, OptionValue> = {};
|
|
736
795
|
const positionals: string[] = [];
|
|
@@ -3631,6 +3690,7 @@ export function parseCliArgs(argv: readonly string[]): CliCommand {
|
|
|
3631
3690
|
const command = argv[0]!;
|
|
3632
3691
|
switch (command) {
|
|
3633
3692
|
case "html": {
|
|
3693
|
+
if (argv[1] === "still" || argv[1] === "preview" || argv[1] === "deliver") return parseHtmlFilmArgs(argv[1], argv.slice(2));
|
|
3634
3694
|
if (argv[1] !== "render") fail("Use html render --input <scene.json> [--dry-run] [--json].");
|
|
3635
3695
|
const parsed = parseOptions(argv.slice(2), { "--input": "value", "--dry-run": "flag", "--json": "flag" });
|
|
3636
3696
|
const input = optionString(parsed, "--input");
|
|
@@ -3659,8 +3719,10 @@ export function parseCliArgs(argv: readonly string[]): CliCommand {
|
|
|
3659
3719
|
case "menubar": {
|
|
3660
3720
|
const parsed = parseOptions(argv.slice(1), { ...JSON_SPEC, "--foreground": "flag", "--background": "flag" });
|
|
3661
3721
|
const positionals = parsed.positionals;
|
|
3662
|
-
if (positionals.length > 1 || (positionals[0] !== undefined && !["install", "uninstall", "status"].includes(positionals[0]))) throw new CliError("usage", "Use slopcamera menubar [--foreground|--background] or slopcamera menubar install|uninstall|status. See slopcamera help menubar.");
|
|
3722
|
+
if (positionals.length > 1 || (positionals[0] !== undefined && !["install", "uninstall", "status", "start"].includes(positionals[0]))) throw new CliError("usage", "Use slopcamera menubar [--foreground|--background] or slopcamera menubar install|uninstall|status|start. See slopcamera help menubar.");
|
|
3663
3723
|
if (positionals[0] !== undefined && (optionFlag(parsed, "--foreground") || optionFlag(parsed, "--background"))) throw new CliError("usage", "Choose a menu-bar action or a run mode, not both.");
|
|
3724
|
+
// `start` opens it now and returns, like --background; the login item's hints use it.
|
|
3725
|
+
if (positionals[0] === "start") return { kind: "menubar", action: "run", mode: "background", json: optionFlag(parsed, "--json") };
|
|
3664
3726
|
return { kind: "menubar", action: (positionals[0] as "install" | "uninstall" | "status" | undefined) ?? "run", mode: optionFlag(parsed, "--background") ? "background" : "foreground", json: optionFlag(parsed, "--json") };
|
|
3665
3727
|
}
|
|
3666
3728
|
case "outputs": {
|
|
@@ -125,6 +125,11 @@ export function commandHostResourceClaims(
|
|
|
125
125
|
coordinator: HostResourceCoordinator,
|
|
126
126
|
): readonly HostResourceClaim[] {
|
|
127
127
|
switch (command.kind) {
|
|
128
|
+
case "html-film": return command.action === "deliver"
|
|
129
|
+
? claims(coordinator, ["cpu", "ffmpeg", "local-io"])
|
|
130
|
+
: claims(coordinator, command.action === "preview"
|
|
131
|
+
? ["cpu", "local-io", "browser", "ffmpeg"]
|
|
132
|
+
: ["cpu", "local-io", "browser"]);
|
|
128
133
|
case "html-render": return command.dryRun
|
|
129
134
|
? claims(coordinator, ["cpu", "local-io"])
|
|
130
135
|
: claims(coordinator, ["cpu", "local-io", "browser", "ffmpeg", "output-publication", "project-render"]);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { activityLabel, recordActivity, recordCredits } from "./menubar-status";
|
|
1
2
|
import { reportUsefulResult, type UsefulResultObserver } from "../../../src/support-completion";
|
|
2
3
|
import { SlopcameraCloudError } from "../../../src/cloud-errors";
|
|
3
4
|
import {
|
|
@@ -187,6 +188,7 @@ import { commandHelp, completions } from "./help";
|
|
|
187
188
|
import { createLocalSlopcameraCapabilityManifest } from "./capability-manifest";
|
|
188
189
|
import { PlaywrightHtmlOverlayRenderer } from "./html-overlay-renderer";
|
|
189
190
|
import { executeHtmlSceneCommand } from "./html-scene";
|
|
191
|
+
import { executeHtmlFilmCommand, formatHtmlFilmResult } from "./html-film";
|
|
190
192
|
import { BunProcessRunner, processIo, writeJson, writeLine, type CliIo, type ProcessRunner } from "./io";
|
|
191
193
|
import { launchMenubar, manageMenubar, reportOutputsRoot } from "./menubar";
|
|
192
194
|
import {
|
|
@@ -362,7 +364,7 @@ import {
|
|
|
362
364
|
workflowRunStore,
|
|
363
365
|
} from "./workflow-runs";
|
|
364
366
|
|
|
365
|
-
export const SLOPCAMERA_VERSION = "3.
|
|
367
|
+
export const SLOPCAMERA_VERSION = "3.7.0";
|
|
366
368
|
|
|
367
369
|
// Legacy direct renders predate per-target output contracts. Keep them
|
|
368
370
|
// bounded generously enough for long-form production while preventing one
|
|
@@ -5548,6 +5550,7 @@ async function handleCreditsStatus(
|
|
|
5548
5550
|
} catch (error) {
|
|
5549
5551
|
throw creditsErrorToCli(error);
|
|
5550
5552
|
}
|
|
5553
|
+
recordCredits(context.stateRoot, { usd: balance.balance.usd, low: balance.lowBalance, checkedAt: context.clock() });
|
|
5551
5554
|
const view = {
|
|
5552
5555
|
configured: true,
|
|
5553
5556
|
tokenSource: loaded.source,
|
|
@@ -7448,6 +7451,16 @@ async function dispatch(context: CommandContext, command: CliCommand): Promise<v
|
|
|
7448
7451
|
writeValue(context.io, command.json, output, () => JSON.stringify(output, null, 2));
|
|
7449
7452
|
return;
|
|
7450
7453
|
}
|
|
7454
|
+
case "html-film": {
|
|
7455
|
+
const output = await executeHtmlFilmCommand(applicationContext(context), command,
|
|
7456
|
+
context.abortSignal ?? new AbortController().signal,
|
|
7457
|
+
{ progress: stage => context.io.stderr(`HTML film: ${stage}\n`) });
|
|
7458
|
+
writeValue(context.io, command.json, output, () => formatHtmlFilmResult(output));
|
|
7459
|
+
if (output.kind === "slopcamera.html-film-delivery" && !output.withinBudget) {
|
|
7460
|
+
throw new CliError("invalid-data", "One or more delivered files are over budget.");
|
|
7461
|
+
}
|
|
7462
|
+
return;
|
|
7463
|
+
}
|
|
7451
7464
|
case "studio": {
|
|
7452
7465
|
const output = await executeStudioCommand(applicationContext(context), command, context.abortSignal ?? new AbortController().signal);
|
|
7453
7466
|
writeValue(context.io, command.json, output, () => JSON.stringify(output, null, 2));
|
|
@@ -8267,6 +8280,7 @@ type MutationReference =
|
|
|
8267
8280
|
function commandMutationReference(command: CliCommand): MutationReference | undefined {
|
|
8268
8281
|
switch (command.kind) {
|
|
8269
8282
|
case "html-render": return undefined; // A fresh retained attempt owns its publication lease.
|
|
8283
|
+
case "html-film": return undefined; // Fresh private job directories; outputs are plain caller files.
|
|
8270
8284
|
case "studio": return undefined; // Native jobs and the machine custody marker own explicit leases.
|
|
8271
8285
|
case "directing": return undefined; // The directing store owns its explicit lease.
|
|
8272
8286
|
case "spatial-world": return undefined; // Immutable world attempts and imports own their publication custody.
|
|
@@ -8419,6 +8433,7 @@ async function resolveMutationTarget(
|
|
|
8419
8433
|
export async function runCli(argv: readonly string[], dependencies: CliDependencies = {}): Promise<number> {
|
|
8420
8434
|
const io = dependencies.io ?? processIo;
|
|
8421
8435
|
let jsonRequested = argv.includes("--json") || argv.includes("--jsonl");
|
|
8436
|
+
let activity: { readonly stateRoot: string; readonly label: string; readonly startedAt: number } | undefined;
|
|
8422
8437
|
try {
|
|
8423
8438
|
const command = parseCliArgs(argv);
|
|
8424
8439
|
jsonRequested = "json" in command ? command.json : command.kind === "events" && command.format !== "human";
|
|
@@ -8513,6 +8528,11 @@ export async function runCli(argv: readonly string[], dependencies: CliDependenc
|
|
|
8513
8528
|
? {}
|
|
8514
8529
|
: { renderGalleryScene: dependencies.renderGalleryScene }),
|
|
8515
8530
|
};
|
|
8531
|
+
const label = activityLabel(command);
|
|
8532
|
+
if (label !== undefined) {
|
|
8533
|
+
activity = { stateRoot, label, startedAt: context.clock() };
|
|
8534
|
+
recordActivity(stateRoot, { state: "running", label, startedAt: activity.startedAt });
|
|
8535
|
+
}
|
|
8516
8536
|
const mutationTarget = await resolveMutationTarget(paths, stateRoot, command);
|
|
8517
8537
|
await withCommandHostResources(context, command, async admittedContext => {
|
|
8518
8538
|
if (mutationTarget === undefined) {
|
|
@@ -8535,9 +8555,15 @@ export async function runCli(argv: readonly string[], dependencies: CliDependenc
|
|
|
8535
8555
|
|| command.kind === "project-render" && command.action === "run" && !command.dryRun) {
|
|
8536
8556
|
reportUsefulResult(dependencies.onUsefulResult);
|
|
8537
8557
|
}
|
|
8558
|
+
if (activity !== undefined) {
|
|
8559
|
+
recordActivity(activity.stateRoot, { state: "done", label: activity.label, startedAt: activity.startedAt, finishedAt: io.now().getTime() });
|
|
8560
|
+
}
|
|
8538
8561
|
return 0;
|
|
8539
8562
|
} catch (error) {
|
|
8540
8563
|
const failure = asCliError(error);
|
|
8564
|
+
if (activity !== undefined) {
|
|
8565
|
+
recordActivity(activity.stateRoot, { state: "failed", label: activity.label, startedAt: activity.startedAt, finishedAt: io.now().getTime(), error: failure.code });
|
|
8566
|
+
}
|
|
8541
8567
|
const payload = {
|
|
8542
8568
|
error: {
|
|
8543
8569
|
code: failure.code,
|
package/apps/desktop/cli/help.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { slopcameraSupportAdvancedHelp, slopcameraSupportHelpLine } from "../../../src/support";
|
|
2
|
+
|
|
1
3
|
import { HTML_OVERLAY_SCAFFOLD_KINDS } from "../html-overlay";
|
|
2
4
|
|
|
3
5
|
const HTML_OVERLAY_SCAFFOLD_KIND_HELP =
|
|
4
6
|
HTML_OVERLAY_SCAFFOLD_KINDS.join("|");
|
|
5
7
|
|
|
6
|
-
const
|
|
8
|
+
const globalHelp = () => `slopcamera: a media studio for coding agents
|
|
7
9
|
|
|
8
10
|
Usage: slopcamera <command> [options]
|
|
9
11
|
|
|
@@ -23,7 +25,8 @@ Commands:
|
|
|
23
25
|
Author and render retained Blender, CAD and Manim productions
|
|
24
26
|
image vectorize|generate|gallery|icon
|
|
25
27
|
Create local SVGs, generated images, or review galleries
|
|
26
|
-
html catalog|scaffold|render
|
|
28
|
+
html catalog|scaffold|init|render|still|preview|deliver
|
|
29
|
+
Author HTML scenes and launch films, render, and deliver web video
|
|
27
30
|
style list|show Discover reusable art direction and review criteria
|
|
28
31
|
workflows list|show|plan|run Plan or run a reviewed reusable workflow
|
|
29
32
|
code init|check|plan|run Author, preflight, and run trusted TypeScript workflows
|
|
@@ -35,11 +38,6 @@ Commands:
|
|
|
35
38
|
credits status|topup|wait|forget
|
|
36
39
|
Buy and inspect hosted generation credits
|
|
37
40
|
media audio|color Apply local non-destructive audio and video effects
|
|
38
|
-
menubar [--foreground|--background]
|
|
39
|
-
Show your outputs in the menu bar
|
|
40
|
-
menubar install|uninstall|status Open it at login, stop that, or check it
|
|
41
|
-
support [--json|protocol --json|offer --json|shown <id>|release <id>|dismiss|snooze|enable|status --json]
|
|
42
|
-
Optional support; no feature requires payment
|
|
43
41
|
outputs Print the agent outputs directory
|
|
44
42
|
recordings list List recording bundles
|
|
45
43
|
projects list|create List projects or create one from a recording
|
|
@@ -63,19 +61,21 @@ Examples:
|
|
|
63
61
|
slopcamera render run REC --output out.mp4 Render a recording
|
|
64
62
|
|
|
65
63
|
Run slopcamera help <command> for command-specific help.
|
|
66
|
-
|
|
64
|
+
${slopcameraSupportHelpLine()}`;
|
|
67
65
|
|
|
68
66
|
const HELP: Readonly<Record<string, string>> = {
|
|
67
|
+
advanced: `${slopcameraSupportAdvancedHelp()}`,
|
|
69
68
|
menubar: `Usage:
|
|
70
69
|
slopcamera menubar [--foreground|--background] [--json]
|
|
71
|
-
slopcamera menubar install|uninstall|status [--json]
|
|
70
|
+
slopcamera menubar install|uninstall|status|start [--json]
|
|
72
71
|
|
|
73
|
-
Shows
|
|
74
|
-
|
|
72
|
+
Shows what Slopcamera is doing in the menu bar: a render in progress, how the
|
|
73
|
+
last job ended and your last known credits balance, plus your newest outputs.
|
|
74
|
+
It only reads that status and the outputs folder (slopcamera outputs).
|
|
75
75
|
|
|
76
76
|
menubar Open it now; it closes when this command ends
|
|
77
|
-
menubar
|
|
78
|
-
menubar install Open it
|
|
77
|
+
menubar start Open it now and return (same as --background)
|
|
78
|
+
menubar install Open it every time you log in
|
|
79
79
|
menubar uninstall Stop opening it at login and remove the installed copy
|
|
80
80
|
menubar status Check whether it opens at login and is running
|
|
81
81
|
|
|
@@ -83,9 +83,10 @@ If it's already in your menu bar, starting it again says so and succeeds.
|
|
|
83
83
|
macOS shows a notice that slopcamera-menubar can open at login. Turn it off any
|
|
84
84
|
time in System Settings › General › Login Items & Extensions.
|
|
85
85
|
|
|
86
|
-
The menu bar isn't in released packages yet
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
The menu bar isn't in released packages yet, so it isn't listed in
|
|
87
|
+
slopcamera help. In a Slopcamera checkout, build it with
|
|
88
|
+
cargo build --release --manifest-path desktop/Cargo.toml and slopcamera finds
|
|
89
|
+
it; elsewhere, set SLOPCAMERA_MENUBAR to the built file.`,
|
|
89
90
|
capabilities: `Usage: slopcamera capabilities [--json]
|
|
90
91
|
|
|
91
92
|
Print the exact statically assembled capability manifest for this CLI build. Each module owns named operation, workflow, MCP-tool, command, and runtime-profile identities. The manifest includes trust classes, resource/effect policies, runtime requirements, qualification status, and a canonical SHA-256. It reads no workspace source, loads no plugin, probes no runtime, and grants no authority. Use doctor separately to inspect this machine's currently available executables.`,
|
|
@@ -304,7 +305,11 @@ compliance. Drawing sheets are exposed through this CLI and the root SDK, not co
|
|
|
304
305
|
[--cell <n>] [--tile|--no-tile] [--json]
|
|
305
306
|
slopcamera image icon <subject> --output <file.svg>
|
|
306
307
|
[--purpose <mark|illustration>] [--model <model>] [--ink <#rgb>]
|
|
308
|
+
[--context <card|hero|inline>] [--candidates <1-4>]
|
|
307
309
|
[--rounds <1-4>] [--critique-model <model>] [--keep-raster] [--json]
|
|
310
|
+
slopcamera image icon --set <file.json> --output-dir <directory>
|
|
311
|
+
[--context <card|hero|inline>] [--candidates <1-4>]
|
|
312
|
+
[--set-rounds <1-4>] [--keep-raster] [--json]
|
|
308
313
|
|
|
309
314
|
Explicit --output file commands delegate to @hraness/slopcamera. Vectorization is local,
|
|
310
315
|
bounded, checksum-pinned, and emits inert SVG. File generation uses Vercel AI Gateway with the
|
|
@@ -312,7 +317,10 @@ caller's environment credential. The --prompt spelling without --output is an al
|
|
|
312
317
|
content-addressed \`ai image generate\` lane and returns project-composable content hash references.
|
|
313
318
|
Icon combines a style-locked Gateway raster, local ink extraction and VTracer tracing into a
|
|
314
319
|
canonical isometric line-art SVG; --rounds above 1 adds a vision-model critique that revises the
|
|
315
|
-
prompt between attempts.
|
|
320
|
+
prompt between attempts. --context tunes the prompt and gates to where the art lands, and --set
|
|
321
|
+
generates a manifest-declared family at once: pooled candidates, joint selection toward the
|
|
322
|
+
family's measured coverage and stroke median, and a contact-sheet critique of the whole set before
|
|
323
|
+
anything publishes. Gallery fans one subject out to bounded parallel candidates — driven by
|
|
316
324
|
variation axes or an explicit candidate list — keeps every candidate with its provenance, and
|
|
317
325
|
composes a labelled contact sheet plus receipt for agent review. Texture cells repeat the
|
|
318
326
|
candidate 2×2 so seams are inspectable (--tile/--no-tile overrides). It never replaces existing
|
|
@@ -330,7 +338,12 @@ direction, not proof of visual quality or historically authentic footage.`,
|
|
|
330
338
|
html: `Usage:
|
|
331
339
|
slopcamera html catalog [--json]
|
|
332
340
|
slopcamera html scaffold <${HTML_OVERLAY_SCAFFOLD_KIND_HELP}> --output <file.html>
|
|
341
|
+
slopcamera html init <dir> --template launch-film [--aspect 16:9|1:1|9:16] [--json]
|
|
333
342
|
slopcamera html render --input <scene.json> [--dry-run] [--json]
|
|
343
|
+
slopcamera html still --input <scene.json> --at <seconds>[,<seconds>...] --output <dir> [--json]
|
|
344
|
+
slopcamera html preview --input <scene.json> --output <dir> [--every <seconds>] [--json]
|
|
345
|
+
slopcamera html deliver <export.json> --basename <name> --poster-at <seconds> --social-at <seconds>
|
|
346
|
+
[--cuts 1:1,9:16,4:5] [--per-beat-clips] [--beats <beats.json>] [--output <dir>] [--json]
|
|
334
347
|
|
|
335
348
|
Catalog lists the closed scaffold profiles in stable order with their primary jobs, render
|
|
336
349
|
substrates, and current exact browser-library versions. Scaffold creates a complete transparent
|
|
@@ -344,7 +357,16 @@ with separate scene-video and original-audio tracks. Device dimensions must be e
|
|
|
344
357
|
Duration rounds up to whole frames; audio starts at zero and is trimmed or padded to that
|
|
345
358
|
duration. Soundtracks encode as 48 kHz stereo AAC at 320 kb/s. Dry run checks source and
|
|
346
359
|
workload bounds without launching a browser, importing audio, or writing project state.
|
|
347
|
-
Progress uses stderr; --json keeps stdout machine-readable
|
|
360
|
+
Progress uses stderr; --json keeps stdout machine-readable.
|
|
361
|
+
|
|
362
|
+
Init creates a launch-film project (film.html, film.css, film.js, build.ts and a sample
|
|
363
|
+
product mockup) in a new directory. Its build writes out/scene.json, out/beats.json and
|
|
364
|
+
out/captions.vtt. Still and preview draw frames with the same injected runtime that render
|
|
365
|
+
uses, so a still matches the rendered frame at that time; preview adds a contact sheet.
|
|
366
|
+
Deliver reads output.path from html render --json output and writes a faststart H.264 MP4,
|
|
367
|
+
a VP9 WebM, a poster, a 1200x630 social still, optional aspect cuts over a blurred fill, and
|
|
368
|
+
optional 6 to 10 second clips per beat. It prints a receipt per file and exits non-zero when
|
|
369
|
+
a file is over its budget: 12 MB per MP4, 10 MB for the WebM, 250 KB per JPEG.`,
|
|
348
370
|
operations: `Usage:
|
|
349
371
|
slopcamera operations list [--json]
|
|
350
372
|
slopcamera operations show <kind>[@<version>] [--json]
|
|
@@ -701,8 +723,8 @@ Long inactivity is analyzed and removed by default; use --keep-inactivity to opt
|
|
|
701
723
|
};
|
|
702
724
|
|
|
703
725
|
export function commandHelp(topic: readonly string[]): string {
|
|
704
|
-
if (topic.length === 0) return
|
|
705
|
-
return HELP[topic[0]!] ??
|
|
726
|
+
if (topic.length === 0) return globalHelp();
|
|
727
|
+
return HELP[topic[0]!] ?? globalHelp();
|
|
706
728
|
}
|
|
707
729
|
|
|
708
730
|
export function completions(words: readonly string[]): readonly string[] {
|
|
@@ -712,7 +734,7 @@ export function completions(words: readonly string[]): readonly string[] {
|
|
|
712
734
|
if (words.length <= 1) return topLevel;
|
|
713
735
|
const command = words[0];
|
|
714
736
|
if (command === "support") return ["protocol", "offer", "shown", "release", "dismiss", "snooze", "enable", "status"];
|
|
715
|
-
if (command === "html") return ["catalog", "scaffold", "render"];
|
|
737
|
+
if (command === "html") return ["catalog", "scaffold", "init", "render", "still", "preview", "deliver"];
|
|
716
738
|
if (command === "style") return ["list", "show"];
|
|
717
739
|
if (command === "direct") return ["init", "anchor", "plan", "start", "inspect", "revise", "generate", "resume", "review", "assemble", "cleanup"];
|
|
718
740
|
if (command === "studio") return words[2] === "assets" || words[1] === "assets" ? ["search", "describe", "plan", "import"] : ["init", "bundle", "plan", "probe", "run", "encode", "asset", "assemble", "inspect", "reconcile", "assets"];
|