@hraness/slopcamera 3.2.8 → 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/README.md +29 -9
- package/apps/desktop/application/context.ts +6 -0
- package/apps/desktop/application/default-registry.ts +26 -0
- package/apps/desktop/application/operation.ts +14 -1
- package/apps/desktop/application/operations/index.ts +4 -0
- 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 +514 -11
- 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 +29 -1
- package/apps/desktop/cli/commands.ts +1188 -4
- package/apps/desktop/cli/gateway-review-provider.ts +334 -0
- package/apps/desktop/cli/help.ts +172 -6
- package/apps/desktop/cli/main.ts +3 -3
- package/apps/desktop/cli/portable-surface.ts +6 -1
- 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/semantic-builder.ts +24 -0
- 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/storage.ts +75 -0
- package/apps/desktop/dist/cli/main.js +725 -273
- 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 +25 -429
- 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 -226
- 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/package.json +7 -5
- package/skills/slopcamera/SKILL.md +6 -1
- 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 +1 -1
- package/skills/slopcamera/references/parametric-design.md +14 -0
- package/skills/slopcamera/references/scene-building.md +32 -0
- package/skills/slopcamera/references/social-collage-banners.md +204 -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 +222 -4
- 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 -0
- 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/version.ts +1 -1
- package/dist/index-42zsesc1.js +0 -2257
- package/dist/index-7131pg1c.js +0 -1820
- package/dist/index-7308egqr.js +0 -48
- package/dist/index-8txs6fkn.js +0 -712
- package/dist/index-fava6pge.js +0 -192
- package/dist/index-h1k0fnjq.js +0 -2363
- package/dist/index-r7gdhmsp.js +0 -424
- package/dist/index-sh6xbav6.js +0 -1219
- package/dist/index-z1w83f81.js +0 -4
- package/dist/index-zfnddgay.js +0 -2029
- package/dist/skill-install-dh0ntqba.js +0 -10
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
spatialSceneSha256,
|
|
5
|
+
SpatialSceneV1Schema,
|
|
6
|
+
} from "../../../src/spatial-scene";
|
|
7
|
+
import { SpatialDirectionSchema } from "../../../src/spatial-scene/direction";
|
|
8
|
+
import { SpatialTemporalAuditOptionsSchema } from "../../../src/spatial-scene/temporal-audit";
|
|
9
|
+
import { SpatialRecipePackSchema } from "../../../src/spatial-scene/recipe-pack";
|
|
10
|
+
import { SpatialRenderRequestSchema, type SpatialRenderRequest } from "../application/spatial-render";
|
|
11
|
+
import type { SpatialEffectsPlanInput } from "../application/operations/spatial-direction";
|
|
12
|
+
import { MediaArtifactRequestSchema } from "../application/operations/media/shared";
|
|
13
|
+
import type { OperationInputValue } from "../code/contracts";
|
|
14
|
+
import { defineWorkflow } from "../code/public";
|
|
15
|
+
|
|
16
|
+
export const CinematicWorldInputSchema = z.strictObject({
|
|
17
|
+
/** The declarative pack an agent authors: direction, axes, previews, audit. */
|
|
18
|
+
pack: SpatialRecipePackSchema,
|
|
19
|
+
/** The admitted scene the pack binds by `sceneSha256`. */
|
|
20
|
+
scene: SpatialSceneV1Schema,
|
|
21
|
+
/** Repository artifact for the same scene, used only by render nodes. */
|
|
22
|
+
source: MediaArtifactRequestSchema,
|
|
23
|
+
}).superRefine((input, context) => {
|
|
24
|
+
if (input.pack.sceneSha256 !== spatialSceneSha256(input.scene)) {
|
|
25
|
+
context.addIssue({
|
|
26
|
+
code: "custom",
|
|
27
|
+
message: "Recipe pack sceneSha256 does not match the admitted scene.",
|
|
28
|
+
path: ["pack", "sceneSha256"],
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
input.pack.previews.forEach((preview, index) => {
|
|
32
|
+
const parsed = SpatialRenderRequestSchema.safeParse(preview.request);
|
|
33
|
+
if (!parsed.success) {
|
|
34
|
+
context.addIssue({
|
|
35
|
+
code: "custom",
|
|
36
|
+
message: `Preview ${preview.name} request is not a valid spatial render request.`,
|
|
37
|
+
path: ["pack", "previews", index, "request"],
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The complete agent-directed planning and review loop for one admitted scene:
|
|
45
|
+
* inspect the world, check and compile the authored direction, plan a bounded
|
|
46
|
+
* gallery per requested axis, optionally bind declared effects into each
|
|
47
|
+
* preview render, and audit sampled temporal evidence. Every proposal stays
|
|
48
|
+
* `verified: false`; selection and promotion are separate explicit operations
|
|
49
|
+
* outside this workflow.
|
|
50
|
+
*/
|
|
51
|
+
export const cinematicWorld = defineWorkflow({
|
|
52
|
+
id: "cinematic-world",
|
|
53
|
+
inputSchema: CinematicWorldInputSchema,
|
|
54
|
+
inputSchemaId: "slopcamera.workflow.cinematic-world.input/v1",
|
|
55
|
+
version: 1,
|
|
56
|
+
build(workflow, input) {
|
|
57
|
+
const sceneSha256 = input.pack.sceneSha256;
|
|
58
|
+
/** Parsed documents enter operation input positions unchanged; refs may substitute any position. */
|
|
59
|
+
const scene = input.scene as unknown as OperationInputValue<z.infer<typeof SpatialSceneV1Schema>>;
|
|
60
|
+
const direction = input.pack.direction as unknown as OperationInputValue<z.infer<typeof SpatialDirectionSchema>>;
|
|
61
|
+
const planning = workflow.namespace("planning");
|
|
62
|
+
const inspect = planning.scene.inspect("inspect", { scene });
|
|
63
|
+
const directionCheck = planning.scene.directionCheck("direction-check", {
|
|
64
|
+
direction,
|
|
65
|
+
scene,
|
|
66
|
+
});
|
|
67
|
+
const compilation = planning.scene.directionCompile("direction-compile", {
|
|
68
|
+
direction,
|
|
69
|
+
scene,
|
|
70
|
+
...(input.pack.cameraId === undefined ? {} : { cameraId: input.pack.cameraId }),
|
|
71
|
+
});
|
|
72
|
+
const galleries = workflow.namespace("galleries");
|
|
73
|
+
const gallery = Object.fromEntries(input.pack.axes.map(axis => [axis, galleries.scene.directionGallery(axis, {
|
|
74
|
+
axis,
|
|
75
|
+
direction,
|
|
76
|
+
scene,
|
|
77
|
+
...(input.pack.cameraId === undefined ? {} : { cameraId: input.pack.cameraId }),
|
|
78
|
+
})]));
|
|
79
|
+
const audit = input.pack.temporalAudit === undefined || input.pack.cameraId === undefined
|
|
80
|
+
? undefined
|
|
81
|
+
: planning.scene.temporalAudit("temporal-audit", {
|
|
82
|
+
cameraId: input.pack.cameraId,
|
|
83
|
+
options: {
|
|
84
|
+
contacts: input.pack.temporalAudit.contacts,
|
|
85
|
+
cutBeforeUs: input.pack.temporalAudit.cutBeforeUs,
|
|
86
|
+
} as unknown as OperationInputValue<z.infer<typeof SpatialTemporalAuditOptionsSchema>>,
|
|
87
|
+
scene,
|
|
88
|
+
...(input.pack.temporalAudit.timesUs === undefined
|
|
89
|
+
? {} : { timesUs: input.pack.temporalAudit.timesUs }),
|
|
90
|
+
});
|
|
91
|
+
const previews = workflow.namespace("previews");
|
|
92
|
+
const renders = input.pack.previews.map(preview => {
|
|
93
|
+
const request = SpatialRenderRequestSchema.parse(preview.request) as unknown as OperationInputValue<SpatialRenderRequest>;
|
|
94
|
+
const branch = previews.namespace(preview.name);
|
|
95
|
+
const effects = input.pack.effects === undefined ? undefined : branch.scene.effectsPlan("effects", {
|
|
96
|
+
particleSystems: input.pack.effects.particleSystems,
|
|
97
|
+
renderPlan: input.pack.effects.renderPlan,
|
|
98
|
+
scene,
|
|
99
|
+
simulationBakes: input.pack.effects.simulationBakes,
|
|
100
|
+
} as unknown as OperationInputValue<SpatialEffectsPlanInput>);
|
|
101
|
+
const render = branch.scene.render("render", {
|
|
102
|
+
request: {
|
|
103
|
+
...request,
|
|
104
|
+
...(effects === undefined ? {} : { effects }),
|
|
105
|
+
},
|
|
106
|
+
sceneSha256,
|
|
107
|
+
source: input.source,
|
|
108
|
+
});
|
|
109
|
+
return {
|
|
110
|
+
effects,
|
|
111
|
+
name: preview.name,
|
|
112
|
+
render,
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
compilation,
|
|
117
|
+
directionCheck,
|
|
118
|
+
galleries: gallery,
|
|
119
|
+
inspect,
|
|
120
|
+
previews: Object.fromEntries(renders.map(candidate => [candidate.name, {
|
|
121
|
+
...(candidate.effects === undefined ? {} : { effects: candidate.effects }),
|
|
122
|
+
render: candidate.render,
|
|
123
|
+
}])),
|
|
124
|
+
...(audit === undefined ? {} : { temporalAudit: audit }),
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
});
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from "../code/define-workflow";
|
|
9
9
|
import type { WorkflowOutputValue } from "../code/contracts";
|
|
10
10
|
import { chapteredDemo } from "./chaptered-demo";
|
|
11
|
+
import { cinematicWorld } from "./cinematic-world";
|
|
11
12
|
import { directedScene } from "./scene-direction";
|
|
12
13
|
import { creativeIteration } from "./creative-iteration";
|
|
13
14
|
import { creativeSelection } from "./creative-selection";
|
|
@@ -52,6 +53,10 @@ export const BUILT_IN_WORKFLOWS: readonly BuiltInWorkflow[] = Object.freeze([
|
|
|
52
53
|
description: "Render a prepared immutable spatial scene composition with exact frame timing and retained project audio.",
|
|
53
54
|
title: "Directed scene",
|
|
54
55
|
}),
|
|
56
|
+
catalogEntry(cinematicWorld, {
|
|
57
|
+
description: "Compile authored direction for one admitted scene into proposals, bounded galleries, effect-bound previews, and a temporal audit — selection and promotion stay explicit.",
|
|
58
|
+
title: "Cinematic world",
|
|
59
|
+
}),
|
|
55
60
|
catalogEntry(chapteredDemo, {
|
|
56
61
|
description: "Freeze the current chapter, overlay, and media composition and render an exact video.",
|
|
57
62
|
title: "Chaptered demo",
|
|
@@ -84,6 +89,7 @@ export function builtInWorkflow(id: string): BuiltInWorkflow | undefined {
|
|
|
84
89
|
|
|
85
90
|
export {
|
|
86
91
|
chapteredDemo,
|
|
92
|
+
cinematicWorld,
|
|
87
93
|
directedScene,
|
|
88
94
|
creativeIteration,
|
|
89
95
|
creativeSelection,
|
package/dist/cli.js
CHANGED
|
@@ -1,95 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
-
import {
|
|
4
|
-
SLOPCAMERA_VERSION,
|
|
5
|
-
artifactSummary,
|
|
6
|
-
checkDiagramFile,
|
|
7
|
-
renderDiagramFile,
|
|
8
|
-
runMcpServer
|
|
9
|
-
} from "./index-7131pg1c.js";
|
|
10
|
-
import {
|
|
11
|
-
installSkill,
|
|
12
|
-
pathExists
|
|
13
|
-
} from "./index-7308egqr.js";
|
|
14
|
-
import"./index-8txs6fkn.js";
|
|
15
|
-
import"./index-fava6pge.js";
|
|
16
|
-
import {
|
|
17
|
-
executeSlopcameraOperation,
|
|
18
|
-
isSlopcameraOperationCode,
|
|
19
|
-
searchSlopcameraOperations,
|
|
20
|
-
slopcameraOperationCodes,
|
|
21
|
-
withSlopcameraOperationHostAdmission
|
|
22
|
-
} from "./index-h1k0fnjq.js";
|
|
23
|
-
import {
|
|
24
|
-
vectorizeImage
|
|
25
|
-
} from "./index-zfnddgay.js";
|
|
26
|
-
import {
|
|
27
|
-
generateSlopcameraImageFile,
|
|
28
|
-
slopcameraGatewayCredentialStatus,
|
|
29
|
-
slopcameraImageModels
|
|
30
|
-
} from "./index-r7gdhmsp.js";
|
|
31
|
-
import"./index-sh6xbav6.js";
|
|
32
|
-
import {
|
|
33
|
-
__require
|
|
34
|
-
} from "./index-z1w83f81.js";
|
|
35
|
-
|
|
36
|
-
// src/cli.ts
|
|
37
|
-
import { writeFile } from "fs/promises";
|
|
38
|
-
import { resolve } from "path";
|
|
39
|
-
|
|
40
|
-
// src/support-completion.ts
|
|
41
|
-
function reportUsefulResult(observer) {
|
|
42
|
-
try {
|
|
43
|
-
if (observer !== undefined)
|
|
44
|
-
Promise.resolve(observer()).catch(() => {
|
|
45
|
-
return;
|
|
46
|
-
});
|
|
47
|
-
} catch {}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// src/support.ts
|
|
51
|
-
import {
|
|
52
|
-
maybeShowSupportInvitation,
|
|
53
|
-
runSupportCommand
|
|
54
|
-
} from "@hraness/support-foundation/node";
|
|
55
|
-
var supportProfile = {
|
|
56
|
-
id: "slopcamera",
|
|
57
|
-
name: "Slopcamera",
|
|
58
|
-
valueProposition: "Support ongoing development of local visual tools for agents.",
|
|
59
|
-
updates: false
|
|
60
|
-
};
|
|
61
|
-
function standaloneSupportEnvironment() {
|
|
62
|
-
const env = { ...process.env };
|
|
63
|
-
process.env.HRANESS_SUPPORT_AUDIENCE = "off";
|
|
64
|
-
process.env.HRANESS_SUPPORT_EMAIL = "off";
|
|
65
|
-
return env;
|
|
66
|
-
}
|
|
67
|
-
async function runProductSupportCommand(args, options = {}) {
|
|
68
|
-
const result = await runSupportCommand(supportProfile, args, {
|
|
69
|
-
command: ["slopcamera"],
|
|
70
|
-
...options,
|
|
71
|
-
gitEmail: false
|
|
72
|
-
});
|
|
73
|
-
if (result.stdout !== "")
|
|
74
|
-
process.stdout.write(result.stdout);
|
|
75
|
-
if (result.stderr !== "")
|
|
76
|
-
process.stderr.write(result.stderr);
|
|
77
|
-
return result.exitCode;
|
|
78
|
-
}
|
|
79
|
-
async function showProductSupportInvitation(options = {}) {
|
|
80
|
-
try {
|
|
81
|
-
await maybeShowSupportInvitation(supportProfile, {
|
|
82
|
-
usefulResult: true,
|
|
83
|
-
command: ["slopcamera"],
|
|
84
|
-
...options,
|
|
85
|
-
gitEmail: false
|
|
86
|
-
});
|
|
87
|
-
} catch {}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// src/cli.ts
|
|
91
|
-
var slopcameraCliVersion = SLOPCAMERA_VERSION;
|
|
92
|
-
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}
|
|
93
4
|
|
|
94
5
|
Turn source material into deterministic diagrams, images, and canvas assets.
|
|
95
6
|
|
|
@@ -99,6 +10,12 @@ Usage:
|
|
|
99
10
|
slopcamera diagram render <file> [--out-dir <directory>] [--config <file>] [--scale <number>]
|
|
100
11
|
slopcamera image vectorize <image> --output <file.svg> [--json] [--duotone <#rgb,#rgb>]
|
|
101
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]
|
|
102
19
|
slopcamera code search [query] [--limit <number>]
|
|
103
20
|
slopcamera code execute <operation> --input <JSON>
|
|
104
21
|
slopcamera mcp --root <workspace>
|
|
@@ -127,6 +44,17 @@ Set AI_GATEWAY_API_KEY, or run through \`vercel env run -- \u2026\` so
|
|
|
127
44
|
VERCEL_OIDC_TOKEN is available. Slopcamera never stores or prints the token.
|
|
128
45
|
PNG, JPEG, and WebP responses are signature-checked and published atomically.
|
|
129
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
|
+
|
|
130
58
|
Optional support: after useful work, agents can read slopcamera support protocol --json.
|
|
131
59
|
Discovery uses stderr without claiming an invitation; HRANESS_SUPPORT_AUDIENCE=off disables it.
|
|
132
60
|
No feature requires payment. Imported CLI/SDK calls and probes stay quiet.
|
|
@@ -138,344 +66,12 @@ MCP preserves root-relative check_diagram/render_diagram and adds closed
|
|
|
138
66
|
search_slopcamera/execute_slopcamera registry tools. It uses built-in assets, never
|
|
139
67
|
executes workspace config or caller code, and writes protocol messages only to
|
|
140
68
|
stdout.
|
|
141
|
-
`;
|
|
142
|
-
function parseArguments(args, valueOptions) {
|
|
143
|
-
const positionals = [];
|
|
144
|
-
const options = {};
|
|
145
|
-
const flags = new Set;
|
|
146
|
-
for (let index = 0;index < args.length; index += 1) {
|
|
147
|
-
const argument = args[index];
|
|
148
|
-
if (argument === undefined)
|
|
149
|
-
continue;
|
|
150
|
-
if (!argument.startsWith("--")) {
|
|
151
|
-
positionals.push(argument);
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
const name = argument.slice(2);
|
|
155
|
-
if (!valueOptions.has(name)) {
|
|
156
|
-
flags.add(name);
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
const value = args[index + 1];
|
|
160
|
-
if (value === undefined || value.startsWith("--")) {
|
|
161
|
-
throw new Error(`--${name} requires a value`);
|
|
162
|
-
}
|
|
163
|
-
options[name] = value;
|
|
164
|
-
index += 1;
|
|
165
|
-
}
|
|
166
|
-
return { positionals, options, flags };
|
|
167
|
-
}
|
|
168
|
-
function requiredPositional(parsed, index, label) {
|
|
169
|
-
const value = parsed.positionals[index];
|
|
170
|
-
if (value === undefined)
|
|
171
|
-
throw new Error(`Missing ${label}`);
|
|
172
|
-
return value;
|
|
173
|
-
}
|
|
174
|
-
function requiredOption(parsed, name) {
|
|
175
|
-
const value = parsed.options[name];
|
|
176
|
-
if (value === undefined)
|
|
177
|
-
throw new Error(`--${name} is required`);
|
|
178
|
-
return value;
|
|
179
|
-
}
|
|
180
|
-
function parsePositiveInteger(value, name) {
|
|
181
|
-
if (value === undefined)
|
|
182
|
-
return;
|
|
183
|
-
const parsed = Number(value);
|
|
184
|
-
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
185
|
-
throw new Error(`--${name} must be a positive integer`);
|
|
186
|
-
}
|
|
187
|
-
return parsed;
|
|
188
|
-
}
|
|
189
|
-
function parseDuotone(value) {
|
|
190
|
-
if (value === undefined)
|
|
191
|
-
return;
|
|
192
|
-
const colors = value.split(",").map((color) => color.trim());
|
|
193
|
-
if (colors.length !== 2 || colors.some((color) => !/^#[a-f0-9]{3}(?:[a-f0-9]{3})?$/iu.test(color))) {
|
|
194
|
-
throw new Error("--duotone must contain two #rgb or #rrggbb colors separated by a comma");
|
|
195
|
-
}
|
|
196
|
-
return [colors[0], colors[1]];
|
|
197
|
-
}
|
|
198
|
-
function printFindings(findings) {
|
|
199
|
-
if (findings.length === 0) {
|
|
200
|
-
console.log("No diagram lint findings.");
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
console.warn(`${findings.length} diagram lint finding${findings.length === 1 ? "" : "s"}:`);
|
|
204
|
-
for (const finding of findings) {
|
|
205
|
-
console.warn(` [${finding.code}] ${finding.message}`);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
var starter = {
|
|
209
|
-
$schema: "https://raw.githubusercontent.com/hraness/slopcamera/main/schema/diagram.schema.json",
|
|
210
|
-
version: 1,
|
|
211
|
-
name: "example-flow",
|
|
212
|
-
canvas: { width: 960, height: 540, padding: 64 },
|
|
213
|
-
layout: { type: "stack", direction: "horizontal", gap: 160, align: "center" },
|
|
214
|
-
shapes: [
|
|
215
|
-
{
|
|
216
|
-
id: "source",
|
|
217
|
-
type: "rect",
|
|
218
|
-
width: 240,
|
|
219
|
-
height: 160,
|
|
220
|
-
label: "Source",
|
|
221
|
-
icon: "document",
|
|
222
|
-
tone: "blue"
|
|
223
|
-
},
|
|
224
|
-
{
|
|
225
|
-
id: "result",
|
|
226
|
-
type: "rect",
|
|
227
|
-
width: 240,
|
|
228
|
-
height: 160,
|
|
229
|
-
label: "Result",
|
|
230
|
-
icon: "check",
|
|
231
|
-
tone: "green"
|
|
232
|
-
}
|
|
233
|
-
],
|
|
234
|
-
edges: [{ id: "source-result", from: "source", to: "result" }]
|
|
235
|
-
};
|
|
236
|
-
function hostAdmissionOptions(dependencies) {
|
|
237
|
-
return dependencies.hostResourceCoordinator === undefined ? {} : { hostResourceCoordinator: dependencies.hostResourceCoordinator };
|
|
238
|
-
}
|
|
239
|
-
function canonicalArguments(args) {
|
|
240
|
-
const [surface, subcommand, ...rest] = args;
|
|
241
|
-
if (surface === "diagram") {
|
|
242
|
-
if (subcommand === "init" || subcommand === "check" || subcommand === "render") {
|
|
243
|
-
return [subcommand, ...rest];
|
|
244
|
-
}
|
|
245
|
-
throw new Error("Use slopcamera diagram init, check, or render");
|
|
246
|
-
}
|
|
247
|
-
if (surface === "image") {
|
|
248
|
-
if (subcommand === "vectorize" || subcommand === "generate") {
|
|
249
|
-
return [subcommand, ...rest];
|
|
250
|
-
}
|
|
251
|
-
throw new Error("Use slopcamera image vectorize or generate");
|
|
252
|
-
}
|
|
253
|
-
if (surface === "init" || surface === "check" || surface === "render" || surface === "vectorize" || surface === "generate") {
|
|
254
|
-
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.
|
|
255
70
|
|
|
256
|
-
${help}`);
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
if (args[0] === "support") {
|
|
262
|
-
process.exitCode = await runProductSupportCommand(args.slice(1), dependencies.supportEnvironment === undefined ? {} : { env: dependencies.supportEnvironment });
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
const [command, ...rest] = canonicalArguments(args);
|
|
266
|
-
if (command === undefined || command === "help" || command === "--help" || command === "-h") {
|
|
267
|
-
console.log(help);
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
if (command === "version" || command === "--version" || command === "-v") {
|
|
271
|
-
console.log(slopcameraCliVersion);
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
if (command === "init") {
|
|
275
|
-
const parsed = parseArguments(rest, new Set);
|
|
276
|
-
const filePath = resolve(parsed.positionals[0] ?? "diagram.diagram.json");
|
|
277
|
-
if (await pathExists(filePath))
|
|
278
|
-
throw new Error(`Refusing to overwrite existing file: ${filePath}`);
|
|
279
|
-
await writeFile(filePath, `${JSON.stringify(starter, null, 2)}
|
|
280
|
-
`);
|
|
281
|
-
console.log(`Created ${filePath}`);
|
|
282
|
-
reportUsefulResult(dependencies.onUsefulResult);
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
if (command === "check") {
|
|
286
|
-
const parsed = parseArguments(rest, new Set(["config"]));
|
|
287
|
-
const result = await withSlopcameraOperationHostAdmission("slopcamera.diagram.check", async () => await checkDiagramFile({
|
|
288
|
-
filePath: requiredPositional(parsed, 0, "diagram file"),
|
|
289
|
-
...parsed.options.config === undefined ? {} : { configPath: parsed.options.config }
|
|
290
|
-
}), hostAdmissionOptions(dependencies));
|
|
291
|
-
console.log(`Valid diagram${result.configPath === null ? "" : ` with ${result.configPath}`}.`);
|
|
292
|
-
printFindings(result.findings);
|
|
293
|
-
if (parsed.flags.has("strict") && result.findings.length > 0)
|
|
294
|
-
process.exitCode = 2;
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
if (command === "render") {
|
|
298
|
-
const parsed = parseArguments(rest, new Set(["out-dir", "config", "scale"]));
|
|
299
|
-
const scale = parsed.options.scale === undefined ? undefined : Number.parseFloat(parsed.options.scale);
|
|
300
|
-
const result = await withSlopcameraOperationHostAdmission("slopcamera.diagram.render", async () => await renderDiagramFile({
|
|
301
|
-
filePath: requiredPositional(parsed, 0, "diagram file"),
|
|
302
|
-
...parsed.options["out-dir"] === undefined ? {} : { outDirectory: parsed.options["out-dir"] },
|
|
303
|
-
...parsed.options.config === undefined ? {} : { configPath: parsed.options.config },
|
|
304
|
-
...scale === undefined ? {} : { scale }
|
|
305
|
-
}), hostAdmissionOptions(dependencies));
|
|
306
|
-
console.log(artifactSummary(result.artifacts));
|
|
307
|
-
printFindings(result.findings);
|
|
308
|
-
reportUsefulResult(dependencies.onUsefulResult);
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
if (command === "vectorize") {
|
|
312
|
-
const parsed = parseArguments(rest, new Set(["output", "duotone", "alpha-cutoff", "timeout-ms"]));
|
|
313
|
-
const unknownFlags = [...parsed.flags].filter((flag) => flag !== "json");
|
|
314
|
-
if (unknownFlags.length > 0) {
|
|
315
|
-
throw new Error(`Unknown vectorize option: --${unknownFlags[0]}`);
|
|
316
|
-
}
|
|
317
|
-
if (parsed.positionals.length > 1) {
|
|
318
|
-
throw new Error("slopcamera image vectorize accepts exactly one raster input");
|
|
319
|
-
}
|
|
320
|
-
const output = requiredOption(parsed, "output");
|
|
321
|
-
if (!output.toLowerCase().endsWith(".svg")) {
|
|
322
|
-
throw new Error("--output must end in .svg");
|
|
323
|
-
}
|
|
324
|
-
const alphaCutoff = parsePositiveInteger(parsed.options["alpha-cutoff"], "alpha-cutoff");
|
|
325
|
-
const timeoutMs = parsePositiveInteger(parsed.options["timeout-ms"], "timeout-ms");
|
|
326
|
-
const duotone = parseDuotone(parsed.options.duotone);
|
|
327
|
-
const result = await withSlopcameraOperationHostAdmission("slopcamera.image.vectorize", async (lease) => await (dependencies.vectorize ?? vectorizeImage)(requiredPositional(parsed, 0, "raster image"), {
|
|
328
|
-
...alphaCutoff === undefined ? {} : { alphaCutoff },
|
|
329
|
-
...duotone === undefined ? {} : { duotone },
|
|
330
|
-
...timeoutMs === undefined ? {} : { limits: { maxDurationMs: timeoutMs } },
|
|
331
|
-
inheritedFileDescriptors: [lease.inheritedFileDescriptor],
|
|
332
|
-
outputPath: output
|
|
333
|
-
}), hostAdmissionOptions(dependencies));
|
|
334
|
-
if (parsed.flags.has("json")) {
|
|
335
|
-
(dependencies.log ?? console.log)(JSON.stringify({ ...result.receipt, outputPath: result.outputPath }, null, 2));
|
|
336
|
-
} else {
|
|
337
|
-
(dependencies.log ?? console.log)(`Vectorized ${result.receipt.width}\xD7${result.receipt.height} with ` + `${result.receipt.profile}/${result.receipt.representation}: ${result.outputPath}`);
|
|
338
|
-
}
|
|
339
|
-
reportUsefulResult(dependencies.onUsefulResult);
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
if (command === "generate") {
|
|
343
|
-
const parsed = parseArguments(rest, new Set(["model", "output"]));
|
|
344
|
-
const unknownFlags = [...parsed.flags].filter((flag) => flag !== "json");
|
|
345
|
-
if (unknownFlags.length > 0) {
|
|
346
|
-
throw new Error(`Unknown generate option: --${unknownFlags[0]}`);
|
|
347
|
-
}
|
|
348
|
-
if (parsed.positionals.length !== 1) {
|
|
349
|
-
throw new Error("slopcamera image generate accepts exactly one prompt");
|
|
350
|
-
}
|
|
351
|
-
const model = parsed.options.model ?? slopcameraImageModels[1];
|
|
352
|
-
if (model.length > 256 || !/^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._:-]*$/iu.test(model)) {
|
|
353
|
-
throw new Error("--model must be a bounded Vercel AI Gateway provider/model id");
|
|
354
|
-
}
|
|
355
|
-
const result = await withSlopcameraOperationHostAdmission("slopcamera.image.generate", async () => await (dependencies.generate ?? generateSlopcameraImageFile)({
|
|
356
|
-
model,
|
|
357
|
-
prompt: requiredPositional(parsed, 0, "prompt"),
|
|
358
|
-
outputPath: requiredOption(parsed, "output")
|
|
359
|
-
}), hostAdmissionOptions(dependencies));
|
|
360
|
-
if (parsed.flags.has("json")) {
|
|
361
|
-
(dependencies.log ?? console.log)(JSON.stringify(result, null, 2));
|
|
362
|
-
} else {
|
|
363
|
-
(dependencies.log ?? console.log)(`Generated ${result.mediaType} with ${result.model}: ${result.outputPath} (${result.bytes} bytes, request ${result.requestId})`);
|
|
364
|
-
}
|
|
365
|
-
reportUsefulResult(dependencies.onUsefulResult);
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
if (command === "code") {
|
|
369
|
-
const [subcommand, ...subcommandArgs] = rest;
|
|
370
|
-
if (subcommand === "search") {
|
|
371
|
-
const parsed = parseArguments(subcommandArgs, new Set(["limit"]));
|
|
372
|
-
if (parsed.flags.size > 0 || parsed.positionals.length > 1) {
|
|
373
|
-
throw new Error("Use slopcamera code search [query] [--limit <number>]");
|
|
374
|
-
}
|
|
375
|
-
const limit = parsePositiveInteger(parsed.options.limit, "limit") ?? slopcameraOperationCodes.length;
|
|
376
|
-
const operations = searchSlopcameraOperations(parsed.positionals[0] ?? "", limit);
|
|
377
|
-
console.log(JSON.stringify({ operations }, null, 2));
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
if (subcommand === "execute") {
|
|
381
|
-
const parsed = parseArguments(subcommandArgs, new Set(["input"]));
|
|
382
|
-
if (parsed.flags.size > 0 || parsed.positionals.length !== 1) {
|
|
383
|
-
throw new Error("Use slopcamera code execute <operation> --input <JSON>");
|
|
384
|
-
}
|
|
385
|
-
const requestedOperation = parsed.positionals[0];
|
|
386
|
-
const operation = isSlopcameraOperationCode(requestedOperation) ? requestedOperation : undefined;
|
|
387
|
-
if (operation === undefined) {
|
|
388
|
-
throw new Error(`Unknown Slopcamera operation code: ${requestedOperation}`);
|
|
389
|
-
}
|
|
390
|
-
const inputText = requiredOption(parsed, "input");
|
|
391
|
-
if (Buffer.byteLength(inputText, "utf8") > 64 * 1024) {
|
|
392
|
-
throw new Error("--input JSON must be no more than 65536 UTF-8 bytes");
|
|
393
|
-
}
|
|
394
|
-
let input;
|
|
395
|
-
try {
|
|
396
|
-
input = JSON.parse(inputText);
|
|
397
|
-
} catch {
|
|
398
|
-
throw new Error("--input must be valid JSON");
|
|
399
|
-
}
|
|
400
|
-
const result = await executeSlopcameraOperation(operation, input, {
|
|
401
|
-
...hostAdmissionOptions(dependencies)
|
|
402
|
-
});
|
|
403
|
-
(dependencies.log ?? console.log)(JSON.stringify({ operation, result }, null, 2));
|
|
404
|
-
if (operation === "slopcamera.diagram.render" || operation === "slopcamera.image.vectorize" || operation === "slopcamera.image.generate") {
|
|
405
|
-
reportUsefulResult(dependencies.onUsefulResult);
|
|
406
|
-
}
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
throw new Error("Use slopcamera code search [query] or slopcamera code execute <operation> --input <JSON>");
|
|
410
|
-
}
|
|
411
|
-
if (command === "mcp") {
|
|
412
|
-
const parsed = parseArguments(rest, new Set(["root"]));
|
|
413
|
-
if (parsed.positionals.length > 0 || parsed.flags.size > 0) {
|
|
414
|
-
throw new Error("slopcamera mcp accepts only --root <workspace>");
|
|
415
|
-
}
|
|
416
|
-
await runMcpServer({
|
|
417
|
-
rootDirectory: requiredOption(parsed, "root"),
|
|
418
|
-
serverVersion: slopcameraCliVersion
|
|
419
|
-
});
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
if (command === "doctor") {
|
|
423
|
-
console.log(`slopcamera ${slopcameraCliVersion}`);
|
|
424
|
-
console.log(`Bun ${process.versions.bun ?? "not detected"}`);
|
|
425
|
-
console.log("Headless diagram SVG/PNG/tldraw renderer ready");
|
|
426
|
-
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)");
|
|
427
|
-
console.log("Root-relative MCP check/render server ready (trusted local workspace)");
|
|
428
|
-
const gateway = slopcameraGatewayCredentialStatus();
|
|
429
|
-
console.log(gateway.available ? `Vercel AI Gateway ready via ${gateway.source}` : "Vercel AI Gateway requires AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN");
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
if (command === "skill") {
|
|
433
|
-
const [subcommand, ...subcommandArgs] = rest;
|
|
434
|
-
if (subcommand === "path") {
|
|
435
|
-
const { bundledSkillPath } = await import("./skill-install-dh0ntqba.js");
|
|
436
|
-
console.log(bundledSkillPath());
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
if (subcommand === "install") {
|
|
440
|
-
const parsed = parseArguments(subcommandArgs, new Set(["target", "scope", "project"]));
|
|
441
|
-
const target = parsed.options.target ?? "codex";
|
|
442
|
-
const scope = parsed.options.scope ?? "user";
|
|
443
|
-
if (!["codex", "claude", "agents"].includes(target)) {
|
|
444
|
-
throw new Error("--target must be codex, claude, or agents");
|
|
445
|
-
}
|
|
446
|
-
if (!["user", "project"].includes(scope)) {
|
|
447
|
-
throw new Error("--scope must be user or project");
|
|
448
|
-
}
|
|
449
|
-
const destination = await installSkill({
|
|
450
|
-
target,
|
|
451
|
-
scope,
|
|
452
|
-
...parsed.options.project === undefined ? {} : { projectDirectory: parsed.options.project },
|
|
453
|
-
force: parsed.flags.has("force")
|
|
454
|
-
});
|
|
455
|
-
console.log(`Installed slopcamera skill at ${destination}`);
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
throw new Error("Use slopcamera skill path or install");
|
|
459
|
-
}
|
|
460
|
-
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}
|
|
461
76
|
|
|
462
|
-
${help}`);
|
|
463
|
-
}
|
|
464
|
-
if (import.meta.main) {
|
|
465
|
-
try {
|
|
466
|
-
const env = standaloneSupportEnvironment();
|
|
467
|
-
let usefulResult = false;
|
|
468
|
-
await main(process.argv.slice(2), { supportEnvironment: env, onUsefulResult: () => {
|
|
469
|
-
usefulResult = true;
|
|
470
|
-
} });
|
|
471
|
-
if (usefulResult && (process.exitCode ?? 0) === 0)
|
|
472
|
-
await showProductSupportInvitation({ env });
|
|
473
|
-
} catch (error) {
|
|
474
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
475
|
-
process.exitCode = 1;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
export {
|
|
479
|
-
slopcameraCliVersion,
|
|
480
|
-
main
|
|
481
|
-
};
|
|
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};
|