@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.
Files changed (188) hide show
  1. package/NOTICE.md +1 -7
  2. package/PRIVACY.md +16 -7
  3. package/README.md +65 -20
  4. package/apps/desktop/README.md +14 -130
  5. package/apps/desktop/application/context.ts +6 -9
  6. package/apps/desktop/application/default-registry.ts +26 -10
  7. package/apps/desktop/application/operation.ts +14 -5
  8. package/apps/desktop/application/operations/index.ts +4 -1
  9. package/apps/desktop/application/operations/spatial-behavior.ts +151 -0
  10. package/apps/desktop/application/operations/spatial-direction.ts +222 -0
  11. package/apps/desktop/application/operations/spatial-rendered-audit.ts +174 -0
  12. package/apps/desktop/application/operations/spatial-review.ts +194 -0
  13. package/apps/desktop/application/operations/spatial-scene.ts +25 -1
  14. package/apps/desktop/application/registry.ts +2 -2
  15. package/apps/desktop/application/spatial-asset-admission.ts +201 -0
  16. package/apps/desktop/application/spatial-assets.ts +160 -31
  17. package/apps/desktop/application/spatial-render.ts +217 -20
  18. package/apps/desktop/application/spatial-rendered-audit.ts +273 -0
  19. package/apps/desktop/application/spatial-review.ts +361 -0
  20. package/apps/desktop/application/spatial-spz.ts +48 -3
  21. package/apps/desktop/application/spatial-world-import.ts +26 -5
  22. package/apps/desktop/application/spatial-world-metadata.ts +105 -0
  23. package/apps/desktop/cli/args.ts +528 -97
  24. package/apps/desktop/cli/capability-manifest.ts +374 -0
  25. package/apps/desktop/cli/cinema-renderer.ts +608 -0
  26. package/apps/desktop/cli/command-host-resources.ts +31 -2
  27. package/apps/desktop/cli/commands.ts +1215 -70
  28. package/apps/desktop/cli/gateway-review-provider.ts +334 -0
  29. package/apps/desktop/cli/help.ts +183 -23
  30. package/apps/desktop/cli/html-scene.ts +139 -28
  31. package/apps/desktop/cli/io.ts +2 -1
  32. package/apps/desktop/cli/main.ts +26 -50
  33. package/apps/desktop/cli/menubar.ts +244 -0
  34. package/apps/desktop/cli/music-analysis-service.ts +61 -0
  35. package/apps/desktop/cli/portable-surface.ts +18 -5
  36. package/apps/desktop/cli/spatial-asset-service.ts +124 -0
  37. package/apps/desktop/cli/spatial-design-service.ts +237 -0
  38. package/apps/desktop/cli/spatial-generate-service.ts +370 -0
  39. package/apps/desktop/cli/spatial-scene-service.ts +268 -4
  40. package/apps/desktop/cli/workflow-code.ts +1 -1
  41. package/apps/desktop/code/application-node-planner.ts +0 -47
  42. package/apps/desktop/code/host-source-layout.ts +46 -0
  43. package/apps/desktop/code/public.ts +0 -1
  44. package/apps/desktop/code/runtime-identity.ts +50 -8
  45. package/apps/desktop/code/semantic-builder.ts +24 -43
  46. package/apps/desktop/code/source-bundle.ts +23 -6
  47. package/apps/desktop/code/source-typecheck.ts +131 -43
  48. package/apps/desktop/code/worker-client.ts +14 -2
  49. package/apps/desktop/contracts/cinema.ts +736 -0
  50. package/apps/desktop/contracts/index.ts +1 -0
  51. package/apps/desktop/contracts/spatial-asset.ts +16 -0
  52. package/apps/desktop/contracts/spatial-world.ts +26 -3
  53. package/apps/desktop/core/cinema-plan.ts +1320 -0
  54. package/apps/desktop/core/index.ts +1 -0
  55. package/apps/desktop/core/music-analysis.ts +124 -1
  56. package/apps/desktop/core/storage.ts +75 -0
  57. package/apps/desktop/dist/cli/main.js +737 -291
  58. package/apps/desktop/html-overlay/audio-reactivity.ts +127 -0
  59. package/apps/desktop/html-overlay/index.ts +9 -0
  60. package/apps/desktop/html-overlay/runtime.ts +2 -0
  61. package/apps/desktop/html-overlay/scene.ts +29 -0
  62. package/apps/desktop/html-overlay/spatial.ts +619 -57
  63. package/apps/desktop/workflows/cinematic-world.ts +127 -0
  64. package/apps/desktop/workflows/index.ts +6 -0
  65. package/dist/cli.js +32 -434
  66. package/dist/code/advanced.js +1 -269
  67. package/dist/code/index.js +16 -2239
  68. package/dist/generate.js +1 -23
  69. package/dist/host-resources.js +1 -25
  70. package/dist/index-0bbesn4m.js +35 -0
  71. package/dist/index-296qhpf2.js +3 -0
  72. package/dist/index-3cxf2c58.js +3 -0
  73. package/dist/index-77fjfg9f.js +3 -0
  74. package/dist/index-80a36bc0.js +3 -0
  75. package/dist/index-a3vvs3rf.js +3 -0
  76. package/dist/index-b3geqczn.js +5 -0
  77. package/dist/index-cgpkyjqa.js +3 -0
  78. package/dist/index-f0ptv8dm.js +7 -0
  79. package/dist/index-grk47tgs.js +3 -0
  80. package/dist/index-qry58nj2.js +10 -0
  81. package/dist/index.js +1 -240
  82. package/dist/operations.js +1 -28
  83. package/dist/skill-install-gx7afxsd.js +2 -0
  84. package/dist/vectorize/worker.js +1 -141
  85. package/dist/workflow.js +1 -15
  86. package/docs/README.md +5 -1
  87. package/docs/studio.md +1 -1
  88. package/package.json +11 -24
  89. package/skills/slopcamera/SKILL.md +14 -1
  90. package/skills/slopcamera/references/diagrams.md +5 -14
  91. package/skills/slopcamera/references/directed-scenes.md +22 -2
  92. package/skills/slopcamera/references/gateway-media.md +15 -1
  93. package/skills/slopcamera/references/image-galleries.md +166 -0
  94. package/skills/slopcamera/references/install.md +2 -2
  95. package/skills/slopcamera/references/music-video.md +23 -3
  96. package/skills/slopcamera/references/parametric-design.md +14 -0
  97. package/skills/slopcamera/references/reference-led-3d.md +1 -1
  98. package/skills/slopcamera/references/scene-building.md +32 -0
  99. package/skills/slopcamera/references/social-collage-banners.md +204 -0
  100. package/skills/slopcamera/references/support.md +25 -0
  101. package/skills/slopcamera/references/web-media-excerpts.md +120 -0
  102. package/skills/slopcamera/references/workflows-sdk.md +3 -3
  103. package/skills/slopcamera/scripts/compose-social-collage-banner.ts +728 -0
  104. package/src/capability-manifest.ts +361 -0
  105. package/src/cli.ts +250 -102
  106. package/src/code/canonical-json.ts +9 -0
  107. package/src/generate.ts +2 -2
  108. package/src/icon.ts +1111 -0
  109. package/src/image-gallery.ts +1049 -0
  110. package/src/index.ts +11 -23
  111. package/src/mcp/boundary.ts +11 -11
  112. package/src/mcp/index.ts +3 -0
  113. package/src/mcp/server.ts +2 -1
  114. package/src/mcp/tools.ts +1642 -130
  115. package/src/mcp/types.ts +19 -0
  116. package/src/operations.ts +412 -0
  117. package/src/portable-capability-manifest.ts +67 -0
  118. package/src/scene-gallery.ts +184 -0
  119. package/src/spatial-scene/asset-admission.ts +183 -0
  120. package/src/spatial-scene/audit-rendered.ts +692 -0
  121. package/src/spatial-scene/audit.ts +617 -0
  122. package/src/spatial-scene/behavior-audit.ts +252 -0
  123. package/src/spatial-scene/behavior-bake.ts +233 -0
  124. package/src/spatial-scene/behavior-fns.ts +585 -0
  125. package/src/spatial-scene/behavior-gallery.ts +109 -0
  126. package/src/spatial-scene/behavior-stdlib.ts +323 -0
  127. package/src/spatial-scene/behavior-trace.ts +325 -0
  128. package/src/spatial-scene/behavior.ts +618 -0
  129. package/src/spatial-scene/build.ts +632 -0
  130. package/src/spatial-scene/camera-rig.ts +125 -0
  131. package/src/spatial-scene/camera-track.ts +4 -2
  132. package/src/spatial-scene/character.ts +124 -0
  133. package/src/spatial-scene/contracts.ts +75 -7
  134. package/src/spatial-scene/design-templates.ts +351 -0
  135. package/src/spatial-scene/design.ts +327 -0
  136. package/src/spatial-scene/direction-compile.ts +584 -0
  137. package/src/spatial-scene/direction.ts +122 -0
  138. package/src/spatial-scene/effects.ts +149 -0
  139. package/src/spatial-scene/evaluate.ts +109 -29
  140. package/src/spatial-scene/gallery.ts +277 -0
  141. package/src/spatial-scene/generate.ts +239 -0
  142. package/src/spatial-scene/geometry-native.ts +165 -0
  143. package/src/spatial-scene/geometry.ts +1216 -0
  144. package/src/spatial-scene/gltf.ts +600 -77
  145. package/src/spatial-scene/identity.ts +55 -2
  146. package/src/spatial-scene/index.ts +36 -0
  147. package/src/spatial-scene/inspect.ts +35 -6
  148. package/src/spatial-scene/material-lighting.ts +609 -0
  149. package/src/spatial-scene/math.ts +21 -4
  150. package/src/spatial-scene/motion-evidence.ts +88 -0
  151. package/src/spatial-scene/parametric.ts +765 -0
  152. package/src/spatial-scene/particle-preparation.ts +183 -0
  153. package/src/spatial-scene/particle.ts +195 -0
  154. package/src/spatial-scene/patch.ts +109 -15
  155. package/src/spatial-scene/performance.ts +1613 -0
  156. package/src/spatial-scene/probe.ts +137 -0
  157. package/src/spatial-scene/recipe-pack.ts +86 -0
  158. package/src/spatial-scene/render-effects.ts +240 -0
  159. package/src/spatial-scene/review.ts +639 -0
  160. package/src/spatial-scene/simulation-bake.ts +206 -0
  161. package/src/spatial-scene/simulation.ts +187 -0
  162. package/src/spatial-scene/solve.ts +366 -0
  163. package/src/spatial-scene/temporal-audit.ts +354 -0
  164. package/src/support-completion.ts +16 -0
  165. package/src/support.ts +42 -0
  166. package/src/version.ts +1 -1
  167. package/apps/desktop/application/operations/recording/index.ts +0 -5
  168. package/apps/desktop/application/operations/recording/pause.ts +0 -34
  169. package/apps/desktop/application/operations/recording/resume.ts +0 -34
  170. package/apps/desktop/application/operations/recording/shared.ts +0 -199
  171. package/apps/desktop/application/operations/recording/start.ts +0 -169
  172. package/apps/desktop/application/operations/recording/stop.ts +0 -34
  173. package/apps/desktop/capture/protocol.ts +0 -689
  174. package/apps/desktop/cli/capture-bundle.ts +0 -1117
  175. package/apps/desktop/cli/recording-controller.ts +0 -1567
  176. package/apps/desktop/cli/recording-daemon.ts +0 -931
  177. package/dist/index-42zsesc1.js +0 -2257
  178. package/dist/index-7308egqr.js +0 -48
  179. package/dist/index-8txs6fkn.js +0 -712
  180. package/dist/index-mcy8z0br.js +0 -192
  181. package/dist/index-p63wavx0.js +0 -2029
  182. package/dist/index-r7gdhmsp.js +0 -424
  183. package/dist/index-rvghzt7b.js +0 -2013
  184. package/dist/index-sh6xbav6.js +0 -1219
  185. package/dist/index-z1w83f81.js +0 -4
  186. package/dist/index-z7239b4h.js +0 -2363
  187. package/dist/skill-install-dh0ntqba.js +0 -10
  188. package/src/desktop.ts +0 -253
package/dist/cli.js CHANGED
@@ -1,49 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
- import {
4
- SLOPCAMERA_VERSION,
5
- artifactSummary,
6
- checkDiagramFile,
7
- desktopStatus,
8
- getLatestDesktopRelease,
9
- installDesktop,
10
- openInDesktop,
11
- renderDiagramFile,
12
- runMcpServer,
13
- selectDesktopAsset
14
- } from "./index-rvghzt7b.js";
15
- import {
16
- installSkill,
17
- pathExists
18
- } from "./index-7308egqr.js";
19
- import"./index-8txs6fkn.js";
20
- import"./index-mcy8z0br.js";
21
- import {
22
- executeSlopcameraOperation,
23
- isSlopcameraOperationCode,
24
- searchSlopcameraOperations,
25
- slopcameraOperationCodes,
26
- withSlopcameraOperationHostAdmission
27
- } from "./index-z7239b4h.js";
28
- import {
29
- vectorizeImage
30
- } from "./index-p63wavx0.js";
31
- import {
32
- generateSlopcameraImageFile,
33
- slopcameraGatewayCredentialStatus,
34
- slopcameraImageModels
35
- } from "./index-r7gdhmsp.js";
36
- import"./index-sh6xbav6.js";
37
- import {
38
- __require
39
- } from "./index-z1w83f81.js";
40
-
41
- // src/cli.ts
42
- import { writeFile } from "fs/promises";
43
- import { resolve } from "path";
44
- import { createInterface } from "readline/promises";
45
- var slopcameraCliVersion = SLOPCAMERA_VERSION;
46
- var help = `slopcamera ${slopcameraCliVersion}
3
+ import{SLOPCAMERA_VERSION,artifactSummary,checkDiagramFile,renderDiagramFile,runMcpServer}from"./index-f0ptv8dm.js";import{installSkill,pathExists}from"./index-grk47tgs.js";import"./index-cgpkyjqa.js";import"./index-3cxf2c58.js";import"./index-296qhpf2.js";import{executeSlopcameraOperation,generateSlopcameraIcon,generateSlopcameraImageGallery,isSlopcameraOperationCode,parseSlopcameraGalleryVary,searchSlopcameraOperations,slopcameraGalleryKinds,slopcameraGalleryLimits,slopcameraIconMaximumRounds,slopcameraOperationCodes,withSlopcameraOperationHostAdmission}from"./index-0bbesn4m.js";import{vectorizeImage}from"./index-qry58nj2.js";import{generateSlopcameraImageFile,slopcameraGatewayCredentialStatus,slopcameraImageModels}from"./index-77fjfg9f.js";import"./index-b3geqczn.js";import{__require}from"./index-80a36bc0.js";import{readFile,writeFile}from"fs/promises";import{resolve}from"path";function reportUsefulResult(observer){try{if(observer!==undefined)Promise.resolve(observer()).catch(()=>{return})}catch{}}import{maybeShowSupportInvitation,runSupportCommand}from"@hraness/support-foundation/node";var supportProfile={id:"slopcamera",name:"Slopcamera",valueProposition:"Support ongoing development of local visual tools for agents.",updates:false};function standaloneSupportEnvironment(){const env={...process.env};process.env.HRANESS_SUPPORT_AUDIENCE="off";process.env.HRANESS_SUPPORT_EMAIL="off";return env}async function runProductSupportCommand(args,options={}){const result=await runSupportCommand(supportProfile,args,{command:["slopcamera"],...options,gitEmail:false});if(result.stdout!=="")process.stdout.write(result.stdout);if(result.stderr!=="")process.stderr.write(result.stderr);return result.exitCode}async function showProductSupportInvitation(options={}){try{await maybeShowSupportInvitation(supportProfile,{usefulResult:true,command:["slopcamera"],...options,gitEmail:false})}catch{}}var slopcameraCliVersion=SLOPCAMERA_VERSION;var help=`slopcamera ${slopcameraCliVersion}
47
4
 
48
5
  Turn source material into deterministic diagrams, images, and canvas assets.
49
6
 
@@ -53,14 +10,17 @@ Usage:
53
10
  slopcamera diagram render <file> [--out-dir <directory>] [--config <file>] [--scale <number>]
54
11
  slopcamera image vectorize <image> --output <file.svg> [--json] [--duotone <#rgb,#rgb>]
55
12
  slopcamera image generate <prompt> --output <file.png|jpg|webp> [--model <provider/model>] [--json]
13
+ slopcamera image icon <subject> --output <file.svg> [--purpose <mark|illustration>]
14
+ [--model <provider/model>] [--ink <#rgb|#rrggbb>] [--rounds <1-${slopcameraIconMaximumRounds}>]
15
+ [--critique-model <provider/model>] [--keep-raster] [--json]
16
+ slopcamera image gallery <subject> --output-dir <directory> [--kind <${slopcameraGalleryKinds.join("|")}>]
17
+ [--count <1-${slopcameraGalleryLimits.candidates}>] [--vary <axis[=v1,v2][;axis...]>] [--candidates <file.json>]
18
+ [--model <provider/model>] [--cell <${slopcameraGalleryLimits.cellEdgeMin}-${slopcameraGalleryLimits.cellEdgeMax}>] [--tile|--no-tile] [--json]
56
19
  slopcamera code search [query] [--limit <number>]
57
20
  slopcamera code execute <operation> --input <JSON>
58
21
  slopcamera mcp --root <workspace>
59
- slopcamera canvas open <file.tldr|file.tldraw>
60
- slopcamera canvas status
61
- slopcamera canvas url
62
- slopcamera canvas install [--yes] [--download-only]
63
22
  slopcamera doctor
23
+ slopcamera support [--json|protocol --json|offer --json|shown <id>|release <id>|dismiss|snooze|enable|status --json]
64
24
  slopcamera skill path
65
25
  slopcamera skill install [--target codex|claude|agents] [--scope user|project] [--force]
66
26
 
@@ -71,9 +31,8 @@ Render writes the same five replaceable artifacts on every run:
71
31
  <name>.light.png
72
32
  <name>.dark.png
73
33
 
74
- The .tldr file is editable tldraw interchange. It imports into tldraw Offline,
75
- which can save the newer app-owned .tldraw bundle. Rendering does not require
76
- tldraw Offline or the tldraw SDK.
34
+ The .tldr file is editable interchange for browser-based canvas tooling.
35
+ Rendering does not require a desktop application or a bundled UI runtime.
77
36
 
78
37
  Vectorize adaptively traces a raster with a checksum-pinned VTracer binary.
79
38
  It enforces bounded input, decode, time, path, and output budgets and emits a
@@ -85,6 +44,21 @@ Set AI_GATEWAY_API_KEY, or run through \`vercel env run -- \u2026\` so
85
44
  VERCEL_OIDC_TOKEN is available. Slopcamera never stores or prints the token.
86
45
  PNG, JPEG, and WebP responses are signature-checked and published atomically.
87
46
 
47
+ Icon produces isometric line-art SVG: a style-locked Gateway raster is
48
+ normalized to canonical ink-on-transparent pixels, traced by the local
49
+ vectorizer, and (when --rounds exceeds 1) critiqued by a vision model whose
50
+ feedback revises the prompt for the next attempt. Only Slopcamera's own
51
+ generated output is uploaded for critique \u2014 never user media.
52
+
53
+ Gallery generates several bounded candidates in parallel and composes them
54
+ into one labelled contact sheet plus a receipt. Use it to review texture,
55
+ skybox, backdrop, sprite, or design alternatives, then promote a chosen
56
+ candidate file explicitly \u2014 nothing is applied automatically.
57
+
58
+ Optional support: after useful work, agents can read slopcamera support protocol --json.
59
+ Discovery uses stderr without claiming an invitation; HRANESS_SUPPORT_AUDIENCE=off disables it.
60
+ No feature requires payment. Imported CLI/SDK calls and probes stay quiet.
61
+
88
62
  Code mode searches and executes a fixed semantic registry. Execute accepts
89
63
  typed JSON for one exact owned operation code; it never evaluates source text.
90
64
 
@@ -92,388 +66,12 @@ MCP preserves root-relative check_diagram/render_diagram and adds closed
92
66
  search_slopcamera/execute_slopcamera registry tools. It uses built-in assets, never
93
67
  executes workspace config or caller code, and writes protocol messages only to
94
68
  stdout.
95
- `;
96
- function parseArguments(args, valueOptions) {
97
- const positionals = [];
98
- const options = {};
99
- const flags = new Set;
100
- for (let index = 0;index < args.length; index += 1) {
101
- const argument = args[index];
102
- if (argument === undefined)
103
- continue;
104
- if (!argument.startsWith("--")) {
105
- positionals.push(argument);
106
- continue;
107
- }
108
- const name = argument.slice(2);
109
- if (!valueOptions.has(name)) {
110
- flags.add(name);
111
- continue;
112
- }
113
- const value = args[index + 1];
114
- if (value === undefined || value.startsWith("--")) {
115
- throw new Error(`--${name} requires a value`);
116
- }
117
- options[name] = value;
118
- index += 1;
119
- }
120
- return { positionals, options, flags };
121
- }
122
- function requiredPositional(parsed, index, label) {
123
- const value = parsed.positionals[index];
124
- if (value === undefined)
125
- throw new Error(`Missing ${label}`);
126
- return value;
127
- }
128
- function requiredOption(parsed, name) {
129
- const value = parsed.options[name];
130
- if (value === undefined)
131
- throw new Error(`--${name} is required`);
132
- return value;
133
- }
134
- function parsePositiveInteger(value, name) {
135
- if (value === undefined)
136
- return;
137
- const parsed = Number(value);
138
- if (!Number.isInteger(parsed) || parsed < 1) {
139
- throw new Error(`--${name} must be a positive integer`);
140
- }
141
- return parsed;
142
- }
143
- function parseDuotone(value) {
144
- if (value === undefined)
145
- return;
146
- const colors = value.split(",").map((color) => color.trim());
147
- if (colors.length !== 2 || colors.some((color) => !/^#[a-f0-9]{3}(?:[a-f0-9]{3})?$/iu.test(color))) {
148
- throw new Error("--duotone must contain two #rgb or #rrggbb colors separated by a comma");
149
- }
150
- return [colors[0], colors[1]];
151
- }
152
- function printFindings(findings) {
153
- if (findings.length === 0) {
154
- console.log("No diagram lint findings.");
155
- return;
156
- }
157
- console.warn(`${findings.length} diagram lint finding${findings.length === 1 ? "" : "s"}:`);
158
- for (const finding of findings) {
159
- console.warn(` [${finding.code}] ${finding.message}`);
160
- }
161
- }
162
- var starter = {
163
- $schema: "https://raw.githubusercontent.com/hraness/slopcamera/main/schema/diagram.schema.json",
164
- version: 1,
165
- name: "example-flow",
166
- canvas: { width: 960, height: 540, padding: 64 },
167
- layout: { type: "stack", direction: "horizontal", gap: 160, align: "center" },
168
- shapes: [
169
- {
170
- id: "source",
171
- type: "rect",
172
- width: 240,
173
- height: 160,
174
- label: "Source",
175
- icon: "document",
176
- tone: "blue"
177
- },
178
- {
179
- id: "result",
180
- type: "rect",
181
- width: 240,
182
- height: 160,
183
- label: "Result",
184
- icon: "check",
185
- tone: "green"
186
- }
187
- ],
188
- edges: [{ id: "source-result", from: "source", to: "result" }]
189
- };
190
- async function confirmInstall() {
191
- if (!process.stdin.isTTY) {
192
- throw new Error("Pass --yes to download the 100\u2013230 MB official tldraw Offline installer");
193
- }
194
- const prompt = createInterface({ input: process.stdin, output: process.stdout });
195
- try {
196
- const answer = await prompt.question("Download, verify, and launch the official tldraw Offline installer? [y/N] ");
197
- return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
198
- } finally {
199
- prompt.close();
200
- }
201
- }
202
- function hostAdmissionOptions(dependencies) {
203
- return dependencies.hostResourceCoordinator === undefined ? {} : { hostResourceCoordinator: dependencies.hostResourceCoordinator };
204
- }
205
- function canonicalArguments(args) {
206
- const [surface, subcommand, ...rest] = args;
207
- if (surface === "diagram") {
208
- if (subcommand === "init" || subcommand === "check" || subcommand === "render") {
209
- return [subcommand, ...rest];
210
- }
211
- throw new Error("Use slopcamera diagram init, check, or render");
212
- }
213
- if (surface === "image") {
214
- if (subcommand === "vectorize" || subcommand === "generate") {
215
- return [subcommand, ...rest];
216
- }
217
- throw new Error("Use slopcamera image vectorize or generate");
218
- }
219
- if (surface === "canvas") {
220
- if (subcommand === "open")
221
- return ["open", ...rest];
222
- if (subcommand === "status" || subcommand === "url" || subcommand === "install") {
223
- return ["desktop", subcommand, ...rest];
224
- }
225
- throw new Error("Use slopcamera canvas open, status, url, or install");
226
- }
227
- if (surface === "init" || surface === "check" || surface === "render" || surface === "vectorize" || surface === "generate" || surface === "open" || surface === "desktop") {
228
- throw new Error(`The flat \`${surface}\` command moved to a namespaced Slopcamera surface.
69
+ `;function parseArguments(args,valueOptions){const positionals=[];const options={};const flags=new Set;for(let index=0;index<args.length;index+=1){const argument=args[index];if(argument===undefined)continue;if(!argument.startsWith("--")){positionals.push(argument);continue}const name=argument.slice(2);if(!valueOptions.has(name)){flags.add(name);continue}const value=args[index+1];if(value===undefined||value.startsWith("--")){throw new Error(`--${name} requires a value`)}options[name]=value;index+=1}return{positionals,options,flags}}function requiredPositional(parsed,index,label){const value=parsed.positionals[index];if(value===undefined)throw new Error(`Missing ${label}`);return value}function requiredOption(parsed,name){const value=parsed.options[name];if(value===undefined)throw new Error(`--${name} is required`);return value}function parsePositiveInteger(value,name){if(value===undefined)return;const parsed=Number(value);if(!Number.isInteger(parsed)||parsed<1){throw new Error(`--${name} must be a positive integer`)}return parsed}function parseDuotone(value){if(value===undefined)return;const colors=value.split(",").map((color)=>color.trim());if(colors.length!==2||colors.some((color)=>!/^#[a-f0-9]{3}(?:[a-f0-9]{3})?$/iu.test(color))){throw new Error("--duotone must contain two #rgb or #rrggbb colors separated by a comma")}return[colors[0],colors[1]]}function parseGalleryVary(value){if(value===undefined)return;return parseSlopcameraGalleryVary(value)}async function readGalleryCandidates(path){const text=await readFile(resolve(path),"utf8");if(Buffer.byteLength(text,"utf8")>64*1024){throw new Error("--candidates JSON must be no more than 65536 UTF-8 bytes")}try{return JSON.parse(text)}catch{throw new Error("--candidates must be valid JSON")}}function printFindings(findings){if(findings.length===0){console.log("No diagram lint findings.");return}console.warn(`${findings.length} diagram lint finding${findings.length===1?"":"s"}:`);for(const finding of findings){console.warn(` [${finding.code}] ${finding.message}`)}}var starter={$schema:"https://raw.githubusercontent.com/hraness/slopcamera/main/schema/diagram.schema.json",version:1,name:"example-flow",canvas:{width:960,height:540,padding:64},layout:{type:"stack",direction:"horizontal",gap:160,align:"center"},shapes:[{id:"source",type:"rect",width:240,height:160,label:"Source",icon:"document",tone:"blue"},{id:"result",type:"rect",width:240,height:160,label:"Result",icon:"check",tone:"green"}],edges:[{id:"source-result",from:"source",to:"result"}]};function hostAdmissionOptions(dependencies){return dependencies.hostResourceCoordinator===undefined?{}:{hostResourceCoordinator:dependencies.hostResourceCoordinator}}function canonicalArguments(args){const[surface,subcommand,...rest]=args;if(surface==="diagram"){if(subcommand==="init"||subcommand==="check"||subcommand==="render"){return[subcommand,...rest]}throw new Error("Use slopcamera diagram init, check, or render")}if(surface==="image"){if(subcommand==="vectorize"||subcommand==="generate"||subcommand==="icon"||subcommand==="gallery"){return[subcommand,...rest]}throw new Error("Use slopcamera image vectorize, generate, icon, or gallery")}if(surface==="init"||surface==="check"||surface==="render"||surface==="vectorize"||surface==="generate"){throw new Error(`The flat \`${surface}\` command moved to a namespaced Slopcamera surface.
229
70
 
230
- ${help}`);
231
- }
232
- return args;
233
- }
234
- async function main(args, dependencies = {}) {
235
- const [command, ...rest] = canonicalArguments(args);
236
- if (command === undefined || command === "help" || command === "--help" || command === "-h") {
237
- console.log(help);
238
- return;
239
- }
240
- if (command === "version" || command === "--version" || command === "-v") {
241
- console.log(slopcameraCliVersion);
242
- return;
243
- }
244
- if (command === "init") {
245
- const parsed = parseArguments(rest, new Set);
246
- const filePath = resolve(parsed.positionals[0] ?? "diagram.diagram.json");
247
- if (await pathExists(filePath))
248
- throw new Error(`Refusing to overwrite existing file: ${filePath}`);
249
- await writeFile(filePath, `${JSON.stringify(starter, null, 2)}
250
- `);
251
- console.log(`Created ${filePath}`);
252
- return;
253
- }
254
- if (command === "check") {
255
- const parsed = parseArguments(rest, new Set(["config"]));
256
- const result = await withSlopcameraOperationHostAdmission("slopcamera.diagram.check", async () => await checkDiagramFile({
257
- filePath: requiredPositional(parsed, 0, "diagram file"),
258
- ...parsed.options.config === undefined ? {} : { configPath: parsed.options.config }
259
- }), hostAdmissionOptions(dependencies));
260
- console.log(`Valid diagram${result.configPath === null ? "" : ` with ${result.configPath}`}.`);
261
- printFindings(result.findings);
262
- if (parsed.flags.has("strict") && result.findings.length > 0)
263
- process.exitCode = 2;
264
- return;
265
- }
266
- if (command === "render") {
267
- const parsed = parseArguments(rest, new Set(["out-dir", "config", "scale"]));
268
- const scale = parsed.options.scale === undefined ? undefined : Number.parseFloat(parsed.options.scale);
269
- const result = await withSlopcameraOperationHostAdmission("slopcamera.diagram.render", async () => await renderDiagramFile({
270
- filePath: requiredPositional(parsed, 0, "diagram file"),
271
- ...parsed.options["out-dir"] === undefined ? {} : { outDirectory: parsed.options["out-dir"] },
272
- ...parsed.options.config === undefined ? {} : { configPath: parsed.options.config },
273
- ...scale === undefined ? {} : { scale }
274
- }), hostAdmissionOptions(dependencies));
275
- console.log(artifactSummary(result.artifacts));
276
- printFindings(result.findings);
277
- return;
278
- }
279
- if (command === "vectorize") {
280
- const parsed = parseArguments(rest, new Set(["output", "duotone", "alpha-cutoff", "timeout-ms"]));
281
- const unknownFlags = [...parsed.flags].filter((flag) => flag !== "json");
282
- if (unknownFlags.length > 0) {
283
- throw new Error(`Unknown vectorize option: --${unknownFlags[0]}`);
284
- }
285
- if (parsed.positionals.length > 1) {
286
- throw new Error("slopcamera image vectorize accepts exactly one raster input");
287
- }
288
- const output = requiredOption(parsed, "output");
289
- if (!output.toLowerCase().endsWith(".svg")) {
290
- throw new Error("--output must end in .svg");
291
- }
292
- const alphaCutoff = parsePositiveInteger(parsed.options["alpha-cutoff"], "alpha-cutoff");
293
- const timeoutMs = parsePositiveInteger(parsed.options["timeout-ms"], "timeout-ms");
294
- const duotone = parseDuotone(parsed.options.duotone);
295
- const result = await withSlopcameraOperationHostAdmission("slopcamera.image.vectorize", async (lease) => await (dependencies.vectorize ?? vectorizeImage)(requiredPositional(parsed, 0, "raster image"), {
296
- ...alphaCutoff === undefined ? {} : { alphaCutoff },
297
- ...duotone === undefined ? {} : { duotone },
298
- ...timeoutMs === undefined ? {} : { limits: { maxDurationMs: timeoutMs } },
299
- inheritedFileDescriptors: [lease.inheritedFileDescriptor],
300
- outputPath: output
301
- }), hostAdmissionOptions(dependencies));
302
- if (parsed.flags.has("json")) {
303
- (dependencies.log ?? console.log)(JSON.stringify({ ...result.receipt, outputPath: result.outputPath }, null, 2));
304
- } else {
305
- (dependencies.log ?? console.log)(`Vectorized ${result.receipt.width}\xD7${result.receipt.height} with ` + `${result.receipt.profile}/${result.receipt.representation}: ${result.outputPath}`);
306
- }
307
- return;
308
- }
309
- if (command === "generate") {
310
- const parsed = parseArguments(rest, new Set(["model", "output"]));
311
- const unknownFlags = [...parsed.flags].filter((flag) => flag !== "json");
312
- if (unknownFlags.length > 0) {
313
- throw new Error(`Unknown generate option: --${unknownFlags[0]}`);
314
- }
315
- if (parsed.positionals.length !== 1) {
316
- throw new Error("slopcamera image generate accepts exactly one prompt");
317
- }
318
- const model = parsed.options.model ?? slopcameraImageModels[1];
319
- if (model.length > 256 || !/^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._:-]*$/iu.test(model)) {
320
- throw new Error("--model must be a bounded Vercel AI Gateway provider/model id");
321
- }
322
- const result = await withSlopcameraOperationHostAdmission("slopcamera.image.generate", async () => await (dependencies.generate ?? generateSlopcameraImageFile)({
323
- model,
324
- prompt: requiredPositional(parsed, 0, "prompt"),
325
- outputPath: requiredOption(parsed, "output")
326
- }), hostAdmissionOptions(dependencies));
327
- if (parsed.flags.has("json")) {
328
- (dependencies.log ?? console.log)(JSON.stringify(result, null, 2));
329
- } else {
330
- (dependencies.log ?? console.log)(`Generated ${result.mediaType} with ${result.model}: ${result.outputPath} (${result.bytes} bytes, request ${result.requestId})`);
331
- }
332
- return;
333
- }
334
- if (command === "code") {
335
- const [subcommand, ...subcommandArgs] = rest;
336
- if (subcommand === "search") {
337
- const parsed = parseArguments(subcommandArgs, new Set(["limit"]));
338
- if (parsed.flags.size > 0 || parsed.positionals.length > 1) {
339
- throw new Error("Use slopcamera code search [query] [--limit <number>]");
340
- }
341
- const limit = parsePositiveInteger(parsed.options.limit, "limit") ?? slopcameraOperationCodes.length;
342
- const operations = searchSlopcameraOperations(parsed.positionals[0] ?? "", limit);
343
- console.log(JSON.stringify({ operations }, null, 2));
344
- return;
345
- }
346
- if (subcommand === "execute") {
347
- const parsed = parseArguments(subcommandArgs, new Set(["input"]));
348
- if (parsed.flags.size > 0 || parsed.positionals.length !== 1) {
349
- throw new Error("Use slopcamera code execute <operation> --input <JSON>");
350
- }
351
- const requestedOperation = parsed.positionals[0];
352
- const operation = isSlopcameraOperationCode(requestedOperation) ? requestedOperation : undefined;
353
- if (operation === undefined) {
354
- throw new Error(`Unknown Slopcamera operation code: ${requestedOperation}`);
355
- }
356
- const inputText = requiredOption(parsed, "input");
357
- if (Buffer.byteLength(inputText, "utf8") > 64 * 1024) {
358
- throw new Error("--input JSON must be no more than 65536 UTF-8 bytes");
359
- }
360
- let input;
361
- try {
362
- input = JSON.parse(inputText);
363
- } catch {
364
- throw new Error("--input must be valid JSON");
365
- }
366
- const result = await executeSlopcameraOperation(operation, input, {
367
- ...hostAdmissionOptions(dependencies)
368
- });
369
- (dependencies.log ?? console.log)(JSON.stringify({ operation, result }, null, 2));
370
- return;
371
- }
372
- throw new Error("Use slopcamera code search [query] or slopcamera code execute <operation> --input <JSON>");
373
- }
374
- if (command === "mcp") {
375
- const parsed = parseArguments(rest, new Set(["root"]));
376
- if (parsed.positionals.length > 0 || parsed.flags.size > 0) {
377
- throw new Error("slopcamera mcp accepts only --root <workspace>");
378
- }
379
- await runMcpServer({
380
- rootDirectory: requiredOption(parsed, "root"),
381
- serverVersion: slopcameraCliVersion
382
- });
383
- return;
384
- }
385
- if (command === "open") {
386
- const parsed = parseArguments(rest, new Set);
387
- await openInDesktop(requiredPositional(parsed, 0, "tldraw file"));
388
- console.log("Opened in tldraw Offline.");
389
- return;
390
- }
391
- if (command === "doctor") {
392
- const status = await desktopStatus();
393
- console.log(`slopcamera ${slopcameraCliVersion}`);
394
- console.log(`Bun ${process.versions.bun ?? "not detected"}`);
395
- console.log("Headless diagram SVG/PNG/tldraw renderer ready");
396
- console.log(process.platform === "win32" ? "Local raster-to-SVG vectorizer unavailable on Windows (fails closed with tool_platform)" : "Local raster-to-SVG vectorizer ready without authentication (VTracer downloads on first use)");
397
- console.log("Root-relative MCP check/render server ready (trusted local workspace)");
398
- const gateway = slopcameraGatewayCredentialStatus();
399
- console.log(gateway.available ? `Vercel AI Gateway ready via ${gateway.source}` : "Vercel AI Gateway requires AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN");
400
- console.log(status.installedPath === null ? "tldraw Offline not installed (optional)" : `tldraw Offline: ${status.installedPath}`);
401
- console.log(status.server === null ? "tldraw Offline agent server not running (optional)" : `tldraw Offline agent server: localhost:${status.server.port}`);
402
- return;
403
- }
404
- if (command === "desktop") {
405
- const [subcommand, ...subcommandArgs] = rest;
406
- if (subcommand === "status") {
407
- const status = await desktopStatus();
408
- console.log(JSON.stringify(status, null, 2));
409
- return;
410
- }
411
- if (subcommand === "url") {
412
- const release = await getLatestDesktopRelease();
413
- const asset = selectDesktopAsset(release);
414
- console.log(JSON.stringify({
415
- release: release.tag_name,
416
- releaseUrl: release.html_url,
417
- asset: asset.name,
418
- url: asset.browser_download_url,
419
- bytes: asset.size,
420
- sha256: asset.digest
421
- }, null, 2));
422
- return;
423
- }
424
- if (subcommand === "install") {
425
- const parsed = parseArguments(subcommandArgs, new Set);
426
- if (!parsed.flags.has("yes") && !await confirmInstall()) {
427
- console.log("Cancelled.");
428
- return;
429
- }
430
- const result = await installDesktop({ downloadOnly: parsed.flags.has("download-only") });
431
- console.log(`${parsed.flags.has("download-only") ? "Downloaded" : "Prepared"} tldraw Offline ${result.release}: ${result.filePath}`);
432
- return;
433
- }
434
- throw new Error("Use slopcamera canvas status, url, or install");
435
- }
436
- if (command === "skill") {
437
- const [subcommand, ...subcommandArgs] = rest;
438
- if (subcommand === "path") {
439
- const { bundledSkillPath } = await import("./skill-install-dh0ntqba.js");
440
- console.log(bundledSkillPath());
441
- return;
442
- }
443
- if (subcommand === "install") {
444
- const parsed = parseArguments(subcommandArgs, new Set(["target", "scope", "project"]));
445
- const target = parsed.options.target ?? "codex";
446
- const scope = parsed.options.scope ?? "user";
447
- if (!["codex", "claude", "agents"].includes(target)) {
448
- throw new Error("--target must be codex, claude, or agents");
449
- }
450
- if (!["user", "project"].includes(scope)) {
451
- throw new Error("--scope must be user or project");
452
- }
453
- const destination = await installSkill({
454
- target,
455
- scope,
456
- ...parsed.options.project === undefined ? {} : { projectDirectory: parsed.options.project },
457
- force: parsed.flags.has("force")
458
- });
459
- console.log(`Installed slopcamera skill at ${destination}`);
460
- return;
461
- }
462
- throw new Error("Use slopcamera skill path or install");
463
- }
464
- throw new Error(`Unknown command: ${command}
71
+ ${help}`)}return args}async function main(args,dependencies={}){if(args[0]==="support"){process.exitCode=await runProductSupportCommand(args.slice(1),dependencies.supportEnvironment===undefined?{}:{env:dependencies.supportEnvironment});return}const[command,...rest]=canonicalArguments(args);if(command===undefined||command==="help"||command==="--help"||command==="-h"){console.log(help);return}if(command==="version"||command==="--version"||command==="-v"){console.log(slopcameraCliVersion);return}if(command==="init"){const parsed=parseArguments(rest,new Set);const filePath=resolve(parsed.positionals[0]??"diagram.diagram.json");if(await pathExists(filePath))throw new Error(`Refusing to overwrite existing file: ${filePath}`);await writeFile(filePath,`${JSON.stringify(starter,null,2)}
72
+ `);console.log(`Created ${filePath}`);reportUsefulResult(dependencies.onUsefulResult);return}if(command==="check"){const parsed=parseArguments(rest,new Set(["config"]));const result=await withSlopcameraOperationHostAdmission("slopcamera.diagram.check",async()=>await checkDiagramFile({filePath:requiredPositional(parsed,0,"diagram file"),...parsed.options.config===undefined?{}:{configPath:parsed.options.config}}),hostAdmissionOptions(dependencies));console.log(`Valid diagram${result.configPath===null?"":` with ${result.configPath}`}.`);printFindings(result.findings);if(parsed.flags.has("strict")&&result.findings.length>0)process.exitCode=2;return}if(command==="render"){const parsed=parseArguments(rest,new Set(["out-dir","config","scale"]));const scale=parsed.options.scale===undefined?undefined:Number.parseFloat(parsed.options.scale);const result=await withSlopcameraOperationHostAdmission("slopcamera.diagram.render",async()=>await renderDiagramFile({filePath:requiredPositional(parsed,0,"diagram file"),...parsed.options["out-dir"]===undefined?{}:{outDirectory:parsed.options["out-dir"]},...parsed.options.config===undefined?{}:{configPath:parsed.options.config},...scale===undefined?{}:{scale}}),hostAdmissionOptions(dependencies));console.log(artifactSummary(result.artifacts));printFindings(result.findings);reportUsefulResult(dependencies.onUsefulResult);return}if(command==="vectorize"){const parsed=parseArguments(rest,new Set(["output","duotone","alpha-cutoff","timeout-ms"]));const unknownFlags=[...parsed.flags].filter((flag)=>flag!=="json");if(unknownFlags.length>0){throw new Error(`Unknown vectorize option: --${unknownFlags[0]}`)}if(parsed.positionals.length>1){throw new Error("slopcamera image vectorize accepts exactly one raster input")}const output=requiredOption(parsed,"output");if(!output.toLowerCase().endsWith(".svg")){throw new Error("--output must end in .svg")}const alphaCutoff=parsePositiveInteger(parsed.options["alpha-cutoff"],"alpha-cutoff");const timeoutMs=parsePositiveInteger(parsed.options["timeout-ms"],"timeout-ms");const duotone=parseDuotone(parsed.options.duotone);const result=await withSlopcameraOperationHostAdmission("slopcamera.image.vectorize",async(lease)=>await(dependencies.vectorize??vectorizeImage)(requiredPositional(parsed,0,"raster image"),{...alphaCutoff===undefined?{}:{alphaCutoff},...duotone===undefined?{}:{duotone},...timeoutMs===undefined?{}:{limits:{maxDurationMs:timeoutMs}},inheritedFileDescriptors:[lease.inheritedFileDescriptor],outputPath:output}),hostAdmissionOptions(dependencies));if(parsed.flags.has("json")){(dependencies.log??console.log)(JSON.stringify({...result.receipt,outputPath:result.outputPath},null,2))}else{(dependencies.log??console.log)(`Vectorized ${result.receipt.width}\xD7${result.receipt.height} with `+`${result.receipt.profile}/${result.receipt.representation}: ${result.outputPath}`)}reportUsefulResult(dependencies.onUsefulResult);return}if(command==="generate"){const parsed=parseArguments(rest,new Set(["model","output"]));const unknownFlags=[...parsed.flags].filter((flag)=>flag!=="json");if(unknownFlags.length>0){throw new Error(`Unknown generate option: --${unknownFlags[0]}`)}if(parsed.positionals.length!==1){throw new Error("slopcamera image generate accepts exactly one prompt")}const model=parsed.options.model??slopcameraImageModels[1];if(model.length>256||!/^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._:-]*$/iu.test(model)){throw new Error("--model must be a bounded Vercel AI Gateway provider/model id")}const result=await withSlopcameraOperationHostAdmission("slopcamera.image.generate",async()=>await(dependencies.generate??generateSlopcameraImageFile)({model,prompt:requiredPositional(parsed,0,"prompt"),outputPath:requiredOption(parsed,"output")}),hostAdmissionOptions(dependencies));if(parsed.flags.has("json")){(dependencies.log??console.log)(JSON.stringify(result,null,2))}else{(dependencies.log??console.log)(`Generated ${result.mediaType} with ${result.model}: ${result.outputPath} (${result.bytes} bytes, request ${result.requestId})`)}reportUsefulResult(dependencies.onUsefulResult);return}if(command==="icon"){const parsed=parseArguments(rest,new Set(["model","output","ink","purpose","rounds","critique-model"]));const unknownFlags=[...parsed.flags].filter((flag)=>flag!=="json"&&flag!=="keep-raster");if(unknownFlags.length>0){throw new Error(`Unknown icon option: --${unknownFlags[0]}`)}if(parsed.positionals.length!==1){throw new Error("slopcamera image icon accepts exactly one subject")}const output=requiredOption(parsed,"output");if(!output.toLowerCase().endsWith(".svg")){throw new Error("--output must end in .svg")}const model=parsed.options.model??slopcameraImageModels[1];if(model.length>256||!/^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._:-]*$/iu.test(model)){throw new Error("--model must be a bounded Vercel AI Gateway provider/model id")}const critiqueModel=parsed.options["critique-model"];if(critiqueModel!==undefined&&(critiqueModel.length>256||!/^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._:-]*$/iu.test(critiqueModel))){throw new Error("--critique-model must be a bounded Vercel AI Gateway provider/model id")}const ink=parsed.options.ink;if(ink!==undefined&&!/^#[a-f0-9]{3}(?:[a-f0-9]{3})?$/iu.test(ink)){throw new Error("--ink must be a #rgb or #rrggbb color")}const purpose=parsed.options.purpose;if(purpose!==undefined&&purpose!=="mark"&&purpose!=="illustration"){throw new Error("--purpose must be mark or illustration")}const rounds=parsePositiveInteger(parsed.options.rounds,"rounds");if(rounds!==undefined&&rounds>slopcameraIconMaximumRounds){throw new Error(`--rounds must be at most ${slopcameraIconMaximumRounds}`)}const result=await withSlopcameraOperationHostAdmission("slopcamera.image.icon",async(lease)=>await(dependencies.icon??generateSlopcameraIcon)({keepRaster:parsed.flags.has("keep-raster"),model,outputPath:output,subject:requiredPositional(parsed,0,"subject"),inheritedFileDescriptors:[lease.inheritedFileDescriptor],...critiqueModel===undefined?{}:{critiqueModel},...ink===undefined?{}:{ink},...purpose===undefined?{}:{purpose},...rounds===undefined?{}:{rounds}}),hostAdmissionOptions(dependencies));if(parsed.flags.has("json")){(dependencies.log??console.log)(JSON.stringify(result,null,2))}else{const scored=result.attempts.map((attempt)=>attempt.score===undefined?`#${attempt.round}`:`#${attempt.round}=${attempt.score}`).join(" ");(dependencies.log??console.log)(`Icon ${result.svgSha256.slice(0,12)} round ${result.selectedRound}/${result.attempts.length} (${scored}): ${result.outputPath}`)}return}if(command==="gallery"){const parsed=parseArguments(rest,new Set(["model","output-dir","kind","count","vary","candidates","cell","timeout-ms"]));const unknownFlags=[...parsed.flags].filter((flag)=>flag!=="json"&&flag!=="tile"&&flag!=="no-tile");if(unknownFlags.length>0){throw new Error(`Unknown gallery option: --${unknownFlags[0]}`)}if(parsed.flags.has("tile")&&parsed.flags.has("no-tile")){throw new Error("--tile and --no-tile are mutually exclusive")}if(parsed.positionals.length!==1){throw new Error("slopcamera image gallery accepts exactly one subject")}const model=parsed.options.model??slopcameraImageModels[1];if(model.length>256||!/^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._:-]*$/iu.test(model)){throw new Error("--model must be a bounded Vercel AI Gateway provider/model id")}const kind=parsed.options.kind;if(kind!==undefined&&!slopcameraGalleryKinds.includes(kind)){throw new Error(`--kind must be one of: ${slopcameraGalleryKinds.join(", ")}`)}const count=parsePositiveInteger(parsed.options.count,"count");if(count!==undefined&&count>slopcameraGalleryLimits.candidates){throw new Error(`--count must be at most ${slopcameraGalleryLimits.candidates}`)}const cell=parsePositiveInteger(parsed.options.cell,"cell");if(cell!==undefined&&(cell<slopcameraGalleryLimits.cellEdgeMin||cell>slopcameraGalleryLimits.cellEdgeMax)){throw new Error(`--cell must be between ${slopcameraGalleryLimits.cellEdgeMin} and ${slopcameraGalleryLimits.cellEdgeMax}`)}const timeoutMs=parsePositiveInteger(parsed.options["timeout-ms"],"timeout-ms");const vary=parseGalleryVary(parsed.options.vary);const candidates=parsed.options.candidates===undefined?undefined:await readGalleryCandidates(parsed.options.candidates);if(candidates!==undefined&&!Array.isArray(candidates)){throw new Error("--candidates JSON must be an array of {id, prompt|variant} entries")}const result=await withSlopcameraOperationHostAdmission("slopcamera.image.gallery",async()=>await(dependencies.gallery??generateSlopcameraImageGallery)({subject:requiredPositional(parsed,0,"subject"),outputDir:requiredOption(parsed,"output-dir"),model,...kind===undefined?{}:{kind},...count===undefined?{}:{count},...vary===undefined?{}:{vary},...candidates===undefined?{}:{candidates},...cell===undefined?{}:{cellEdge:cell},...timeoutMs===undefined?{}:{timeoutMs},...parsed.flags.has("tile")?{tiled:true}:parsed.flags.has("no-tile")?{tiled:false}:{}}),hostAdmissionOptions(dependencies));if(parsed.flags.has("json")){(dependencies.log??console.log)(JSON.stringify(result,null,2))}else{const rows=result.candidates.map((candidate)=>candidate.status==="generated"?`#${candidate.index} ${candidate.id} ${candidate.path}`:`#${candidate.index} ${candidate.id} failed`);(dependencies.log??console.log)(`Gallery ${result.counts.generated}/${result.counts.requested} generated (${result.model}): ${result.gallery.path}
73
+ `+`${rows.join(`
74
+ `)}
75
+ receipt ${result.receiptPath}`)}reportUsefulResult(dependencies.onUsefulResult);return}if(command==="code"){const[subcommand,...subcommandArgs]=rest;if(subcommand==="search"){const parsed=parseArguments(subcommandArgs,new Set(["limit"]));if(parsed.flags.size>0||parsed.positionals.length>1){throw new Error("Use slopcamera code search [query] [--limit <number>]")}const limit=parsePositiveInteger(parsed.options.limit,"limit")??slopcameraOperationCodes.length;const operations=searchSlopcameraOperations(parsed.positionals[0]??"",limit);console.log(JSON.stringify({operations},null,2));return}if(subcommand==="execute"){const parsed=parseArguments(subcommandArgs,new Set(["input"]));if(parsed.flags.size>0||parsed.positionals.length!==1){throw new Error("Use slopcamera code execute <operation> --input <JSON>")}const requestedOperation=parsed.positionals[0];const operation=isSlopcameraOperationCode(requestedOperation)?requestedOperation:undefined;if(operation===undefined){throw new Error(`Unknown Slopcamera operation code: ${requestedOperation}`)}const inputText=requiredOption(parsed,"input");if(Buffer.byteLength(inputText,"utf8")>64*1024){throw new Error("--input JSON must be no more than 65536 UTF-8 bytes")}let input;try{input=JSON.parse(inputText)}catch{throw new Error("--input must be valid JSON")}const result=await executeSlopcameraOperation(operation,input,{...hostAdmissionOptions(dependencies)});(dependencies.log??console.log)(JSON.stringify({operation,result},null,2));if(operation==="slopcamera.diagram.render"||operation==="slopcamera.image.vectorize"||operation==="slopcamera.image.generate"){reportUsefulResult(dependencies.onUsefulResult)}return}throw new Error("Use slopcamera code search [query] or slopcamera code execute <operation> --input <JSON>")}if(command==="mcp"){const parsed=parseArguments(rest,new Set(["root"]));if(parsed.positionals.length>0||parsed.flags.size>0){throw new Error("slopcamera mcp accepts only --root <workspace>")}await runMcpServer({rootDirectory:requiredOption(parsed,"root"),serverVersion:slopcameraCliVersion});return}if(command==="doctor"){console.log(`slopcamera ${slopcameraCliVersion}`);console.log(`Bun ${process.versions.bun??"not detected"}`);console.log("Headless diagram SVG/PNG/tldraw renderer ready");console.log(process.platform==="win32"?"Local raster-to-SVG vectorizer unavailable on Windows (fails closed with tool_platform)":"Local raster-to-SVG vectorizer ready without authentication (VTracer downloads on first use)");console.log("Root-relative MCP check/render/scene server ready (trusted local workspace)");const gateway=slopcameraGatewayCredentialStatus();console.log(gateway.available?`Vercel AI Gateway ready via ${gateway.source}`:"Vercel AI Gateway requires AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN");return}if(command==="skill"){const[subcommand,...subcommandArgs]=rest;if(subcommand==="path"){const{bundledSkillPath}=await import("./skill-install-gx7afxsd.js");console.log(bundledSkillPath());return}if(subcommand==="install"){const parsed=parseArguments(subcommandArgs,new Set(["target","scope","project"]));const target=parsed.options.target??"codex";const scope=parsed.options.scope??"user";if(!["codex","claude","agents"].includes(target)){throw new Error("--target must be codex, claude, or agents")}if(!["user","project"].includes(scope)){throw new Error("--scope must be user or project")}const destination=await installSkill({target,scope,...parsed.options.project===undefined?{}:{projectDirectory:parsed.options.project},force:parsed.flags.has("force")});console.log(`Installed slopcamera skill at ${destination}`);return}throw new Error("Use slopcamera skill path or install")}throw new Error(`Unknown command: ${command}
465
76
 
466
- ${help}`);
467
- }
468
- if (import.meta.main) {
469
- try {
470
- await main(process.argv.slice(2));
471
- } catch (error) {
472
- console.error(error instanceof Error ? error.message : String(error));
473
- process.exitCode = 1;
474
- }
475
- }
476
- export {
477
- slopcameraCliVersion,
478
- main
479
- };
77
+ ${help}`)}if(import.meta.main){try{const env=standaloneSupportEnvironment();let usefulResult=false;await main(process.argv.slice(2),{supportEnvironment:env,onUsefulResult:()=>{usefulResult=true}});if(usefulResult&&(process.exitCode??0)===0)await showProductSupportInvitation({env})}catch(error){console.error(error instanceof Error?error.message:String(error));process.exitCode=1}}export{slopcameraCliVersion,main};