@hraness/slopcamera 3.2.7 → 3.3.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.
- package/NOTICE.md +1 -7
- package/PRIVACY.md +16 -7
- package/README.md +65 -20
- package/apps/desktop/README.md +14 -130
- package/apps/desktop/application/context.ts +6 -9
- package/apps/desktop/application/default-registry.ts +26 -10
- package/apps/desktop/application/operation.ts +14 -5
- package/apps/desktop/application/operations/index.ts +4 -1
- package/apps/desktop/application/operations/spatial-behavior.ts +151 -0
- package/apps/desktop/application/operations/spatial-direction.ts +222 -0
- package/apps/desktop/application/operations/spatial-rendered-audit.ts +174 -0
- package/apps/desktop/application/operations/spatial-review.ts +194 -0
- package/apps/desktop/application/operations/spatial-scene.ts +25 -1
- package/apps/desktop/application/registry.ts +2 -2
- package/apps/desktop/application/spatial-asset-admission.ts +201 -0
- package/apps/desktop/application/spatial-assets.ts +160 -31
- package/apps/desktop/application/spatial-render.ts +217 -20
- package/apps/desktop/application/spatial-rendered-audit.ts +273 -0
- package/apps/desktop/application/spatial-review.ts +361 -0
- package/apps/desktop/application/spatial-spz.ts +48 -3
- package/apps/desktop/application/spatial-world-import.ts +26 -5
- package/apps/desktop/application/spatial-world-metadata.ts +105 -0
- package/apps/desktop/cli/args.ts +528 -97
- package/apps/desktop/cli/capability-manifest.ts +374 -0
- package/apps/desktop/cli/cinema-renderer.ts +608 -0
- package/apps/desktop/cli/command-host-resources.ts +31 -2
- package/apps/desktop/cli/commands.ts +1215 -70
- package/apps/desktop/cli/gateway-review-provider.ts +334 -0
- package/apps/desktop/cli/help.ts +183 -23
- package/apps/desktop/cli/html-scene.ts +139 -28
- package/apps/desktop/cli/io.ts +2 -1
- package/apps/desktop/cli/main.ts +26 -50
- package/apps/desktop/cli/menubar.ts +244 -0
- package/apps/desktop/cli/music-analysis-service.ts +61 -0
- package/apps/desktop/cli/portable-surface.ts +18 -5
- package/apps/desktop/cli/spatial-asset-service.ts +124 -0
- package/apps/desktop/cli/spatial-design-service.ts +237 -0
- package/apps/desktop/cli/spatial-generate-service.ts +370 -0
- package/apps/desktop/cli/spatial-scene-service.ts +268 -4
- package/apps/desktop/cli/workflow-code.ts +1 -1
- package/apps/desktop/code/application-node-planner.ts +0 -47
- package/apps/desktop/code/host-source-layout.ts +46 -0
- package/apps/desktop/code/public.ts +0 -1
- package/apps/desktop/code/runtime-identity.ts +50 -8
- package/apps/desktop/code/semantic-builder.ts +24 -43
- package/apps/desktop/code/source-bundle.ts +23 -6
- package/apps/desktop/code/source-typecheck.ts +131 -43
- package/apps/desktop/code/worker-client.ts +14 -2
- package/apps/desktop/contracts/cinema.ts +736 -0
- package/apps/desktop/contracts/index.ts +1 -0
- package/apps/desktop/contracts/spatial-asset.ts +16 -0
- package/apps/desktop/contracts/spatial-world.ts +26 -3
- package/apps/desktop/core/cinema-plan.ts +1320 -0
- package/apps/desktop/core/index.ts +1 -0
- package/apps/desktop/core/music-analysis.ts +124 -1
- package/apps/desktop/core/storage.ts +75 -0
- package/apps/desktop/dist/cli/main.js +737 -291
- package/apps/desktop/html-overlay/audio-reactivity.ts +127 -0
- package/apps/desktop/html-overlay/index.ts +9 -0
- package/apps/desktop/html-overlay/runtime.ts +2 -0
- package/apps/desktop/html-overlay/scene.ts +29 -0
- package/apps/desktop/html-overlay/spatial.ts +619 -57
- package/apps/desktop/workflows/cinematic-world.ts +127 -0
- package/apps/desktop/workflows/index.ts +6 -0
- package/dist/cli.js +32 -434
- package/dist/code/advanced.js +1 -269
- package/dist/code/index.js +16 -2239
- package/dist/generate.js +1 -23
- package/dist/host-resources.js +1 -25
- package/dist/index-0bbesn4m.js +35 -0
- package/dist/index-296qhpf2.js +3 -0
- package/dist/index-3cxf2c58.js +3 -0
- package/dist/index-77fjfg9f.js +3 -0
- package/dist/index-80a36bc0.js +3 -0
- package/dist/index-a3vvs3rf.js +3 -0
- package/dist/index-b3geqczn.js +5 -0
- package/dist/index-cgpkyjqa.js +3 -0
- package/dist/index-f0ptv8dm.js +7 -0
- package/dist/index-grk47tgs.js +3 -0
- package/dist/index-qry58nj2.js +10 -0
- package/dist/index.js +1 -240
- package/dist/operations.js +1 -28
- package/dist/skill-install-gx7afxsd.js +2 -0
- package/dist/vectorize/worker.js +1 -141
- package/dist/workflow.js +1 -15
- package/docs/README.md +5 -1
- package/docs/studio.md +1 -1
- package/package.json +11 -24
- package/skills/slopcamera/SKILL.md +14 -1
- package/skills/slopcamera/references/diagrams.md +5 -14
- package/skills/slopcamera/references/directed-scenes.md +22 -2
- package/skills/slopcamera/references/gateway-media.md +15 -1
- package/skills/slopcamera/references/image-galleries.md +166 -0
- package/skills/slopcamera/references/install.md +2 -2
- package/skills/slopcamera/references/music-video.md +23 -3
- package/skills/slopcamera/references/parametric-design.md +14 -0
- package/skills/slopcamera/references/reference-led-3d.md +1 -1
- package/skills/slopcamera/references/scene-building.md +32 -0
- package/skills/slopcamera/references/social-collage-banners.md +204 -0
- package/skills/slopcamera/references/support.md +25 -0
- package/skills/slopcamera/references/web-media-excerpts.md +120 -0
- package/skills/slopcamera/references/workflows-sdk.md +3 -3
- package/skills/slopcamera/scripts/compose-social-collage-banner.ts +728 -0
- package/src/capability-manifest.ts +361 -0
- package/src/cli.ts +250 -102
- package/src/code/canonical-json.ts +9 -0
- package/src/generate.ts +2 -2
- package/src/icon.ts +1111 -0
- package/src/image-gallery.ts +1049 -0
- package/src/index.ts +11 -23
- package/src/mcp/boundary.ts +11 -11
- package/src/mcp/index.ts +3 -0
- package/src/mcp/server.ts +2 -1
- package/src/mcp/tools.ts +1642 -130
- package/src/mcp/types.ts +19 -0
- package/src/operations.ts +412 -0
- package/src/portable-capability-manifest.ts +67 -0
- package/src/scene-gallery.ts +184 -0
- package/src/spatial-scene/asset-admission.ts +183 -0
- package/src/spatial-scene/audit-rendered.ts +692 -0
- package/src/spatial-scene/audit.ts +617 -0
- package/src/spatial-scene/behavior-audit.ts +252 -0
- package/src/spatial-scene/behavior-bake.ts +233 -0
- package/src/spatial-scene/behavior-fns.ts +585 -0
- package/src/spatial-scene/behavior-gallery.ts +109 -0
- package/src/spatial-scene/behavior-stdlib.ts +323 -0
- package/src/spatial-scene/behavior-trace.ts +325 -0
- package/src/spatial-scene/behavior.ts +618 -0
- package/src/spatial-scene/build.ts +632 -0
- package/src/spatial-scene/camera-rig.ts +125 -0
- package/src/spatial-scene/camera-track.ts +4 -2
- package/src/spatial-scene/character.ts +124 -0
- package/src/spatial-scene/contracts.ts +75 -7
- package/src/spatial-scene/design-templates.ts +351 -0
- package/src/spatial-scene/design.ts +327 -0
- package/src/spatial-scene/direction-compile.ts +584 -0
- package/src/spatial-scene/direction.ts +122 -0
- package/src/spatial-scene/effects.ts +149 -0
- package/src/spatial-scene/evaluate.ts +109 -29
- package/src/spatial-scene/gallery.ts +277 -0
- package/src/spatial-scene/generate.ts +239 -0
- package/src/spatial-scene/geometry-native.ts +165 -0
- package/src/spatial-scene/geometry.ts +1216 -0
- package/src/spatial-scene/gltf.ts +600 -77
- package/src/spatial-scene/identity.ts +55 -2
- package/src/spatial-scene/index.ts +36 -0
- package/src/spatial-scene/inspect.ts +35 -6
- package/src/spatial-scene/material-lighting.ts +609 -0
- package/src/spatial-scene/math.ts +21 -4
- package/src/spatial-scene/motion-evidence.ts +88 -0
- package/src/spatial-scene/parametric.ts +765 -0
- package/src/spatial-scene/particle-preparation.ts +183 -0
- package/src/spatial-scene/particle.ts +195 -0
- package/src/spatial-scene/patch.ts +109 -15
- package/src/spatial-scene/performance.ts +1613 -0
- package/src/spatial-scene/probe.ts +137 -0
- package/src/spatial-scene/recipe-pack.ts +86 -0
- package/src/spatial-scene/render-effects.ts +240 -0
- package/src/spatial-scene/review.ts +639 -0
- package/src/spatial-scene/simulation-bake.ts +206 -0
- package/src/spatial-scene/simulation.ts +187 -0
- package/src/spatial-scene/solve.ts +366 -0
- package/src/spatial-scene/temporal-audit.ts +354 -0
- package/src/support-completion.ts +16 -0
- package/src/support.ts +42 -0
- package/src/version.ts +1 -1
- package/apps/desktop/application/operations/recording/index.ts +0 -5
- package/apps/desktop/application/operations/recording/pause.ts +0 -34
- package/apps/desktop/application/operations/recording/resume.ts +0 -34
- package/apps/desktop/application/operations/recording/shared.ts +0 -199
- package/apps/desktop/application/operations/recording/start.ts +0 -169
- package/apps/desktop/application/operations/recording/stop.ts +0 -34
- package/apps/desktop/capture/protocol.ts +0 -689
- package/apps/desktop/cli/capture-bundle.ts +0 -1117
- package/apps/desktop/cli/recording-controller.ts +0 -1567
- package/apps/desktop/cli/recording-daemon.ts +0 -931
- package/dist/index-42zsesc1.js +0 -2257
- package/dist/index-7308egqr.js +0 -48
- package/dist/index-8txs6fkn.js +0 -712
- package/dist/index-mcy8z0br.js +0 -192
- package/dist/index-p63wavx0.js +0 -2029
- package/dist/index-r7gdhmsp.js +0 -424
- package/dist/index-rvghzt7b.js +0 -2013
- package/dist/index-sh6xbav6.js +0 -1219
- package/dist/index-z1w83f81.js +0 -4
- package/dist/index-z7239b4h.js +0 -2363
- package/dist/skill-install-dh0ntqba.js +0 -10
- package/src/desktop.ts +0 -253
package/src/cli.ts
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
import { writeFile } from "node:fs/promises"
|
|
3
|
+
import { readFile, writeFile } from "node:fs/promises"
|
|
4
4
|
import { resolve } from "node:path"
|
|
5
|
-
import { createInterface } from "node:readline/promises"
|
|
6
5
|
import {
|
|
7
6
|
artifactSummary,
|
|
8
7
|
checkDiagramFile,
|
|
9
|
-
desktopStatus,
|
|
10
|
-
getLatestDesktopRelease,
|
|
11
|
-
installDesktop,
|
|
12
|
-
openInDesktop,
|
|
13
8
|
renderDiagramFile,
|
|
14
9
|
runMcpServer,
|
|
15
|
-
selectDesktopAsset,
|
|
16
10
|
vectorizeImage,
|
|
17
11
|
} from "./index.js"
|
|
18
12
|
import {
|
|
@@ -21,6 +15,17 @@ import {
|
|
|
21
15
|
slopcameraImageModels,
|
|
22
16
|
type SlopcameraImageModel,
|
|
23
17
|
} from "./generate.js"
|
|
18
|
+
import {
|
|
19
|
+
generateSlopcameraIcon,
|
|
20
|
+
slopcameraIconMaximumRounds,
|
|
21
|
+
} from "./icon.js"
|
|
22
|
+
import {
|
|
23
|
+
generateSlopcameraImageGallery,
|
|
24
|
+
parseSlopcameraGalleryVary,
|
|
25
|
+
slopcameraGalleryKinds,
|
|
26
|
+
slopcameraGalleryLimits,
|
|
27
|
+
type SlopcameraGalleryAxisSpec,
|
|
28
|
+
} from "./image-gallery.js"
|
|
24
29
|
import {
|
|
25
30
|
executeSlopcameraOperation,
|
|
26
31
|
slopcameraOperationCodes,
|
|
@@ -32,6 +37,8 @@ import type { HostResourceCoordinator } from "./host-resources.js"
|
|
|
32
37
|
import { installSkill, type SkillScope, type SkillTarget } from "./skill-install.js"
|
|
33
38
|
import { pathExists } from "./fs.js"
|
|
34
39
|
import { SLOPCAMERA_VERSION } from "./version.js"
|
|
40
|
+
import { reportUsefulResult, type UsefulResultObserver } from "./support-completion.js"
|
|
41
|
+
import { runProductSupportCommand, showProductSupportInvitation, standaloneSupportEnvironment } from "./support.js"
|
|
35
42
|
|
|
36
43
|
export const slopcameraCliVersion = SLOPCAMERA_VERSION
|
|
37
44
|
|
|
@@ -45,14 +52,17 @@ Usage:
|
|
|
45
52
|
slopcamera diagram render <file> [--out-dir <directory>] [--config <file>] [--scale <number>]
|
|
46
53
|
slopcamera image vectorize <image> --output <file.svg> [--json] [--duotone <#rgb,#rgb>]
|
|
47
54
|
slopcamera image generate <prompt> --output <file.png|jpg|webp> [--model <provider/model>] [--json]
|
|
55
|
+
slopcamera image icon <subject> --output <file.svg> [--purpose <mark|illustration>]
|
|
56
|
+
[--model <provider/model>] [--ink <#rgb|#rrggbb>] [--rounds <1-${slopcameraIconMaximumRounds}>]
|
|
57
|
+
[--critique-model <provider/model>] [--keep-raster] [--json]
|
|
58
|
+
slopcamera image gallery <subject> --output-dir <directory> [--kind <${slopcameraGalleryKinds.join("|")}>]
|
|
59
|
+
[--count <1-${slopcameraGalleryLimits.candidates}>] [--vary <axis[=v1,v2][;axis...]>] [--candidates <file.json>]
|
|
60
|
+
[--model <provider/model>] [--cell <${slopcameraGalleryLimits.cellEdgeMin}-${slopcameraGalleryLimits.cellEdgeMax}>] [--tile|--no-tile] [--json]
|
|
48
61
|
slopcamera code search [query] [--limit <number>]
|
|
49
62
|
slopcamera code execute <operation> --input <JSON>
|
|
50
63
|
slopcamera mcp --root <workspace>
|
|
51
|
-
slopcamera canvas open <file.tldr|file.tldraw>
|
|
52
|
-
slopcamera canvas status
|
|
53
|
-
slopcamera canvas url
|
|
54
|
-
slopcamera canvas install [--yes] [--download-only]
|
|
55
64
|
slopcamera doctor
|
|
65
|
+
slopcamera support [--json|protocol --json|offer --json|shown <id>|release <id>|dismiss|snooze|enable|status --json]
|
|
56
66
|
slopcamera skill path
|
|
57
67
|
slopcamera skill install [--target codex|claude|agents] [--scope user|project] [--force]
|
|
58
68
|
|
|
@@ -63,9 +73,8 @@ Render writes the same five replaceable artifacts on every run:
|
|
|
63
73
|
<name>.light.png
|
|
64
74
|
<name>.dark.png
|
|
65
75
|
|
|
66
|
-
The .tldr file is editable
|
|
67
|
-
|
|
68
|
-
tldraw Offline or the tldraw SDK.
|
|
76
|
+
The .tldr file is editable interchange for browser-based canvas tooling.
|
|
77
|
+
Rendering does not require a desktop application or a bundled UI runtime.
|
|
69
78
|
|
|
70
79
|
Vectorize adaptively traces a raster with a checksum-pinned VTracer binary.
|
|
71
80
|
It enforces bounded input, decode, time, path, and output budgets and emits a
|
|
@@ -77,6 +86,21 @@ Set AI_GATEWAY_API_KEY, or run through \`vercel env run -- …\` so
|
|
|
77
86
|
VERCEL_OIDC_TOKEN is available. Slopcamera never stores or prints the token.
|
|
78
87
|
PNG, JPEG, and WebP responses are signature-checked and published atomically.
|
|
79
88
|
|
|
89
|
+
Icon produces isometric line-art SVG: a style-locked Gateway raster is
|
|
90
|
+
normalized to canonical ink-on-transparent pixels, traced by the local
|
|
91
|
+
vectorizer, and (when --rounds exceeds 1) critiqued by a vision model whose
|
|
92
|
+
feedback revises the prompt for the next attempt. Only Slopcamera's own
|
|
93
|
+
generated output is uploaded for critique — never user media.
|
|
94
|
+
|
|
95
|
+
Gallery generates several bounded candidates in parallel and composes them
|
|
96
|
+
into one labelled contact sheet plus a receipt. Use it to review texture,
|
|
97
|
+
skybox, backdrop, sprite, or design alternatives, then promote a chosen
|
|
98
|
+
candidate file explicitly — nothing is applied automatically.
|
|
99
|
+
|
|
100
|
+
Optional support: after useful work, agents can read slopcamera support protocol --json.
|
|
101
|
+
Discovery uses stderr without claiming an invitation; HRANESS_SUPPORT_AUDIENCE=off disables it.
|
|
102
|
+
No feature requires payment. Imported CLI/SDK calls and probes stay quiet.
|
|
103
|
+
|
|
80
104
|
Code mode searches and executes a fixed semantic registry. Execute accepts
|
|
81
105
|
typed JSON for one exact owned operation code; it never evaluates source text.
|
|
82
106
|
|
|
@@ -151,6 +175,23 @@ function parseDuotone(value: string | undefined): readonly [string, string] | un
|
|
|
151
175
|
return [colors[0]!, colors[1]!]
|
|
152
176
|
}
|
|
153
177
|
|
|
178
|
+
function parseGalleryVary(value: string | undefined): readonly SlopcameraGalleryAxisSpec[] | undefined {
|
|
179
|
+
if (value === undefined) return undefined
|
|
180
|
+
return parseSlopcameraGalleryVary(value)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function readGalleryCandidates(path: string): Promise<unknown> {
|
|
184
|
+
const text = await readFile(resolve(path), "utf8")
|
|
185
|
+
if (Buffer.byteLength(text, "utf8") > 64 * 1024) {
|
|
186
|
+
throw new Error("--candidates JSON must be no more than 65536 UTF-8 bytes")
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
return JSON.parse(text)
|
|
190
|
+
} catch {
|
|
191
|
+
throw new Error("--candidates must be valid JSON")
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
154
195
|
function printFindings(findings: Awaited<ReturnType<typeof checkDiagramFile>>["findings"]): void {
|
|
155
196
|
if (findings.length === 0) {
|
|
156
197
|
console.log("No diagram lint findings.")
|
|
@@ -191,24 +232,13 @@ const starter = {
|
|
|
191
232
|
edges: [{ id: "source-result", from: "source", to: "result" }],
|
|
192
233
|
}
|
|
193
234
|
|
|
194
|
-
async function confirmInstall(): Promise<boolean> {
|
|
195
|
-
if (!process.stdin.isTTY) {
|
|
196
|
-
throw new Error("Pass --yes to download the 100–230 MB official tldraw Offline installer")
|
|
197
|
-
}
|
|
198
|
-
const prompt = createInterface({ input: process.stdin, output: process.stdout })
|
|
199
|
-
try {
|
|
200
|
-
const answer = await prompt.question(
|
|
201
|
-
"Download, verify, and launch the official tldraw Offline installer? [y/N] ",
|
|
202
|
-
)
|
|
203
|
-
return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes"
|
|
204
|
-
} finally {
|
|
205
|
-
prompt.close()
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
235
|
export interface SlopcameraCliDependencies {
|
|
236
|
+
readonly onUsefulResult?: UsefulResultObserver
|
|
237
|
+
readonly supportEnvironment?: Readonly<Record<string, string | undefined>>
|
|
238
|
+
readonly gallery?: typeof generateSlopcameraImageGallery
|
|
210
239
|
readonly generate?: typeof generateSlopcameraImageFile
|
|
211
240
|
readonly hostResourceCoordinator?: HostResourceCoordinator
|
|
241
|
+
readonly icon?: typeof generateSlopcameraIcon
|
|
212
242
|
readonly log?: (value: string) => void
|
|
213
243
|
readonly vectorize?: typeof vectorizeImage
|
|
214
244
|
}
|
|
@@ -230,26 +260,22 @@ function canonicalArguments(args: readonly string[]): readonly string[] {
|
|
|
230
260
|
throw new Error("Use slopcamera diagram init, check, or render")
|
|
231
261
|
}
|
|
232
262
|
if (surface === "image") {
|
|
233
|
-
if (
|
|
263
|
+
if (
|
|
264
|
+
subcommand === "vectorize" ||
|
|
265
|
+
subcommand === "generate" ||
|
|
266
|
+
subcommand === "icon" ||
|
|
267
|
+
subcommand === "gallery"
|
|
268
|
+
) {
|
|
234
269
|
return [subcommand, ...rest]
|
|
235
270
|
}
|
|
236
|
-
throw new Error("Use slopcamera image vectorize or
|
|
237
|
-
}
|
|
238
|
-
if (surface === "canvas") {
|
|
239
|
-
if (subcommand === "open") return ["open", ...rest]
|
|
240
|
-
if (subcommand === "status" || subcommand === "url" || subcommand === "install") {
|
|
241
|
-
return ["desktop", subcommand, ...rest]
|
|
242
|
-
}
|
|
243
|
-
throw new Error("Use slopcamera canvas open, status, url, or install")
|
|
271
|
+
throw new Error("Use slopcamera image vectorize, generate, icon, or gallery")
|
|
244
272
|
}
|
|
245
273
|
if (
|
|
246
274
|
surface === "init" ||
|
|
247
275
|
surface === "check" ||
|
|
248
276
|
surface === "render" ||
|
|
249
277
|
surface === "vectorize" ||
|
|
250
|
-
surface === "generate"
|
|
251
|
-
surface === "open" ||
|
|
252
|
-
surface === "desktop"
|
|
278
|
+
surface === "generate"
|
|
253
279
|
) {
|
|
254
280
|
throw new Error(`The flat \`${surface}\` command moved to a namespaced Slopcamera surface.\n\n${help}`)
|
|
255
281
|
}
|
|
@@ -260,6 +286,11 @@ export async function main(
|
|
|
260
286
|
args: readonly string[],
|
|
261
287
|
dependencies: SlopcameraCliDependencies = {},
|
|
262
288
|
): Promise<void> {
|
|
289
|
+
if (args[0] === "support") {
|
|
290
|
+
process.exitCode = await runProductSupportCommand(args.slice(1),
|
|
291
|
+
dependencies.supportEnvironment === undefined ? {} : { env: dependencies.supportEnvironment })
|
|
292
|
+
return
|
|
293
|
+
}
|
|
263
294
|
const [command, ...rest] = canonicalArguments(args)
|
|
264
295
|
if (command === undefined || command === "help" || command === "--help" || command === "-h") {
|
|
265
296
|
console.log(help)
|
|
@@ -276,6 +307,7 @@ export async function main(
|
|
|
276
307
|
if (await pathExists(filePath)) throw new Error(`Refusing to overwrite existing file: ${filePath}`)
|
|
277
308
|
await writeFile(filePath, `${JSON.stringify(starter, null, 2)}\n`)
|
|
278
309
|
console.log(`Created ${filePath}`)
|
|
310
|
+
reportUsefulResult(dependencies.onUsefulResult)
|
|
279
311
|
return
|
|
280
312
|
}
|
|
281
313
|
|
|
@@ -317,6 +349,7 @@ export async function main(
|
|
|
317
349
|
)
|
|
318
350
|
console.log(artifactSummary(result.artifacts))
|
|
319
351
|
printFindings(result.findings)
|
|
352
|
+
reportUsefulResult(dependencies.onUsefulResult)
|
|
320
353
|
return
|
|
321
354
|
}
|
|
322
355
|
|
|
@@ -365,6 +398,7 @@ export async function main(
|
|
|
365
398
|
+ `${result.receipt.profile}/${result.receipt.representation}: ${result.outputPath}`,
|
|
366
399
|
)
|
|
367
400
|
}
|
|
401
|
+
reportUsefulResult(dependencies.onUsefulResult)
|
|
368
402
|
return
|
|
369
403
|
}
|
|
370
404
|
|
|
@@ -405,6 +439,173 @@ export async function main(
|
|
|
405
439
|
`Generated ${result.mediaType} with ${result.model}: ${result.outputPath} (${result.bytes} bytes, request ${result.requestId})`,
|
|
406
440
|
)
|
|
407
441
|
}
|
|
442
|
+
reportUsefulResult(dependencies.onUsefulResult)
|
|
443
|
+
return
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (command === "icon") {
|
|
447
|
+
const parsed = parseArguments(
|
|
448
|
+
rest,
|
|
449
|
+
new Set(["model", "output", "ink", "purpose", "rounds", "critique-model"]),
|
|
450
|
+
)
|
|
451
|
+
const unknownFlags = [...parsed.flags].filter(
|
|
452
|
+
(flag) => flag !== "json" && flag !== "keep-raster",
|
|
453
|
+
)
|
|
454
|
+
if (unknownFlags.length > 0) {
|
|
455
|
+
throw new Error(`Unknown icon option: --${unknownFlags[0]}`)
|
|
456
|
+
}
|
|
457
|
+
if (parsed.positionals.length !== 1) {
|
|
458
|
+
throw new Error("slopcamera image icon accepts exactly one subject")
|
|
459
|
+
}
|
|
460
|
+
const output = requiredOption(parsed, "output")
|
|
461
|
+
if (!output.toLowerCase().endsWith(".svg")) {
|
|
462
|
+
throw new Error("--output must end in .svg")
|
|
463
|
+
}
|
|
464
|
+
const model = parsed.options.model ?? slopcameraImageModels[1]
|
|
465
|
+
if (
|
|
466
|
+
model.length > 256 ||
|
|
467
|
+
!/^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._:-]*$/iu.test(model)
|
|
468
|
+
) {
|
|
469
|
+
throw new Error(
|
|
470
|
+
"--model must be a bounded Vercel AI Gateway provider/model id",
|
|
471
|
+
)
|
|
472
|
+
}
|
|
473
|
+
const critiqueModel = parsed.options["critique-model"]
|
|
474
|
+
if (
|
|
475
|
+
critiqueModel !== undefined &&
|
|
476
|
+
(critiqueModel.length > 256 ||
|
|
477
|
+
!/^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._:-]*$/iu.test(critiqueModel))
|
|
478
|
+
) {
|
|
479
|
+
throw new Error(
|
|
480
|
+
"--critique-model must be a bounded Vercel AI Gateway provider/model id",
|
|
481
|
+
)
|
|
482
|
+
}
|
|
483
|
+
const ink = parsed.options.ink
|
|
484
|
+
if (ink !== undefined && !/^#[a-f0-9]{3}(?:[a-f0-9]{3})?$/iu.test(ink)) {
|
|
485
|
+
throw new Error("--ink must be a #rgb or #rrggbb color")
|
|
486
|
+
}
|
|
487
|
+
const purpose = parsed.options.purpose
|
|
488
|
+
if (purpose !== undefined && purpose !== "mark" && purpose !== "illustration") {
|
|
489
|
+
throw new Error("--purpose must be mark or illustration")
|
|
490
|
+
}
|
|
491
|
+
const rounds = parsePositiveInteger(parsed.options.rounds, "rounds")
|
|
492
|
+
if (rounds !== undefined && rounds > slopcameraIconMaximumRounds) {
|
|
493
|
+
throw new Error(`--rounds must be at most ${slopcameraIconMaximumRounds}`)
|
|
494
|
+
}
|
|
495
|
+
const result = await withSlopcameraOperationHostAdmission(
|
|
496
|
+
"slopcamera.image.icon",
|
|
497
|
+
async (lease) => await (dependencies.icon ?? generateSlopcameraIcon)({
|
|
498
|
+
keepRaster: parsed.flags.has("keep-raster"),
|
|
499
|
+
model,
|
|
500
|
+
outputPath: output,
|
|
501
|
+
subject: requiredPositional(parsed, 0, "subject"),
|
|
502
|
+
inheritedFileDescriptors: [lease.inheritedFileDescriptor],
|
|
503
|
+
...(critiqueModel === undefined ? {} : { critiqueModel }),
|
|
504
|
+
...(ink === undefined ? {} : { ink }),
|
|
505
|
+
...(purpose === undefined ? {} : { purpose }),
|
|
506
|
+
...(rounds === undefined ? {} : { rounds }),
|
|
507
|
+
}),
|
|
508
|
+
hostAdmissionOptions(dependencies),
|
|
509
|
+
)
|
|
510
|
+
if (parsed.flags.has("json")) {
|
|
511
|
+
;(dependencies.log ?? console.log)(JSON.stringify(result, null, 2))
|
|
512
|
+
} else {
|
|
513
|
+
const scored = result.attempts
|
|
514
|
+
.map((attempt) =>
|
|
515
|
+
attempt.score === undefined
|
|
516
|
+
? `#${attempt.round}`
|
|
517
|
+
: `#${attempt.round}=${attempt.score}`,
|
|
518
|
+
)
|
|
519
|
+
.join(" ")
|
|
520
|
+
;(dependencies.log ?? console.log)(
|
|
521
|
+
`Icon ${result.svgSha256.slice(0, 12)} round ${result.selectedRound}/${result.attempts.length} (${scored}): ${result.outputPath}`,
|
|
522
|
+
)
|
|
523
|
+
}
|
|
524
|
+
return
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (command === "gallery") {
|
|
528
|
+
const parsed = parseArguments(
|
|
529
|
+
rest,
|
|
530
|
+
new Set(["model", "output-dir", "kind", "count", "vary", "candidates", "cell", "timeout-ms"]),
|
|
531
|
+
)
|
|
532
|
+
const unknownFlags = [...parsed.flags].filter(
|
|
533
|
+
(flag) => flag !== "json" && flag !== "tile" && flag !== "no-tile",
|
|
534
|
+
)
|
|
535
|
+
if (unknownFlags.length > 0) {
|
|
536
|
+
throw new Error(`Unknown gallery option: --${unknownFlags[0]}`)
|
|
537
|
+
}
|
|
538
|
+
if (parsed.flags.has("tile") && parsed.flags.has("no-tile")) {
|
|
539
|
+
throw new Error("--tile and --no-tile are mutually exclusive")
|
|
540
|
+
}
|
|
541
|
+
if (parsed.positionals.length !== 1) {
|
|
542
|
+
throw new Error("slopcamera image gallery accepts exactly one subject")
|
|
543
|
+
}
|
|
544
|
+
const model = parsed.options.model ?? slopcameraImageModels[1]
|
|
545
|
+
if (
|
|
546
|
+
model.length > 256 ||
|
|
547
|
+
!/^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._:-]*$/iu.test(model)
|
|
548
|
+
) {
|
|
549
|
+
throw new Error(
|
|
550
|
+
"--model must be a bounded Vercel AI Gateway provider/model id",
|
|
551
|
+
)
|
|
552
|
+
}
|
|
553
|
+
const kind = parsed.options.kind
|
|
554
|
+
if (kind !== undefined && !slopcameraGalleryKinds.includes(kind as never)) {
|
|
555
|
+
throw new Error(`--kind must be one of: ${slopcameraGalleryKinds.join(", ")}`)
|
|
556
|
+
}
|
|
557
|
+
const count = parsePositiveInteger(parsed.options.count, "count")
|
|
558
|
+
if (count !== undefined && count > slopcameraGalleryLimits.candidates) {
|
|
559
|
+
throw new Error(`--count must be at most ${slopcameraGalleryLimits.candidates}`)
|
|
560
|
+
}
|
|
561
|
+
const cell = parsePositiveInteger(parsed.options.cell, "cell")
|
|
562
|
+
if (
|
|
563
|
+
cell !== undefined &&
|
|
564
|
+
(cell < slopcameraGalleryLimits.cellEdgeMin || cell > slopcameraGalleryLimits.cellEdgeMax)
|
|
565
|
+
) {
|
|
566
|
+
throw new Error(
|
|
567
|
+
`--cell must be between ${slopcameraGalleryLimits.cellEdgeMin} and ${slopcameraGalleryLimits.cellEdgeMax}`,
|
|
568
|
+
)
|
|
569
|
+
}
|
|
570
|
+
const timeoutMs = parsePositiveInteger(parsed.options["timeout-ms"], "timeout-ms")
|
|
571
|
+
const vary = parseGalleryVary(parsed.options.vary)
|
|
572
|
+
const candidates = parsed.options.candidates === undefined
|
|
573
|
+
? undefined
|
|
574
|
+
: await readGalleryCandidates(parsed.options.candidates)
|
|
575
|
+
if (candidates !== undefined && !Array.isArray(candidates)) {
|
|
576
|
+
throw new Error("--candidates JSON must be an array of {id, prompt|variant} entries")
|
|
577
|
+
}
|
|
578
|
+
const result = await withSlopcameraOperationHostAdmission(
|
|
579
|
+
"slopcamera.image.gallery",
|
|
580
|
+
async () => await (dependencies.gallery ?? generateSlopcameraImageGallery)({
|
|
581
|
+
subject: requiredPositional(parsed, 0, "subject"),
|
|
582
|
+
outputDir: requiredOption(parsed, "output-dir"),
|
|
583
|
+
model,
|
|
584
|
+
...(kind === undefined ? {} : { kind }),
|
|
585
|
+
...(count === undefined ? {} : { count }),
|
|
586
|
+
...(vary === undefined ? {} : { vary }),
|
|
587
|
+
...(candidates === undefined ? {} : { candidates }),
|
|
588
|
+
...(cell === undefined ? {} : { cellEdge: cell }),
|
|
589
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
590
|
+
...(parsed.flags.has("tile")
|
|
591
|
+
? { tiled: true }
|
|
592
|
+
: parsed.flags.has("no-tile") ? { tiled: false } : {}),
|
|
593
|
+
}),
|
|
594
|
+
hostAdmissionOptions(dependencies),
|
|
595
|
+
)
|
|
596
|
+
if (parsed.flags.has("json")) {
|
|
597
|
+
;(dependencies.log ?? console.log)(JSON.stringify(result, null, 2))
|
|
598
|
+
} else {
|
|
599
|
+
const rows = result.candidates.map(candidate =>
|
|
600
|
+
candidate.status === "generated"
|
|
601
|
+
? `#${candidate.index} ${candidate.id}\t${candidate.path}`
|
|
602
|
+
: `#${candidate.index} ${candidate.id}\tfailed`)
|
|
603
|
+
;(dependencies.log ?? console.log)(
|
|
604
|
+
`Gallery ${result.counts.generated}/${result.counts.requested} generated (${result.model}): ${result.gallery.path}\n`
|
|
605
|
+
+ `${rows.join("\n")}\nreceipt ${result.receiptPath}`,
|
|
606
|
+
)
|
|
607
|
+
}
|
|
608
|
+
reportUsefulResult(dependencies.onUsefulResult)
|
|
408
609
|
return
|
|
409
610
|
}
|
|
410
611
|
|
|
@@ -460,6 +661,9 @@ export async function main(
|
|
|
460
661
|
;(dependencies.log ?? console.log)(
|
|
461
662
|
JSON.stringify({ operation, result }, null, 2),
|
|
462
663
|
)
|
|
664
|
+
if (operation === "slopcamera.diagram.render" || operation === "slopcamera.image.vectorize" || operation === "slopcamera.image.generate") {
|
|
665
|
+
reportUsefulResult(dependencies.onUsefulResult)
|
|
666
|
+
}
|
|
463
667
|
return
|
|
464
668
|
}
|
|
465
669
|
throw new Error(
|
|
@@ -479,15 +683,7 @@ export async function main(
|
|
|
479
683
|
return
|
|
480
684
|
}
|
|
481
685
|
|
|
482
|
-
if (command === "open") {
|
|
483
|
-
const parsed = parseArguments(rest, new Set())
|
|
484
|
-
await openInDesktop(requiredPositional(parsed, 0, "tldraw file"))
|
|
485
|
-
console.log("Opened in tldraw Offline.")
|
|
486
|
-
return
|
|
487
|
-
}
|
|
488
|
-
|
|
489
686
|
if (command === "doctor") {
|
|
490
|
-
const status = await desktopStatus()
|
|
491
687
|
console.log(`slopcamera ${slopcameraCliVersion}`)
|
|
492
688
|
console.log(`Bun ${process.versions.bun ?? "not detected"}`)
|
|
493
689
|
console.log("Headless diagram SVG/PNG/tldraw renderer ready")
|
|
@@ -496,67 +692,16 @@ export async function main(
|
|
|
496
692
|
? "Local raster-to-SVG vectorizer unavailable on Windows (fails closed with tool_platform)"
|
|
497
693
|
: "Local raster-to-SVG vectorizer ready without authentication (VTracer downloads on first use)",
|
|
498
694
|
)
|
|
499
|
-
console.log("Root-relative MCP check/render server ready (trusted local workspace)")
|
|
695
|
+
console.log("Root-relative MCP check/render/scene server ready (trusted local workspace)")
|
|
500
696
|
const gateway = slopcameraGatewayCredentialStatus()
|
|
501
697
|
console.log(
|
|
502
698
|
gateway.available
|
|
503
699
|
? `Vercel AI Gateway ready via ${gateway.source}`
|
|
504
700
|
: "Vercel AI Gateway requires AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN",
|
|
505
701
|
)
|
|
506
|
-
console.log(
|
|
507
|
-
status.installedPath === null
|
|
508
|
-
? "tldraw Offline not installed (optional)"
|
|
509
|
-
: `tldraw Offline: ${status.installedPath}`,
|
|
510
|
-
)
|
|
511
|
-
console.log(
|
|
512
|
-
status.server === null
|
|
513
|
-
? "tldraw Offline agent server not running (optional)"
|
|
514
|
-
: `tldraw Offline agent server: localhost:${status.server.port}`,
|
|
515
|
-
)
|
|
516
702
|
return
|
|
517
703
|
}
|
|
518
704
|
|
|
519
|
-
if (command === "desktop") {
|
|
520
|
-
const [subcommand, ...subcommandArgs] = rest
|
|
521
|
-
if (subcommand === "status") {
|
|
522
|
-
const status = await desktopStatus()
|
|
523
|
-
console.log(JSON.stringify(status, null, 2))
|
|
524
|
-
return
|
|
525
|
-
}
|
|
526
|
-
if (subcommand === "url") {
|
|
527
|
-
const release = await getLatestDesktopRelease()
|
|
528
|
-
const asset = selectDesktopAsset(release)
|
|
529
|
-
console.log(
|
|
530
|
-
JSON.stringify(
|
|
531
|
-
{
|
|
532
|
-
release: release.tag_name,
|
|
533
|
-
releaseUrl: release.html_url,
|
|
534
|
-
asset: asset.name,
|
|
535
|
-
url: asset.browser_download_url,
|
|
536
|
-
bytes: asset.size,
|
|
537
|
-
sha256: asset.digest,
|
|
538
|
-
},
|
|
539
|
-
null,
|
|
540
|
-
2,
|
|
541
|
-
),
|
|
542
|
-
)
|
|
543
|
-
return
|
|
544
|
-
}
|
|
545
|
-
if (subcommand === "install") {
|
|
546
|
-
const parsed = parseArguments(subcommandArgs, new Set())
|
|
547
|
-
if (!parsed.flags.has("yes") && !(await confirmInstall())) {
|
|
548
|
-
console.log("Cancelled.")
|
|
549
|
-
return
|
|
550
|
-
}
|
|
551
|
-
const result = await installDesktop({ downloadOnly: parsed.flags.has("download-only") })
|
|
552
|
-
console.log(
|
|
553
|
-
`${parsed.flags.has("download-only") ? "Downloaded" : "Prepared"} tldraw Offline ${result.release}: ${result.filePath}`,
|
|
554
|
-
)
|
|
555
|
-
return
|
|
556
|
-
}
|
|
557
|
-
throw new Error("Use slopcamera canvas status, url, or install")
|
|
558
|
-
}
|
|
559
|
-
|
|
560
705
|
if (command === "skill") {
|
|
561
706
|
const [subcommand, ...subcommandArgs] = rest
|
|
562
707
|
if (subcommand === "path") {
|
|
@@ -593,7 +738,10 @@ export async function main(
|
|
|
593
738
|
|
|
594
739
|
if (import.meta.main) {
|
|
595
740
|
try {
|
|
596
|
-
|
|
741
|
+
const env = standaloneSupportEnvironment()
|
|
742
|
+
let usefulResult = false
|
|
743
|
+
await main(process.argv.slice(2), { supportEnvironment: env, onUsefulResult: () => { usefulResult = true } })
|
|
744
|
+
if (usefulResult && (process.exitCode ?? 0) === 0) await showProductSupportInvitation({ env })
|
|
597
745
|
} catch (error) {
|
|
598
746
|
console.error(error instanceof Error ? error.message : String(error))
|
|
599
747
|
process.exitCode = 1
|
|
@@ -19,6 +19,15 @@ const MAX_CANONICAL_JSON_BYTES = Number.MAX_SAFE_INTEGER
|
|
|
19
19
|
const MAX_CANONICAL_JSON_DEPTH = Number.MAX_SAFE_INTEGER
|
|
20
20
|
const MAX_CANONICAL_JSON_VALUES = Number.MAX_SAFE_INTEGER
|
|
21
21
|
|
|
22
|
+
// Cross-implementation parity (pinned byte- and digest-for-digest by
|
|
23
|
+
// canonical-parity.test.ts): on the shared domain — finite numbers other
|
|
24
|
+
// than -0, well-formed UTF-16 strings, dense arrays, and plain objects
|
|
25
|
+
// with enumerable string data properties — canonicalJson is identical to
|
|
26
|
+
// @hraness/oh's RFC 8785 canonicalizer. Slopcamera intentionally keeps a
|
|
27
|
+
// wider domain that oh rejects: -0 normalizes to 0, unpaired surrogates
|
|
28
|
+
// serialize through \uXXXX escapes, non-enumerable own properties and
|
|
29
|
+
// non-index array properties are ignored, and the iterative traversal
|
|
30
|
+
// accepts nesting far beyond oh's recursive stack range.
|
|
22
31
|
export type CanonicalJsonValue =
|
|
23
32
|
| JsonScalar
|
|
24
33
|
| readonly CanonicalJsonValue[]
|
package/src/generate.ts
CHANGED
|
@@ -116,7 +116,7 @@ function environment(
|
|
|
116
116
|
return injected ?? process.env
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
function
|
|
119
|
+
export function resolveSlopcameraGatewayCredential(
|
|
120
120
|
injected: SlopcameraEnvironment | undefined,
|
|
121
121
|
): Readonly<{
|
|
122
122
|
source: SlopcameraGatewayCredentialSource
|
|
@@ -588,7 +588,7 @@ async function performGeneration(
|
|
|
588
588
|
}>> {
|
|
589
589
|
const model = validateModel(input.model)
|
|
590
590
|
const prompt = validatePrompt(input.prompt)
|
|
591
|
-
const credential =
|
|
591
|
+
const credential = resolveSlopcameraGatewayCredential(dependencies.environment)
|
|
592
592
|
const timeout = combineSignals(input.signal, validateTimeout(input.timeoutMs))
|
|
593
593
|
try {
|
|
594
594
|
const generation = (async () => {
|