human2ai 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +120 -0
- package/assets/quaternius/License_Standard.txt +22 -0
- package/assets/quaternius/README.md +31 -0
- package/assets/quaternius/Superhero_Female_FullBody.bin +0 -0
- package/assets/quaternius/Superhero_Female_FullBody.gltf +2056 -0
- package/assets/quaternius/Superhero_Male_FullBody.bin +0 -0
- package/assets/quaternius/Superhero_Male_FullBody.gltf +2018 -0
- package/dist/cli/integration.d.ts +39 -0
- package/dist/cli/integration.js +466 -0
- package/dist/cli/integration.js.map +1 -0
- package/dist/cli/main.d.ts +17 -0
- package/dist/cli/main.js +1455 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/database/composition-session-repository.d.ts +37 -0
- package/dist/database/composition-session-repository.js +147 -0
- package/dist/database/composition-session-repository.js.map +1 -0
- package/dist/database/draft-version-errors.d.ts +29 -0
- package/dist/database/draft-version-errors.js +47 -0
- package/dist/database/draft-version-errors.js.map +1 -0
- package/dist/database/draft-version-store.d.ts +30 -0
- package/dist/database/draft-version-store.js +168 -0
- package/dist/database/draft-version-store.js.map +1 -0
- package/dist/database/image-asset-repository.d.ts +37 -0
- package/dist/database/image-asset-repository.js +101 -0
- package/dist/database/image-asset-repository.js.map +1 -0
- package/dist/database/image-input.d.ts +15 -0
- package/dist/database/image-input.js +28 -0
- package/dist/database/image-input.js.map +1 -0
- package/dist/database/migrate.d.ts +4 -0
- package/dist/database/migrate.js +45 -0
- package/dist/database/migrate.js.map +1 -0
- package/dist/database/project-session-repository.d.ts +72 -0
- package/dist/database/project-session-repository.js +291 -0
- package/dist/database/project-session-repository.js.map +1 -0
- package/dist/database/spatial-session-repository.d.ts +26 -0
- package/dist/database/spatial-session-repository.js +44 -0
- package/dist/database/spatial-session-repository.js.map +1 -0
- package/dist/database/style-library-repository.d.ts +70 -0
- package/dist/database/style-library-repository.js +290 -0
- package/dist/database/style-library-repository.js.map +1 -0
- package/dist/database/svg-input.d.ts +1 -0
- package/dist/database/svg-input.js +77 -0
- package/dist/database/svg-input.js.map +1 -0
- package/dist/database/ui-sketch-session-repository.d.ts +19 -0
- package/dist/database/ui-sketch-session-repository.js +35 -0
- package/dist/database/ui-sketch-session-repository.js.map +1 -0
- package/dist/domain/canvas-layer-order.d.ts +6 -0
- package/dist/domain/canvas-layer-order.js +40 -0
- package/dist/domain/canvas-layer-order.js.map +1 -0
- package/dist/domain/canvas-node-metadata.d.ts +20 -0
- package/dist/domain/canvas-node-metadata.js +17 -0
- package/dist/domain/canvas-node-metadata.js.map +1 -0
- package/dist/domain/canvas-states.d.ts +8 -0
- package/dist/domain/canvas-states.js +23 -0
- package/dist/domain/canvas-states.js.map +1 -0
- package/dist/domain/composition/analysis.d.ts +104 -0
- package/dist/domain/composition/analysis.js +337 -0
- package/dist/domain/composition/analysis.js.map +1 -0
- package/dist/domain/composition/camera-reference.d.ts +24 -0
- package/dist/domain/composition/camera-reference.js +34 -0
- package/dist/domain/composition/camera-reference.js.map +1 -0
- package/dist/domain/composition/draft.d.ts +50 -0
- package/dist/domain/composition/draft.js +609 -0
- package/dist/domain/composition/draft.js.map +1 -0
- package/dist/domain/composition/frame.d.ts +23 -0
- package/dist/domain/composition/frame.js +154 -0
- package/dist/domain/composition/frame.js.map +1 -0
- package/dist/domain/composition/geometry.d.ts +15 -0
- package/dist/domain/composition/geometry.js +262 -0
- package/dist/domain/composition/geometry.js.map +1 -0
- package/dist/domain/composition/index.d.ts +13 -0
- package/dist/domain/composition/index.js +10 -0
- package/dist/domain/composition/index.js.map +1 -0
- package/dist/domain/composition/layers.d.ts +4 -0
- package/dist/domain/composition/layers.js +12 -0
- package/dist/domain/composition/layers.js.map +1 -0
- package/dist/domain/composition/records.d.ts +14 -0
- package/dist/domain/composition/records.js +2 -0
- package/dist/domain/composition/records.js.map +1 -0
- package/dist/domain/composition/refinement/engine.d.ts +12 -0
- package/dist/domain/composition/refinement/engine.js +521 -0
- package/dist/domain/composition/refinement/engine.js.map +1 -0
- package/dist/domain/composition/refinement/relations.d.ts +16 -0
- package/dist/domain/composition/refinement/relations.js +293 -0
- package/dist/domain/composition/refinement/relations.js.map +1 -0
- package/dist/domain/composition/refinement/types.d.ts +181 -0
- package/dist/domain/composition/refinement/types.js +2 -0
- package/dist/domain/composition/refinement/types.js.map +1 -0
- package/dist/domain/composition/render.d.ts +5 -0
- package/dist/domain/composition/render.js +169 -0
- package/dist/domain/composition/render.js.map +1 -0
- package/dist/domain/composition/states.d.ts +9 -0
- package/dist/domain/composition/states.js +104 -0
- package/dist/domain/composition/states.js.map +1 -0
- package/dist/domain/composition/types.d.ts +149 -0
- package/dist/domain/composition/types.js +2 -0
- package/dist/domain/composition/types.js.map +1 -0
- package/dist/domain/composition/workflow.d.ts +15 -0
- package/dist/domain/composition/workflow.js +77 -0
- package/dist/domain/composition/workflow.js.map +1 -0
- package/dist/domain/fingerprint.d.ts +2 -0
- package/dist/domain/fingerprint.js +25 -0
- package/dist/domain/fingerprint.js.map +1 -0
- package/dist/domain/session/canvas-edit-history.d.ts +17 -0
- package/dist/domain/session/canvas-edit-history.js +53 -0
- package/dist/domain/session/canvas-edit-history.js.map +1 -0
- package/dist/domain/session/capabilities.d.ts +16 -0
- package/dist/domain/session/capabilities.js +84 -0
- package/dist/domain/session/capabilities.js.map +1 -0
- package/dist/domain/session/draft-version.d.ts +41 -0
- package/dist/domain/session/draft-version.js +2 -0
- package/dist/domain/session/draft-version.js.map +1 -0
- package/dist/domain/session/index.d.ts +5 -0
- package/dist/domain/session/index.js +3 -0
- package/dist/domain/session/index.js.map +1 -0
- package/dist/domain/session/types.d.ts +23 -0
- package/dist/domain/session/types.js +2 -0
- package/dist/domain/session/types.js.map +1 -0
- package/dist/domain/spatial/assets/quaternius-female-rig.json +799 -0
- package/dist/domain/spatial/assets/quaternius-rig.json +799 -0
- package/dist/domain/spatial/assets/quaternius-superhero-female.json +1 -0
- package/dist/domain/spatial/assets/quaternius-superhero.json +1 -0
- package/dist/domain/spatial/camera-box.d.ts +13 -0
- package/dist/domain/spatial/camera-box.js +52 -0
- package/dist/domain/spatial/camera-box.js.map +1 -0
- package/dist/domain/spatial/geometric-model.d.ts +4 -0
- package/dist/domain/spatial/geometric-model.js +101 -0
- package/dist/domain/spatial/geometric-model.js.map +1 -0
- package/dist/domain/spatial/hands.d.ts +36 -0
- package/dist/domain/spatial/hands.js +128 -0
- package/dist/domain/spatial/hands.js.map +1 -0
- package/dist/domain/spatial/index.d.ts +30 -0
- package/dist/domain/spatial/index.js +381 -0
- package/dist/domain/spatial/index.js.map +1 -0
- package/dist/domain/spatial/kinematics.d.ts +27 -0
- package/dist/domain/spatial/kinematics.js +285 -0
- package/dist/domain/spatial/kinematics.js.map +1 -0
- package/dist/domain/spatial/lighting.d.ts +35 -0
- package/dist/domain/spatial/lighting.js +173 -0
- package/dist/domain/spatial/lighting.js.map +1 -0
- package/dist/domain/spatial/model.d.ts +5 -0
- package/dist/domain/spatial/model.js +193 -0
- package/dist/domain/spatial/model.js.map +1 -0
- package/dist/domain/spatial/morph.d.ts +3 -0
- package/dist/domain/spatial/morph.js +187 -0
- package/dist/domain/spatial/morph.js.map +1 -0
- package/dist/domain/spatial/proportions.d.ts +46 -0
- package/dist/domain/spatial/proportions.js +59 -0
- package/dist/domain/spatial/proportions.js.map +1 -0
- package/dist/domain/spatial/reference-lines.d.ts +6 -0
- package/dist/domain/spatial/reference-lines.js +58 -0
- package/dist/domain/spatial/reference-lines.js.map +1 -0
- package/dist/domain/spatial/scene-cache.d.ts +15 -0
- package/dist/domain/spatial/scene-cache.js +69 -0
- package/dist/domain/spatial/scene-cache.js.map +1 -0
- package/dist/domain/spatial/scene.d.ts +8 -0
- package/dist/domain/spatial/scene.js +69 -0
- package/dist/domain/spatial/scene.js.map +1 -0
- package/dist/domain/spatial/types.d.ts +187 -0
- package/dist/domain/spatial/types.js +5 -0
- package/dist/domain/spatial/types.js.map +1 -0
- package/dist/domain/style/index.d.ts +1 -0
- package/dist/domain/style/index.js +2 -0
- package/dist/domain/style/index.js.map +1 -0
- package/dist/domain/style/prompt-summary.d.ts +2 -0
- package/dist/domain/style/prompt-summary.js +11 -0
- package/dist/domain/style/prompt-summary.js.map +1 -0
- package/dist/domain/style/types.d.ts +32 -0
- package/dist/domain/style/types.js +3 -0
- package/dist/domain/style/types.js.map +1 -0
- package/dist/domain/ui-sketch/draft.d.ts +12 -0
- package/dist/domain/ui-sketch/draft.js +424 -0
- package/dist/domain/ui-sketch/draft.js.map +1 -0
- package/dist/domain/ui-sketch/geometry.d.ts +7 -0
- package/dist/domain/ui-sketch/geometry.js +11 -0
- package/dist/domain/ui-sketch/geometry.js.map +1 -0
- package/dist/domain/ui-sketch/groups.d.ts +5 -0
- package/dist/domain/ui-sketch/groups.js +40 -0
- package/dist/domain/ui-sketch/groups.js.map +1 -0
- package/dist/domain/ui-sketch/index.d.ts +8 -0
- package/dist/domain/ui-sketch/index.js +8 -0
- package/dist/domain/ui-sketch/index.js.map +1 -0
- package/dist/domain/ui-sketch/layers.d.ts +4 -0
- package/dist/domain/ui-sketch/layers.js +11 -0
- package/dist/domain/ui-sketch/layers.js.map +1 -0
- package/dist/domain/ui-sketch/render.d.ts +2 -0
- package/dist/domain/ui-sketch/render.js +58 -0
- package/dist/domain/ui-sketch/render.js.map +1 -0
- package/dist/domain/ui-sketch/standardize.d.ts +17 -0
- package/dist/domain/ui-sketch/standardize.js +139 -0
- package/dist/domain/ui-sketch/standardize.js.map +1 -0
- package/dist/domain/ui-sketch/states.d.ts +5 -0
- package/dist/domain/ui-sketch/states.js +21 -0
- package/dist/domain/ui-sketch/states.js.map +1 -0
- package/dist/domain/ui-sketch/types.d.ts +73 -0
- package/dist/domain/ui-sketch/types.js +2 -0
- package/dist/domain/ui-sketch/types.js.map +1 -0
- package/dist/server/app.d.ts +18 -0
- package/dist/server/app.js +108 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/image-content-type-parser.d.ts +2 -0
- package/dist/server/image-content-type-parser.js +5 -0
- package/dist/server/image-content-type-parser.js.map +1 -0
- package/dist/server/routes/composition-sessions.d.ts +3 -0
- package/dist/server/routes/composition-sessions.js +190 -0
- package/dist/server/routes/composition-sessions.js.map +1 -0
- package/dist/server/routes/draft-version-routes.d.ts +10 -0
- package/dist/server/routes/draft-version-routes.js +201 -0
- package/dist/server/routes/draft-version-routes.js.map +1 -0
- package/dist/server/routes/image-assets.d.ts +3 -0
- package/dist/server/routes/image-assets.js +114 -0
- package/dist/server/routes/image-assets.js.map +1 -0
- package/dist/server/routes/project-sessions.d.ts +48 -0
- package/dist/server/routes/project-sessions.js +305 -0
- package/dist/server/routes/project-sessions.js.map +1 -0
- package/dist/server/routes/spatial-sessions.d.ts +3 -0
- package/dist/server/routes/spatial-sessions.js +80 -0
- package/dist/server/routes/spatial-sessions.js.map +1 -0
- package/dist/server/routes/style-library.d.ts +3 -0
- package/dist/server/routes/style-library.js +308 -0
- package/dist/server/routes/style-library.js.map +1 -0
- package/dist/server/routes/ui-sketch-sessions.d.ts +3 -0
- package/dist/server/routes/ui-sketch-sessions.js +14 -0
- package/dist/server/routes/ui-sketch-sessions.js.map +1 -0
- package/dist/server/runtime.d.ts +30 -0
- package/dist/server/runtime.js +123 -0
- package/dist/server/runtime.js.map +1 -0
- package/dist/server/spatial-render.d.ts +5 -0
- package/dist/server/spatial-render.js +360 -0
- package/dist/server/spatial-render.js.map +1 -0
- package/dist/server/start.d.ts +1 -0
- package/dist/server/start.js +3 -0
- package/dist/server/start.js.map +1 -0
- package/licenses/THIRD-PARTY-NOTICES.txt +16664 -0
- package/licenses/YisiUI-LICENSE +21 -0
- package/locales/en/common.json +671 -0
- package/migrations/0001_projects_and_sessions.sql +45 -0
- package/migrations/0002_composition_versions_and_refinements.sql +26 -0
- package/migrations/0003_ui_sketch_draft_versions.sql +12 -0
- package/migrations/0004_image_assets.sql +16 -0
- package/migrations/0005_style_library.sql +31 -0
- package/migrations/0006_session_styles.sql +6 -0
- package/migrations/0007_svg_image_assets.sql +19 -0
- package/migrations/0008_spatial_spaces.sql +20 -0
- package/package.json +82 -0
- package/schemas/composition-draft.schema.json +234 -0
- package/schemas/composition-refinement-plan.schema.json +758 -0
- package/schemas/spatial-draft.schema.json +517 -0
- package/schemas/spatial-operations.schema.json +1082 -0
- package/schemas/ui-layout-standardization-plan.schema.json +29 -0
- package/schemas/ui-sketch-draft.schema.json +197 -0
- package/skills/human2ai/SKILL.md +149 -0
- package/skills/human2ai/agents/openai.yaml +3 -0
- package/skills/human2ai/references/composition-projection.md +170 -0
- package/skills/human2ai/references/composition.md +79 -0
- package/skills/human2ai/references/spatial.md +188 -0
- package/skills/human2ai/references/svg-images.md +27 -0
- package/skills/human2ai/references/ui-layout.md +100 -0
- package/web/out/404/index.html +1 -0
- package/web/out/404.html +1 -0
- package/web/out/__next.__PAGE__.txt +26 -0
- package/web/out/__next._full.txt +21 -0
- package/web/out/__next._tree.txt +4 -0
- package/web/out/_next/static/TV-jgcACg6HANZAMZTE2S/_buildManifest.js +1 -0
- package/web/out/_next/static/TV-jgcACg6HANZAMZTE2S/_ssgManifest.js +1 -0
- package/web/out/_next/static/chunks/130-9fadd0432daff55d.js +1 -0
- package/web/out/_next/static/chunks/158-eb00456e1bab68b3.js +52 -0
- package/web/out/_next/static/chunks/20-0801fc297cda2a7a.js +1 -0
- package/web/out/_next/static/chunks/248-d4a79649dae2a3f8.js +259 -0
- package/web/out/_next/static/chunks/678-1305cadf8254d814.js +1 -0
- package/web/out/_next/static/chunks/728-eb8b667f79229443.js +1 -0
- package/web/out/_next/static/chunks/83607670-bc1279370476e59f.js +1 -0
- package/web/out/_next/static/chunks/app/_global-error/page-5eece43b8152c414.js +1 -0
- package/web/out/_next/static/chunks/app/_not-found/page-c0cd75ac39fbf9cf.js +1 -0
- package/web/out/_next/static/chunks/app/composition/page-051ee2fabb12110a.js +1 -0
- package/web/out/_next/static/chunks/app/layout-745ca27ef8830f4f.js +1 -0
- package/web/out/_next/static/chunks/app/page-0027378a269a6168.js +1 -0
- package/web/out/_next/static/chunks/app/spatial/page-266dedc81784d4f6.js +1 -0
- package/web/out/_next/static/chunks/app/styles/page-d5cb83af828ea9ed.js +1 -0
- package/web/out/_next/static/chunks/app/ui-sketch/page-2a8436e05a05c618.js +1 -0
- package/web/out/_next/static/chunks/c3f2cda1-9e279dab137b7272.js +342 -0
- package/web/out/_next/static/chunks/c7879cf7-7873c8afd9c9c666.js +1 -0
- package/web/out/_next/static/chunks/framework-6eacf80ec1e4c887.js +1 -0
- package/web/out/_next/static/chunks/main-5ee8fd93319f955c.js +5 -0
- package/web/out/_next/static/chunks/main-app-0cf10803ab17c700.js +1 -0
- package/web/out/_next/static/chunks/next/dist/client/components/builtin/app-error-5eece43b8152c414.js +1 -0
- package/web/out/_next/static/chunks/next/dist/client/components/builtin/forbidden-5eece43b8152c414.js +1 -0
- package/web/out/_next/static/chunks/next/dist/client/components/builtin/global-error-728204b7cccc4ed2.js +1 -0
- package/web/out/_next/static/chunks/next/dist/client/components/builtin/not-found-5eece43b8152c414.js +1 -0
- package/web/out/_next/static/chunks/next/dist/client/components/builtin/unauthorized-5eece43b8152c414.js +1 -0
- package/web/out/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/web/out/_next/static/chunks/webpack-97ae63c0e0b2a512.js +1 -0
- package/web/out/_next/static/css/3b7982b41ed8eccd.css +4 -0
- package/web/out/_next/static/css/81ae71f1d3e9a866.css +1 -0
- package/web/out/_next/static/css/8c3843f4951fa9f6.css +5 -0
- package/web/out/_next/static/css/c739d0131cdcc891.css +1 -0
- package/web/out/_next/static/css/ce1b4bb3ad3c6073.css +1 -0
- package/web/out/_next/static/css/db96ae2e88545d96.css +1 -0
- package/web/out/_not-found/__next._full.txt +18 -0
- package/web/out/_not-found/__next._not-found.__PAGE__.txt +24 -0
- package/web/out/_not-found/__next._tree.txt +3 -0
- package/web/out/_not-found/index.html +1 -0
- package/web/out/_not-found/index.txt +18 -0
- package/web/out/composition/__next._full.txt +25 -0
- package/web/out/composition/__next._tree.txt +6 -0
- package/web/out/composition/__next.composition.__PAGE__.txt +31 -0
- package/web/out/composition/index.html +1 -0
- package/web/out/composition/index.txt +25 -0
- package/web/out/index.html +1 -0
- package/web/out/index.txt +21 -0
- package/web/out/spatial/__next._full.txt +23 -0
- package/web/out/spatial/__next._tree.txt +4 -0
- package/web/out/spatial/__next.spatial.__PAGE__.txt +29 -0
- package/web/out/spatial/index.html +1 -0
- package/web/out/spatial/index.txt +23 -0
- package/web/out/styles/__next._full.txt +23 -0
- package/web/out/styles/__next._tree.txt +4 -0
- package/web/out/styles/__next.styles.__PAGE__.txt +29 -0
- package/web/out/styles/index.html +1 -0
- package/web/out/styles/index.txt +23 -0
- package/web/out/ui-sketch/__next._full.txt +25 -0
- package/web/out/ui-sketch/__next._tree.txt +6 -0
- package/web/out/ui-sketch/__next.ui-sketch.__PAGE__.txt +31 -0
- package/web/out/ui-sketch/index.html +1 -0
- package/web/out/ui-sketch/index.txt +25 -0
package/dist/cli/main.js
ADDED
|
@@ -0,0 +1,1455 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
4
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import sharp from "sharp";
|
|
9
|
+
import { CliIntegrationError, executeIntegrationCommand, integrationStartDirectory, integrationUsage, readIntegrationManifestSync, } from "./integration.js";
|
|
10
|
+
import { RefinementConstraintError, applyRefinementPlan, inspectComposition, refinementMethods, refinementPlanSchema, renderCompositionReferenceSvg, renderCompositionSvg, validateDraft, } from "../domain/composition/index.js";
|
|
11
|
+
import { SESSION_TYPES, sessionTypeDefinition, } from "../domain/session/index.js";
|
|
12
|
+
import { validateUiSketchDraft, standardizeUiSketchDraft, uiSketchStateTabs, uiSketchDraftForStage, renderUiSketchSvg, } from "../domain/ui-sketch/index.js";
|
|
13
|
+
import { validateSpatialDraft, jointWorldTransforms, boneWorldTransforms, angles } from "../domain/spatial/index.js";
|
|
14
|
+
import spatialOperationsSchema from "../../schemas/spatial-operations.schema.json" with { type: "json" };
|
|
15
|
+
import spatialSchema from "../../schemas/spatial-draft.schema.json" with { type: "json" };
|
|
16
|
+
import { SPATIAL_BOX_FACES, SPATIAL_RENDER_PASSES } from "../domain/spatial/types.js";
|
|
17
|
+
import en from "../../locales/en/common.json" with { type: "json" };
|
|
18
|
+
const DEFAULT_SERVICE_URL = "http://127.0.0.1:4179";
|
|
19
|
+
export class CliServiceError extends Error {
|
|
20
|
+
code;
|
|
21
|
+
status;
|
|
22
|
+
details;
|
|
23
|
+
constructor(code, message, status, details = {}) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.code = code;
|
|
26
|
+
this.status = status;
|
|
27
|
+
this.details = details;
|
|
28
|
+
this.name = "CliServiceError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export async function executeCli(args, dependencies = {}) {
|
|
32
|
+
const invocation = parseGlobalOptions(args);
|
|
33
|
+
const effectiveDependencies = {
|
|
34
|
+
...dependencies,
|
|
35
|
+
...(invocation.options.apiUrl === undefined
|
|
36
|
+
? {}
|
|
37
|
+
: { serviceUrl: invocation.options.apiUrl }),
|
|
38
|
+
...(invocation.options.webUrl === undefined
|
|
39
|
+
? {}
|
|
40
|
+
: { webUrl: invocation.options.webUrl }),
|
|
41
|
+
};
|
|
42
|
+
const scope = invocation.commandArgs[0];
|
|
43
|
+
const command = invocation.commandArgs[1];
|
|
44
|
+
const options = parseOptions(invocation.commandArgs.slice(2));
|
|
45
|
+
if (scope === "service") {
|
|
46
|
+
return executeServiceCommand(command, options, effectiveDependencies);
|
|
47
|
+
}
|
|
48
|
+
if (scope === "integration") {
|
|
49
|
+
if (command === "install") {
|
|
50
|
+
assertOnlyOptions(options, ["agent", "root", "mode"], "integration install");
|
|
51
|
+
}
|
|
52
|
+
else if (command === "sync") {
|
|
53
|
+
assertOnlyOptions(options, ["root", "check"], "integration sync");
|
|
54
|
+
}
|
|
55
|
+
else if (command === "doctor") {
|
|
56
|
+
assertOnlyOptions(options, ["root"], "integration doctor");
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
throw new Error(integrationUsage());
|
|
60
|
+
}
|
|
61
|
+
const startDirectory = options.root
|
|
62
|
+
?? effectiveDependencies.cwd
|
|
63
|
+
?? integrationStartDirectory();
|
|
64
|
+
const integrationDependencies = {
|
|
65
|
+
...effectiveDependencies,
|
|
66
|
+
cwd: startDirectory,
|
|
67
|
+
};
|
|
68
|
+
return executeIntegrationCommand(command, options, {
|
|
69
|
+
apiUrl: configuredServiceUrl(integrationDependencies).origin,
|
|
70
|
+
sourceRoot: effectiveDependencies.integrationSourceRoot,
|
|
71
|
+
startDirectory,
|
|
72
|
+
webUrl: configuredWebUrl(integrationDependencies).origin,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (scope === "project") {
|
|
76
|
+
return executeProjectCommand(command, options, effectiveDependencies);
|
|
77
|
+
}
|
|
78
|
+
if (scope === "session") {
|
|
79
|
+
return executeSessionCommand(command, options, effectiveDependencies);
|
|
80
|
+
}
|
|
81
|
+
if (scope === "capture") {
|
|
82
|
+
return executeCaptureCommand(command, options, effectiveDependencies);
|
|
83
|
+
}
|
|
84
|
+
if (scope === "style") {
|
|
85
|
+
return executeStyleCommand(command, options, effectiveDependencies);
|
|
86
|
+
}
|
|
87
|
+
if (scope === "image") {
|
|
88
|
+
return executeImageCommand(command, options, effectiveDependencies);
|
|
89
|
+
}
|
|
90
|
+
if (scope === "spatial") {
|
|
91
|
+
if (command === "methods") {
|
|
92
|
+
assertOnlyOptions(options, [], "spatial methods");
|
|
93
|
+
return { kind: "spatial-methods", schema: spatialSchema, operationsSchema: spatialOperationsSchema, renderPasses: SPATIAL_RENDER_PASSES, cameraBoxViews: ["sheet", ...SPATIAL_BOX_FACES], cameraBoxGuidance: en.spatial.cameraBoxGenerationGuidance, operations: ["set-lighting", "add-character", "put-character", "add-limb", "set-proportions", "move-joint", "rotate-bone", "lock-joint", "lock-bone", "set-bone-limits", "reset-pose", "pose-hand", "reset-hand", "put-object", "put-camera", "put-camera-box", "fit-camera-box", "remove"], reference: "skills/human2ai/references/spatial.md" };
|
|
94
|
+
}
|
|
95
|
+
const sessionId = requireOption(options, "session");
|
|
96
|
+
if (command === "inspect") {
|
|
97
|
+
assertOnlyOptions(options, ["session", "revision", "output"], "spatial inspect");
|
|
98
|
+
const revision = requireInteger(options, "revision", 1);
|
|
99
|
+
const version = requireRecord(await requestService(effectiveDependencies, `/api/v1/sessions/${encodeURIComponent(sessionId)}/spatial/drafts/${revision}`), "spatial draft");
|
|
100
|
+
const draft = validateSpatialDraft(version.draft);
|
|
101
|
+
const result = { sessionId, revision, draft, characters: draft.characters.map(character => ({ id: character.id, bones: Object.entries(boneWorldTransforms(character)).map(([id, world]) => ({ id, pivotPosition: world.position.toArray(), worldRotation: angles(world.rotation) })), joints: Object.entries(jointWorldTransforms(character)).map(([id, world]) => ({ id, worldPosition: world.position.toArray(), worldRotation: angles(world.rotation) })) })) };
|
|
102
|
+
if (options.output)
|
|
103
|
+
await writeJson(path.resolve(options.output), result);
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
if (command === "apply") {
|
|
107
|
+
assertOnlyOptions(options, ["session", "revision", "input", "output"], "spatial apply");
|
|
108
|
+
const result = await requestService(effectiveDependencies, `/api/v1/sessions/${encodeURIComponent(sessionId)}/spatial/operations`, { method: "POST", body: { expectedLatestRevision: requireInteger(options, "revision", 0), operations: await readJson(path.resolve(requireOption(options, "input"))) } });
|
|
109
|
+
if (options.output)
|
|
110
|
+
await writeJson(path.resolve(options.output), result);
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
if (command === "render") {
|
|
114
|
+
assertOnlyOptions(options, ["session", "revision", "camera", "box", "view", "pass", "output"], "spatial render");
|
|
115
|
+
const cameraId = options.camera, boxId = options.box;
|
|
116
|
+
if (Boolean(cameraId) === Boolean(boxId))
|
|
117
|
+
throw new Error(en.spatial.cameraBoxTargetError);
|
|
118
|
+
const view = options.view ?? "sheet";
|
|
119
|
+
if (options.view && !boxId)
|
|
120
|
+
throw new Error(en.spatial.cameraBoxViewTargetError);
|
|
121
|
+
if (view !== "sheet" && !SPATIAL_BOX_FACES.some(face => face === view))
|
|
122
|
+
throw new Error(en.spatial.cameraBoxViewError);
|
|
123
|
+
const revision = requireInteger(options, "revision", 1);
|
|
124
|
+
const pass = options.pass ?? "color";
|
|
125
|
+
if (!SPATIAL_RENDER_PASSES.some(value => value === pass))
|
|
126
|
+
throw new Error(en.spatial.renderPassError.replace("{{passes}}", SPATIAL_RENDER_PASSES.join(", ")));
|
|
127
|
+
const target = boxId ? `camera-boxes/${encodeURIComponent(boxId)}.png?view=${view}&` : `cameras/${encodeURIComponent(cameraId)}.png?`;
|
|
128
|
+
const png = await requestService(effectiveDependencies, `/api/v1/sessions/${encodeURIComponent(sessionId)}/spatial/${target}revision=${revision}&pass=${pass}`, { png: true });
|
|
129
|
+
const outputPath = path.resolve(requireOption(options, "output"));
|
|
130
|
+
await writeFile(outputPath, png);
|
|
131
|
+
return { sessionId, ...(boxId ? { boxId, view } : { cameraId }), revision, pass, outputPath };
|
|
132
|
+
}
|
|
133
|
+
throw new Error("Use spatial methods, spatial inspect, spatial apply, or spatial render.");
|
|
134
|
+
}
|
|
135
|
+
if (scope === "ui-layout" && command === "render") {
|
|
136
|
+
return renderUiLayoutPreview(options, effectiveDependencies);
|
|
137
|
+
}
|
|
138
|
+
if (scope === "ui-layout" && command === "standardize") {
|
|
139
|
+
assertOnlyOptions(options, ["input", "output", "plan"], "ui-layout standardize");
|
|
140
|
+
const inputPath = path.resolve(requireOption(options, "input"));
|
|
141
|
+
const outputPath = path.resolve(requireOption(options, "output"));
|
|
142
|
+
if (inputPath === outputPath)
|
|
143
|
+
throw new Error("Standardization output must differ from the source document.");
|
|
144
|
+
const result = standardizeUiSketchDraft(await readJson(inputPath), options.plan ? await readJson(path.resolve(options.plan)) : undefined);
|
|
145
|
+
await writeJson(outputPath, result.draft);
|
|
146
|
+
const { draft: _draft, ...report } = result;
|
|
147
|
+
return { ...report, outputPath };
|
|
148
|
+
}
|
|
149
|
+
if (scope !== "composition")
|
|
150
|
+
throw new Error(usage());
|
|
151
|
+
if (command === "methods") {
|
|
152
|
+
assertOnlyOptions(options, [], "methods");
|
|
153
|
+
return {
|
|
154
|
+
version: 1,
|
|
155
|
+
kind: "composition-refinement-methods",
|
|
156
|
+
decisionOwner: "agent",
|
|
157
|
+
methods: refinementMethods(),
|
|
158
|
+
planSchema: refinementPlanSchema(),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (command === "save") {
|
|
162
|
+
assertOnlyOptions(options, ["session", "draft", "expectedRevision"], "save");
|
|
163
|
+
const sessionId = requireOption(options, "session");
|
|
164
|
+
const draftPath = path.resolve(requireOption(options, "draft"));
|
|
165
|
+
const draft = validateDraft(await readJson(draftPath));
|
|
166
|
+
const expectedLatestRevision = requireInteger(options, "expectedRevision", 0);
|
|
167
|
+
return parseDraftVersion(await requestService(effectiveDependencies, compositionDraftsPath(sessionId), {
|
|
168
|
+
method: "POST",
|
|
169
|
+
body: { expectedLatestRevision, draft },
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
if (command === "drafts") {
|
|
173
|
+
assertOnlyOptions(options, ["session"], "drafts");
|
|
174
|
+
const sessionId = requireOption(options, "session");
|
|
175
|
+
return requestService(effectiveDependencies, compositionDraftsPath(sessionId));
|
|
176
|
+
}
|
|
177
|
+
if (command === "inspect") {
|
|
178
|
+
return options.session
|
|
179
|
+
? inspectSessionDraft(options, effectiveDependencies)
|
|
180
|
+
: inspectLocalDraft(options);
|
|
181
|
+
}
|
|
182
|
+
if (command === "apply") {
|
|
183
|
+
return options.session
|
|
184
|
+
? applySessionRefinement(options, effectiveDependencies)
|
|
185
|
+
: applyLocalRefinement(options);
|
|
186
|
+
}
|
|
187
|
+
if (command === "refinements") {
|
|
188
|
+
assertOnlyOptions(options, ["session"], "refinements");
|
|
189
|
+
const sessionId = requireOption(options, "session");
|
|
190
|
+
return requestService(effectiveDependencies, compositionRefinementsPath(sessionId));
|
|
191
|
+
}
|
|
192
|
+
if (command === "refinement") {
|
|
193
|
+
assertOnlyOptions(options, ["session", "run", "output", "preview"], "refinement");
|
|
194
|
+
const sessionId = requireOption(options, "session");
|
|
195
|
+
const runId = requireOption(options, "run");
|
|
196
|
+
const run = parseRefinementRun(await requestService(effectiveDependencies, `${compositionRefinementsPath(sessionId)}/${encodeURIComponent(runId)}`));
|
|
197
|
+
return writeSessionRefinementArtifacts(run, options, effectiveDependencies);
|
|
198
|
+
}
|
|
199
|
+
if (command === "reference") {
|
|
200
|
+
return writeSessionReference(options, effectiveDependencies);
|
|
201
|
+
}
|
|
202
|
+
throw new Error(usage());
|
|
203
|
+
}
|
|
204
|
+
async function executeStyleCommand(command, options, dependencies) {
|
|
205
|
+
if (command === "list") {
|
|
206
|
+
assertOnlyOptions(options, ["category", "creator"], "style list");
|
|
207
|
+
if (options.category && options.category !== "visual" && options.category !== "ui") {
|
|
208
|
+
throw new Error("--category must be visual or ui.");
|
|
209
|
+
}
|
|
210
|
+
if (options.creator && options.creator !== "user" && options.creator !== "agent") {
|
|
211
|
+
throw new Error("--creator must be user or agent.");
|
|
212
|
+
}
|
|
213
|
+
const payload = requireRecord(await requestService(dependencies, "/api/v1/styles"), "style list");
|
|
214
|
+
if (!Array.isArray(payload.styles)) {
|
|
215
|
+
throw invalidServiceResponse("style list styles is not an array");
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
styles: payload.styles.filter((style) => (isRecord(style)
|
|
219
|
+
&& (!options.category || style.category === options.category)
|
|
220
|
+
&& (!options.creator || style.creatorType === options.creator))),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (command === "get") {
|
|
224
|
+
assertOnlyOptions(options, ["style"], "style get");
|
|
225
|
+
const styleId = requireOption(options, "style");
|
|
226
|
+
const payload = requireRecord(await requestService(dependencies, `/api/v1/agent/styles/${encodeURIComponent(styleId)}/context`), "style context");
|
|
227
|
+
if (typeof payload.description !== "string" || !Array.isArray(payload.referenceImages)) {
|
|
228
|
+
throw invalidServiceResponse("style context is invalid");
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
referenceImages: payload.referenceImages.map((reference) => {
|
|
232
|
+
const value = requireRecord(reference, "style reference");
|
|
233
|
+
if (typeof value.url !== "string") {
|
|
234
|
+
throw invalidServiceResponse("style reference URL is invalid");
|
|
235
|
+
}
|
|
236
|
+
return { url: new URL(value.url, configuredServiceUrl(dependencies)).toString() };
|
|
237
|
+
}),
|
|
238
|
+
description: payload.description,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
if (command === "create") {
|
|
242
|
+
assertOnlyOptions(options, ["name", "category", "description", "summary"], "style create");
|
|
243
|
+
return requestService(dependencies, "/api/v1/agent/styles", {
|
|
244
|
+
method: "POST",
|
|
245
|
+
body: {
|
|
246
|
+
name: requireOption(options, "name"),
|
|
247
|
+
category: requireStyleCategory(options),
|
|
248
|
+
...(options.summary === undefined ? {} : { promptSummary: options.summary }),
|
|
249
|
+
description: requireOption(options, "description"),
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
if (command === "update") {
|
|
254
|
+
assertOnlyOptions(options, ["style", "expectedRevision", "name", "category", "description", "summary"], "style update");
|
|
255
|
+
if (options.name === undefined && options.category === undefined && options.description === undefined && options.summary === undefined) {
|
|
256
|
+
throw new Error("style update requires --name, --category, --description, or --summary.");
|
|
257
|
+
}
|
|
258
|
+
const styleId = requireOption(options, "style");
|
|
259
|
+
return requestService(dependencies, `/api/v1/styles/${encodeURIComponent(styleId)}`, {
|
|
260
|
+
method: "PATCH",
|
|
261
|
+
body: {
|
|
262
|
+
expectedRevision: requireInteger(options, "expectedRevision", 1),
|
|
263
|
+
...(options.name === undefined ? {} : { name: options.name }),
|
|
264
|
+
...(options.summary === undefined ? {} : { promptSummary: options.summary }),
|
|
265
|
+
...(options.category === undefined
|
|
266
|
+
? {}
|
|
267
|
+
: { category: requireStyleCategory(options) }),
|
|
268
|
+
...(options.description === undefined
|
|
269
|
+
? {}
|
|
270
|
+
: { description: options.description }),
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
if (command === "add-reference") {
|
|
275
|
+
assertOnlyOptions(options, ["style", "input", "expectedRevision"], "style add-reference");
|
|
276
|
+
const styleId = requireOption(options, "style");
|
|
277
|
+
const filename = path.resolve(requireOption(options, "input"));
|
|
278
|
+
return requestImageService(dependencies, `/api/v1/styles/${encodeURIComponent(styleId)}/references?filename=${encodeURIComponent(path.basename(filename))}&expectedRevision=${requireInteger(options, "expectedRevision", 1)}`, await readFile(filename));
|
|
279
|
+
}
|
|
280
|
+
if (command === "remove-reference") {
|
|
281
|
+
assertOnlyOptions(options, ["style", "reference", "expectedRevision"], "style remove-reference");
|
|
282
|
+
const styleId = requireOption(options, "style");
|
|
283
|
+
const referenceId = requireOption(options, "reference");
|
|
284
|
+
return requestService(dependencies, `/api/v1/styles/${encodeURIComponent(styleId)}/references/${encodeURIComponent(referenceId)}`, {
|
|
285
|
+
method: "DELETE",
|
|
286
|
+
body: { expectedRevision: requireInteger(options, "expectedRevision", 1) },
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
if (command === "delete") {
|
|
290
|
+
assertOnlyOptions(options, ["style", "expectedRevision"], "style delete");
|
|
291
|
+
const styleId = requireOption(options, "style");
|
|
292
|
+
await requestService(dependencies, `/api/v1/styles/${encodeURIComponent(styleId)}`, {
|
|
293
|
+
method: "DELETE",
|
|
294
|
+
body: { expectedRevision: requireInteger(options, "expectedRevision", 1) },
|
|
295
|
+
});
|
|
296
|
+
return { deleted: true, styleId };
|
|
297
|
+
}
|
|
298
|
+
throw new Error(styleUsage());
|
|
299
|
+
}
|
|
300
|
+
function requireStyleCategory(options) {
|
|
301
|
+
const value = requireOption(options, "category");
|
|
302
|
+
if (value !== "visual" && value !== "ui") {
|
|
303
|
+
throw new Error("--category must be visual or ui.");
|
|
304
|
+
}
|
|
305
|
+
return value;
|
|
306
|
+
}
|
|
307
|
+
async function executeImageCommand(command, options, dependencies) {
|
|
308
|
+
if (command === "upload") {
|
|
309
|
+
assertOnlyOptions(options, ["session", "input"], "image upload");
|
|
310
|
+
const sessionId = requireOption(options, "session");
|
|
311
|
+
const filename = path.resolve(requireOption(options, "input"));
|
|
312
|
+
return requestImageService(dependencies, `/api/v1/sessions/${encodeURIComponent(sessionId)}/assets?filename=${encodeURIComponent(path.basename(filename))}`, await readFile(filename));
|
|
313
|
+
}
|
|
314
|
+
if (command === "source") {
|
|
315
|
+
assertOnlyOptions(options, ["session", "asset", "output"], "image source");
|
|
316
|
+
const sessionId = requireOption(options, "session");
|
|
317
|
+
const assetId = requireOption(options, "asset");
|
|
318
|
+
const source = await requestService(dependencies, `/api/v1/sessions/${encodeURIComponent(sessionId)}/assets/${encodeURIComponent(assetId)}/content`, { svgSource: true });
|
|
319
|
+
if (options.output) {
|
|
320
|
+
const output = path.resolve(options.output);
|
|
321
|
+
await writeFile(output, source, "utf8");
|
|
322
|
+
return { sessionId, assetId, output };
|
|
323
|
+
}
|
|
324
|
+
return { sessionId, assetId, source };
|
|
325
|
+
}
|
|
326
|
+
throw new Error(imageUsage());
|
|
327
|
+
}
|
|
328
|
+
async function executeProjectCommand(command, options, dependencies) {
|
|
329
|
+
if (command === "list") {
|
|
330
|
+
assertOnlyOptions(options, [], "project list");
|
|
331
|
+
return requestService(dependencies, "/api/v1/projects");
|
|
332
|
+
}
|
|
333
|
+
if (command === "create") {
|
|
334
|
+
assertOnlyOptions(options, ["name", "description"], "project create");
|
|
335
|
+
return requestService(dependencies, "/api/v1/projects", {
|
|
336
|
+
method: "POST",
|
|
337
|
+
body: {
|
|
338
|
+
name: requireOption(options, "name"),
|
|
339
|
+
...(options.description === undefined
|
|
340
|
+
? {}
|
|
341
|
+
: { description: options.description }),
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
if (command === "get") {
|
|
346
|
+
assertOnlyOptions(options, ["project"], "project get");
|
|
347
|
+
return requestService(dependencies, `/api/v1/projects/${encodeURIComponent(requireOption(options, "project"))}`);
|
|
348
|
+
}
|
|
349
|
+
if (command === "update") {
|
|
350
|
+
assertOnlyOptions(options, ["project", "expectedRevision", "name", "description"], "project update");
|
|
351
|
+
if (options.name === undefined && options.description === undefined) {
|
|
352
|
+
throw new Error("project update requires --name or --description.");
|
|
353
|
+
}
|
|
354
|
+
const projectId = requireOption(options, "project");
|
|
355
|
+
return requestService(dependencies, `/api/v1/projects/${encodeURIComponent(projectId)}`, {
|
|
356
|
+
method: "PATCH",
|
|
357
|
+
body: {
|
|
358
|
+
expectedRevision: requireInteger(options, "expectedRevision", 1),
|
|
359
|
+
...(options.name === undefined ? {} : { name: options.name }),
|
|
360
|
+
...(options.description === undefined
|
|
361
|
+
? {}
|
|
362
|
+
: { description: options.description }),
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
throw new Error(projectUsage());
|
|
367
|
+
}
|
|
368
|
+
async function executeSessionCommand(command, options, dependencies) {
|
|
369
|
+
if (command === "style") {
|
|
370
|
+
assertOnlyOptions(options, ["session"], "session style");
|
|
371
|
+
return requestService(dependencies, `/api/v1/sessions/${encodeURIComponent(requireOption(options, "session"))}/style`);
|
|
372
|
+
}
|
|
373
|
+
if (command === "bind-style" || command === "unbind-style") {
|
|
374
|
+
assertOnlyOptions(options, command === "bind-style" ? ["session", "style", "expectedRevision"] : ["session", "expectedRevision"], `session ${command}`);
|
|
375
|
+
return requestService(dependencies, `/api/v1/sessions/${encodeURIComponent(requireOption(options, "session"))}/style`, {
|
|
376
|
+
method: "PATCH",
|
|
377
|
+
body: {
|
|
378
|
+
styleId: command === "bind-style" ? requireOption(options, "style") : null,
|
|
379
|
+
expectedRevision: requireInteger(options, "expectedRevision", 1),
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
if (command === "connect") {
|
|
384
|
+
assertOnlyOptions(options, ["session"], "session connect");
|
|
385
|
+
return connectSession(requireOption(options, "session"), dependencies);
|
|
386
|
+
}
|
|
387
|
+
if (command === "open") {
|
|
388
|
+
assertOnlyOptions(options, ["session"], "session open");
|
|
389
|
+
const connection = await connectSession(requireOption(options, "session"), dependencies);
|
|
390
|
+
await (dependencies.openUrl ?? openExternalUrl)(connection.ui.editUrl);
|
|
391
|
+
return {
|
|
392
|
+
version: 1,
|
|
393
|
+
kind: "session-opened",
|
|
394
|
+
sessionId: connection.session.id,
|
|
395
|
+
url: connection.ui.editUrl,
|
|
396
|
+
opened: true,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
if (command === "list") {
|
|
400
|
+
assertOnlyOptions(options, ["project", "unassigned"], "session list");
|
|
401
|
+
assertExclusiveProjectTarget(options, true);
|
|
402
|
+
const pathname = options.unassigned
|
|
403
|
+
? "/api/v1/sessions/unassigned"
|
|
404
|
+
: options.project
|
|
405
|
+
? `/api/v1/projects/${encodeURIComponent(options.project)}/sessions`
|
|
406
|
+
: "/api/v1/sessions";
|
|
407
|
+
return requestService(dependencies, pathname);
|
|
408
|
+
}
|
|
409
|
+
if (command === "create") {
|
|
410
|
+
assertOnlyOptions(options, ["type", "title", "project"], "session create");
|
|
411
|
+
return requestService(dependencies, "/api/v1/sessions", {
|
|
412
|
+
method: "POST",
|
|
413
|
+
body: {
|
|
414
|
+
sessionType: requireOption(options, "type"),
|
|
415
|
+
title: requireOption(options, "title"),
|
|
416
|
+
...(options.project === undefined ? {} : { projectId: options.project }),
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
if (command === "get") {
|
|
421
|
+
assertOnlyOptions(options, ["session"], "session get");
|
|
422
|
+
return requestService(dependencies, `/api/v1/sessions/${encodeURIComponent(requireOption(options, "session"))}`);
|
|
423
|
+
}
|
|
424
|
+
if (command === "move") {
|
|
425
|
+
assertOnlyOptions(options, ["session", "project", "unassigned", "expectedRevision"], "session move");
|
|
426
|
+
assertExclusiveProjectTarget(options, false);
|
|
427
|
+
const sessionId = requireOption(options, "session");
|
|
428
|
+
return requestService(dependencies, `/api/v1/sessions/${encodeURIComponent(sessionId)}/project`, {
|
|
429
|
+
method: "PATCH",
|
|
430
|
+
body: {
|
|
431
|
+
projectId: options.unassigned ? null : options.project,
|
|
432
|
+
expectedRevision: requireInteger(options, "expectedRevision", 1),
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
throw new Error(sessionUsage());
|
|
437
|
+
}
|
|
438
|
+
async function executeServiceCommand(command, options, dependencies) {
|
|
439
|
+
if (command !== "status")
|
|
440
|
+
throw new Error(serviceUsage());
|
|
441
|
+
assertOnlyOptions(options, [], "service status");
|
|
442
|
+
const payload = requireRecord(await requestService(dependencies, "/api/v1/health"), "service status");
|
|
443
|
+
if (payload.service !== "human2ai"
|
|
444
|
+
|| payload.status !== "ok"
|
|
445
|
+
|| !Array.isArray(payload.capabilities)
|
|
446
|
+
|| payload.capabilities.some((capability) => typeof capability !== "string")) {
|
|
447
|
+
throw invalidServiceResponse("service health metadata is invalid");
|
|
448
|
+
}
|
|
449
|
+
return {
|
|
450
|
+
version: 1,
|
|
451
|
+
kind: "service-status",
|
|
452
|
+
status: "ready",
|
|
453
|
+
apiUrl: configuredServiceUrl(dependencies).origin,
|
|
454
|
+
webUrl: configuredWebUrl(dependencies).origin,
|
|
455
|
+
capabilities: payload.capabilities,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
async function connectSession(sessionId, dependencies) {
|
|
459
|
+
let session = parseSession(await requestService(dependencies, `/api/v1/sessions/${encodeURIComponent(sessionId)}`), sessionId);
|
|
460
|
+
let currentStyle = null;
|
|
461
|
+
if (session.styleId) {
|
|
462
|
+
const state = requireRecord(await requestService(dependencies, `/api/v1/sessions/${encodeURIComponent(sessionId)}/style`), "session style");
|
|
463
|
+
session = parseSession(state.session, sessionId);
|
|
464
|
+
if (state.style !== null) {
|
|
465
|
+
const value = requireRecord(state.style, "session style entry");
|
|
466
|
+
if (value.id !== session.styleId || !Number.isInteger(value.revision)
|
|
467
|
+
|| typeof value.description !== "string" || !Array.isArray(value.referenceImages)) {
|
|
468
|
+
throw invalidServiceResponse("session style is invalid");
|
|
469
|
+
}
|
|
470
|
+
currentStyle = value;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const descriptor = captureDescriptor(session);
|
|
474
|
+
const draftsPayload = requireRecord(await requestService(dependencies, descriptor.draftsPath), "capture list");
|
|
475
|
+
if (!Array.isArray(draftsPayload.draftVersions)) {
|
|
476
|
+
throw invalidServiceResponse("capture list draftVersions is not an array");
|
|
477
|
+
}
|
|
478
|
+
const versions = draftsPayload.draftVersions.map((version) => parseCaptureVersion(version, descriptor));
|
|
479
|
+
const latest = versions.at(-1) ?? null;
|
|
480
|
+
const definition = sessionTypeDefinition(session.sessionType);
|
|
481
|
+
const webUrl = configuredWebUrl(dependencies);
|
|
482
|
+
const editUrl = new URL(definition.uiPath, webUrl);
|
|
483
|
+
editUrl.searchParams.set("session", session.id);
|
|
484
|
+
const commandPrefix = [
|
|
485
|
+
"--api-url",
|
|
486
|
+
configuredServiceUrl(dependencies).origin,
|
|
487
|
+
"--web-url",
|
|
488
|
+
webUrl.origin,
|
|
489
|
+
];
|
|
490
|
+
const sessionCommand = (command, extra = []) => [
|
|
491
|
+
...commandPrefix,
|
|
492
|
+
"session",
|
|
493
|
+
command,
|
|
494
|
+
"--session",
|
|
495
|
+
session.id,
|
|
496
|
+
...extra,
|
|
497
|
+
];
|
|
498
|
+
const captureCommand = (command, extra = []) => [
|
|
499
|
+
...commandPrefix,
|
|
500
|
+
"capture",
|
|
501
|
+
command,
|
|
502
|
+
"--session",
|
|
503
|
+
session.id,
|
|
504
|
+
...extra,
|
|
505
|
+
];
|
|
506
|
+
return {
|
|
507
|
+
version: 1,
|
|
508
|
+
kind: "session-connection",
|
|
509
|
+
service: {
|
|
510
|
+
apiUrl: configuredServiceUrl(dependencies).origin,
|
|
511
|
+
webUrl: webUrl.origin,
|
|
512
|
+
},
|
|
513
|
+
cli: { executable: "human2ai" },
|
|
514
|
+
session,
|
|
515
|
+
images: {
|
|
516
|
+
mimeTypes: ["image/png", "image/jpeg", "image/webp", "image/svg+xml"],
|
|
517
|
+
commands: {
|
|
518
|
+
upload: [...commandPrefix, "image", "upload", "--session", session.id, "--input", "<image-file>"],
|
|
519
|
+
source: [...commandPrefix, "image", "source", "--session", session.id, "--asset", "<asset-id>"],
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
style: {
|
|
523
|
+
current: currentStyle ? {
|
|
524
|
+
id: currentStyle.id, name: currentStyle.name, revision: currentStyle.revision,
|
|
525
|
+
promptSummary: currentStyle.promptSummary, description: currentStyle.description,
|
|
526
|
+
referenceImages: currentStyle.referenceImages.map((reference) => ({
|
|
527
|
+
url: new URL(`/api/v1/styles/${encodeURIComponent(currentStyle.id)}/references/${encodeURIComponent(reference.id)}/content`, configuredServiceUrl(dependencies)).toString(),
|
|
528
|
+
})),
|
|
529
|
+
} : null,
|
|
530
|
+
commands: {
|
|
531
|
+
get: sessionCommand("style"),
|
|
532
|
+
list: [...commandPrefix, "style", "list"],
|
|
533
|
+
bind: sessionCommand("bind-style", ["--style", "<style-id>", "--expected-revision", String(session.revision)]),
|
|
534
|
+
unbind: sessionCommand("unbind-style", ["--expected-revision", String(session.revision)]),
|
|
535
|
+
...(currentStyle ? {
|
|
536
|
+
save: captureCommand("save", ["--kind", descriptor.captureKind, "--input", "<document.json>",
|
|
537
|
+
"--expected-revision", String(latest?.revision ?? 0), "--style", currentStyle.id,
|
|
538
|
+
"--style-revision", String(currentStyle.revision), "--session-revision", String(session.revision)]),
|
|
539
|
+
} : {}),
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
ui: { editUrl: editUrl.toString() },
|
|
543
|
+
capture: {
|
|
544
|
+
kind: descriptor.captureKind,
|
|
545
|
+
latest: latest === null
|
|
546
|
+
? null
|
|
547
|
+
: {
|
|
548
|
+
id: latest.id,
|
|
549
|
+
revision: latest.revision,
|
|
550
|
+
fingerprint: latest.fingerprint,
|
|
551
|
+
createdAt: latest.createdAt,
|
|
552
|
+
...(latest.styleProcessing ? { styleProcessing: latest.styleProcessing } : {}),
|
|
553
|
+
},
|
|
554
|
+
commands: {
|
|
555
|
+
list: captureCommand("list"),
|
|
556
|
+
save: captureCommand("save", [
|
|
557
|
+
"--kind",
|
|
558
|
+
descriptor.captureKind,
|
|
559
|
+
"--input",
|
|
560
|
+
"<document.json>",
|
|
561
|
+
"--expected-revision",
|
|
562
|
+
String(latest?.revision ?? 0),
|
|
563
|
+
]),
|
|
564
|
+
...(latest === null
|
|
565
|
+
? {}
|
|
566
|
+
: {
|
|
567
|
+
get: captureCommand("get", [
|
|
568
|
+
"--kind",
|
|
569
|
+
descriptor.captureKind,
|
|
570
|
+
"--revision",
|
|
571
|
+
String(latest.revision),
|
|
572
|
+
]),
|
|
573
|
+
...(latest.revision <= 1
|
|
574
|
+
? {}
|
|
575
|
+
: {
|
|
576
|
+
undo: captureCommand("undo", [
|
|
577
|
+
"--kind",
|
|
578
|
+
descriptor.captureKind,
|
|
579
|
+
"--change-revision",
|
|
580
|
+
String(latest.revision),
|
|
581
|
+
"--expected-revision",
|
|
582
|
+
String(latest.revision),
|
|
583
|
+
]),
|
|
584
|
+
}),
|
|
585
|
+
}),
|
|
586
|
+
},
|
|
587
|
+
},
|
|
588
|
+
operations: [
|
|
589
|
+
{
|
|
590
|
+
id: "session.connect@1",
|
|
591
|
+
mode: "read",
|
|
592
|
+
command: sessionCommand("connect"),
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
id: "session.open@1",
|
|
596
|
+
mode: "read",
|
|
597
|
+
command: sessionCommand("open"),
|
|
598
|
+
},
|
|
599
|
+
...definition.operations.map((operation) => ({
|
|
600
|
+
...operation,
|
|
601
|
+
command: [
|
|
602
|
+
...commandPrefix,
|
|
603
|
+
...operation.command,
|
|
604
|
+
...(operation.sessionScoped ? ["--session", session.id] : []),
|
|
605
|
+
],
|
|
606
|
+
})),
|
|
607
|
+
],
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
async function executeCaptureCommand(command, options, dependencies) {
|
|
611
|
+
if (command === "list") {
|
|
612
|
+
assertOnlyOptions(options, ["session"], "capture list");
|
|
613
|
+
const descriptor = await resolveCaptureDescriptor(requireOption(options, "session"), dependencies);
|
|
614
|
+
const payload = requireRecord(await requestService(dependencies, descriptor.draftsPath), "capture list");
|
|
615
|
+
if (!Array.isArray(payload.draftVersions)) {
|
|
616
|
+
throw invalidServiceResponse("capture list draftVersions is not an array");
|
|
617
|
+
}
|
|
618
|
+
const versions = payload.draftVersions.map((version) => parseCaptureVersion(version, descriptor));
|
|
619
|
+
return {
|
|
620
|
+
version: 1,
|
|
621
|
+
kind: "capture-list",
|
|
622
|
+
sessionId: descriptor.sessionId,
|
|
623
|
+
sessionType: descriptor.sessionType,
|
|
624
|
+
captures: [{
|
|
625
|
+
captureKind: descriptor.captureKind,
|
|
626
|
+
versions: versions.map(({ id, revision, fingerprint, createdAt, styleProcessing }) => ({
|
|
627
|
+
id,
|
|
628
|
+
revision,
|
|
629
|
+
fingerprint,
|
|
630
|
+
createdAt,
|
|
631
|
+
...(styleProcessing ? { styleProcessing } : {}),
|
|
632
|
+
})),
|
|
633
|
+
}],
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
if (command === "get") {
|
|
637
|
+
assertOnlyOptions(options, ["session", "kind", "revision"], "capture get");
|
|
638
|
+
const descriptor = await resolveCaptureDescriptor(requireOption(options, "session"), dependencies);
|
|
639
|
+
assertCaptureKind(descriptor, requireOption(options, "kind"));
|
|
640
|
+
const revision = requireInteger(options, "revision", 1);
|
|
641
|
+
const version = parseCaptureVersion(await requestService(dependencies, `${descriptor.draftsPath}/${revision}`), descriptor);
|
|
642
|
+
return captureVersionResult(version, descriptor);
|
|
643
|
+
}
|
|
644
|
+
if (command === "save") {
|
|
645
|
+
assertOnlyOptions(options, ["session", "kind", "input", "expectedRevision", "style", "styleRevision", "sessionRevision"], "capture save");
|
|
646
|
+
const descriptor = await resolveCaptureDescriptor(requireOption(options, "session"), dependencies);
|
|
647
|
+
assertCaptureKind(descriptor, requireOption(options, "kind"));
|
|
648
|
+
const inputPath = path.resolve(requireOption(options, "input"));
|
|
649
|
+
const document = descriptor.validateDocument(await readJson(inputPath));
|
|
650
|
+
const version = parseCaptureVersion(await requestService(dependencies, descriptor.draftsPath, {
|
|
651
|
+
method: "POST",
|
|
652
|
+
body: {
|
|
653
|
+
expectedLatestRevision: requireInteger(options, "expectedRevision", 0),
|
|
654
|
+
draft: document,
|
|
655
|
+
...(options.style !== undefined || options.styleRevision !== undefined || options.sessionRevision !== undefined
|
|
656
|
+
? { styleProcessing: {
|
|
657
|
+
styleId: requireOption(options, "style"),
|
|
658
|
+
styleRevision: requireInteger(options, "styleRevision", 1),
|
|
659
|
+
sessionRevision: requireInteger(options, "sessionRevision", 1),
|
|
660
|
+
} }
|
|
661
|
+
: {}),
|
|
662
|
+
},
|
|
663
|
+
}), descriptor);
|
|
664
|
+
return captureVersionResult(version, descriptor);
|
|
665
|
+
}
|
|
666
|
+
if (command === "undo") {
|
|
667
|
+
assertOnlyOptions(options, ["session", "kind", "changeRevision", "expectedRevision"], "capture undo");
|
|
668
|
+
const descriptor = await resolveCaptureDescriptor(requireOption(options, "session"), dependencies);
|
|
669
|
+
assertCaptureKind(descriptor, requireOption(options, "kind"));
|
|
670
|
+
const changeRevision = requireInteger(options, "changeRevision", 1);
|
|
671
|
+
const expectedLatestRevision = requireInteger(options, "expectedRevision", 1);
|
|
672
|
+
const version = parseCaptureVersion(await requestService(dependencies, `${descriptor.draftsPath}/undo`, {
|
|
673
|
+
method: "POST",
|
|
674
|
+
body: { changeRevision, expectedLatestRevision },
|
|
675
|
+
}), descriptor);
|
|
676
|
+
return {
|
|
677
|
+
...captureVersionResult(version, descriptor),
|
|
678
|
+
change: {
|
|
679
|
+
type: "undo",
|
|
680
|
+
undoesRevision: changeRevision,
|
|
681
|
+
restoredFromRevision: changeRevision - 1,
|
|
682
|
+
},
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
throw new Error(captureUsage());
|
|
686
|
+
}
|
|
687
|
+
async function resolveCaptureDescriptor(sessionId, dependencies) {
|
|
688
|
+
const session = parseSession(await requestService(dependencies, `/api/v1/sessions/${encodeURIComponent(sessionId)}`), sessionId);
|
|
689
|
+
return captureDescriptor(session);
|
|
690
|
+
}
|
|
691
|
+
function captureDescriptor(session) {
|
|
692
|
+
if (session.sessionType === "spatial")
|
|
693
|
+
return { sessionId: session.id, sessionType: "spatial", captureKind: "spatial-draft", draftsPath: `/api/v1/sessions/${encodeURIComponent(session.id)}/spatial/drafts`, validateDocument: validateSpatialDraft };
|
|
694
|
+
if (session.sessionType === "image-composition") {
|
|
695
|
+
return {
|
|
696
|
+
sessionId: session.id,
|
|
697
|
+
sessionType: session.sessionType,
|
|
698
|
+
captureKind: "composition-draft",
|
|
699
|
+
draftsPath: compositionDraftsPath(session.id),
|
|
700
|
+
validateDocument: validateDraft,
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
if (session.sessionType === "ui-layout") {
|
|
704
|
+
return {
|
|
705
|
+
sessionId: session.id,
|
|
706
|
+
sessionType: session.sessionType,
|
|
707
|
+
captureKind: "ui-layout-draft",
|
|
708
|
+
draftsPath: uiSketchDraftsPath(session.id),
|
|
709
|
+
validateDocument: validateUiSketchDraft,
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
throw invalidServiceResponse(`unsupported session type: ${session.sessionType}`);
|
|
713
|
+
}
|
|
714
|
+
function assertCaptureKind(descriptor, requestedKind) {
|
|
715
|
+
if (requestedKind !== descriptor.captureKind) {
|
|
716
|
+
throw new Error(`Session type ${descriptor.sessionType} does not provide capture kind ${requestedKind}.`);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
function parseCaptureVersion(input, descriptor) {
|
|
720
|
+
const value = requireRecord(input, "capture version");
|
|
721
|
+
if (typeof value.id !== "string"
|
|
722
|
+
|| value.sessionId !== descriptor.sessionId
|
|
723
|
+
|| !Number.isInteger(value.revision)
|
|
724
|
+
|| typeof value.fingerprint !== "string"
|
|
725
|
+
|| typeof value.createdAt !== "string") {
|
|
726
|
+
throw invalidServiceResponse("capture version metadata is invalid");
|
|
727
|
+
}
|
|
728
|
+
let document;
|
|
729
|
+
try {
|
|
730
|
+
document = descriptor.validateDocument(value.draft);
|
|
731
|
+
}
|
|
732
|
+
catch (error) {
|
|
733
|
+
throw invalidServiceResponse(`capture document is invalid: ${errorMessage(error)}`);
|
|
734
|
+
}
|
|
735
|
+
return {
|
|
736
|
+
id: value.id,
|
|
737
|
+
sessionId: value.sessionId,
|
|
738
|
+
revision: value.revision,
|
|
739
|
+
fingerprint: value.fingerprint,
|
|
740
|
+
document,
|
|
741
|
+
createdAt: value.createdAt,
|
|
742
|
+
...(value.styleProcessing === undefined ? {} : { styleProcessing: parseStyleProcessing(value.styleProcessing) }),
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
function captureVersionResult(version, descriptor) {
|
|
746
|
+
return {
|
|
747
|
+
version: 1,
|
|
748
|
+
kind: "capture-version",
|
|
749
|
+
sessionId: version.sessionId,
|
|
750
|
+
sessionType: descriptor.sessionType,
|
|
751
|
+
captureKind: descriptor.captureKind,
|
|
752
|
+
id: version.id,
|
|
753
|
+
revision: version.revision,
|
|
754
|
+
fingerprint: version.fingerprint,
|
|
755
|
+
document: version.document,
|
|
756
|
+
createdAt: version.createdAt,
|
|
757
|
+
...(version.styleProcessing ? { styleProcessing: version.styleProcessing } : {}),
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
function parseStyleProcessing(input) {
|
|
761
|
+
const value = requireRecord(input, "style processing");
|
|
762
|
+
if (typeof value.styleId !== "string"
|
|
763
|
+
|| !Number.isInteger(value.styleRevision) || Number(value.styleRevision) < 1
|
|
764
|
+
|| !Number.isInteger(value.sourceRevision) || Number(value.sourceRevision) < 0
|
|
765
|
+
|| !Number.isInteger(value.resultRevision) || Number(value.resultRevision) < 1) {
|
|
766
|
+
throw invalidServiceResponse("style processing metadata is invalid");
|
|
767
|
+
}
|
|
768
|
+
return value;
|
|
769
|
+
}
|
|
770
|
+
async function inspectLocalDraft(options) {
|
|
771
|
+
assertOnlyOptions(options, ["draft", "preview"], "inspect with --draft");
|
|
772
|
+
const draftPath = path.resolve(requireOption(options, "draft"));
|
|
773
|
+
const draft = validateDraft(await readJson(draftPath));
|
|
774
|
+
const previewPath = options.preview ? path.resolve(options.preview) : null;
|
|
775
|
+
assertArtifactTargets([{ label: "preview", filename: previewPath }], [{ label: "draft", filename: draftPath }]);
|
|
776
|
+
if (previewPath)
|
|
777
|
+
await writeFile(previewPath, renderCompositionSvg(draft), "utf8");
|
|
778
|
+
return {
|
|
779
|
+
...inspectComposition(draft),
|
|
780
|
+
artifacts: { previewSvg: previewPath },
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
async function sessionImageSources(draft, sessionId, dependencies) {
|
|
784
|
+
const assets = new Map();
|
|
785
|
+
for (const assetId of new Set(draft.images.flatMap(image => image.assetId ? [image.assetId] : []))) {
|
|
786
|
+
const source = await requestService(dependencies, `/api/v1/sessions/${encodeURIComponent(sessionId)}/assets/${encodeURIComponent(assetId)}/content`, { imageSource: true });
|
|
787
|
+
assets.set(assetId, source);
|
|
788
|
+
}
|
|
789
|
+
return (assetId) => assets.get(assetId);
|
|
790
|
+
}
|
|
791
|
+
async function renderUiLayoutPreview(options, dependencies) {
|
|
792
|
+
assertOnlyOptions(options, ["session", "revision", "state", "output"], "ui-layout render");
|
|
793
|
+
const sessionId = requireOption(options, "session");
|
|
794
|
+
const revision = requireInteger(options, "revision", 1);
|
|
795
|
+
const outputPath = path.resolve(requireOption(options, "output"));
|
|
796
|
+
const extension = path.extname(outputPath).toLowerCase();
|
|
797
|
+
if (extension !== ".png" && extension !== ".svg")
|
|
798
|
+
throw new Error(en.uiSketch.preview.outputFormatError);
|
|
799
|
+
const descriptor = await resolveCaptureDescriptor(sessionId, dependencies);
|
|
800
|
+
assertCaptureKind(descriptor, "ui-layout-draft");
|
|
801
|
+
const version = parseCaptureVersion(await requestService(dependencies, `${descriptor.draftsPath}/${revision}`), descriptor);
|
|
802
|
+
const draft = version.document;
|
|
803
|
+
const tabs = uiSketchStateTabs(draft);
|
|
804
|
+
const stateId = options.state ?? tabs[0].id;
|
|
805
|
+
const state = tabs.find(tab => tab.id === stateId);
|
|
806
|
+
if (!state)
|
|
807
|
+
throw new Error(en.uiSketch.states.notFound.replace("{{state}}", stateId));
|
|
808
|
+
const selected = uiSketchDraftForStage(draft, state.id);
|
|
809
|
+
const sources = await sessionImageSources({ ...selected, images: selected.images.filter(image => image.visible) }, sessionId, dependencies);
|
|
810
|
+
const svg = renderUiSketchSvg(selected, sources);
|
|
811
|
+
if (extension === ".svg")
|
|
812
|
+
await writeFile(outputPath, svg, "utf8");
|
|
813
|
+
else
|
|
814
|
+
await sharp(Buffer.from(svg)).png().toFile(outputPath);
|
|
815
|
+
return {
|
|
816
|
+
version: 1, kind: "ui-layout-preview", sessionId, revision: version.revision, fingerprint: version.fingerprint, state,
|
|
817
|
+
artifact: {
|
|
818
|
+
path: outputPath, mimeType: extension === ".svg" ? "image/svg+xml" : "image/png",
|
|
819
|
+
width: Math.max(1, Math.round(selected.frame.width)), height: Math.max(1, Math.round(selected.frame.height)),
|
|
820
|
+
},
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
async function inspectSessionDraft(options, dependencies) {
|
|
824
|
+
assertOnlyOptions(options, ["session", "revision", "preview"], "inspect with --session");
|
|
825
|
+
const sessionId = requireOption(options, "session");
|
|
826
|
+
const revision = requireInteger(options, "revision", 1);
|
|
827
|
+
const version = parseDraftVersion(await requestService(dependencies, `${compositionDraftsPath(sessionId)}/${revision}`));
|
|
828
|
+
const previewPath = options.preview ? path.resolve(options.preview) : null;
|
|
829
|
+
if (previewPath) {
|
|
830
|
+
await writeFile(previewPath, renderCompositionSvg(version.draft, await sessionImageSources(version.draft, sessionId, dependencies)), "utf8");
|
|
831
|
+
}
|
|
832
|
+
return {
|
|
833
|
+
...inspectComposition(version.draft),
|
|
834
|
+
source: {
|
|
835
|
+
mode: "session",
|
|
836
|
+
sessionId: version.sessionId,
|
|
837
|
+
draftVersionId: version.id,
|
|
838
|
+
draftRevision: version.revision,
|
|
839
|
+
},
|
|
840
|
+
artifacts: { previewSvg: previewPath },
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
async function applyLocalRefinement(options) {
|
|
844
|
+
assertOnlyOptions(options, ["draft", "plan", "output", "preview"], "apply with --draft");
|
|
845
|
+
const draftPath = path.resolve(requireOption(options, "draft"));
|
|
846
|
+
const planPath = path.resolve(requireOption(options, "plan"));
|
|
847
|
+
const outputPath = options.output ? path.resolve(options.output) : null;
|
|
848
|
+
const previewPath = options.preview ? path.resolve(options.preview) : null;
|
|
849
|
+
assertArtifactTargets([
|
|
850
|
+
{ label: "output", filename: outputPath },
|
|
851
|
+
{ label: "preview", filename: previewPath },
|
|
852
|
+
], [
|
|
853
|
+
{ label: "draft", filename: draftPath },
|
|
854
|
+
{ label: "plan", filename: planPath },
|
|
855
|
+
]);
|
|
856
|
+
const draft = validateDraft(await readJson(draftPath));
|
|
857
|
+
const plan = await readJson(planPath);
|
|
858
|
+
const result = applyRefinementPlan(draft, plan);
|
|
859
|
+
const payload = {
|
|
860
|
+
...result,
|
|
861
|
+
artifacts: {
|
|
862
|
+
result: outputPath,
|
|
863
|
+
previewSvg: previewPath,
|
|
864
|
+
},
|
|
865
|
+
};
|
|
866
|
+
if (outputPath)
|
|
867
|
+
await writeJson(outputPath, payload);
|
|
868
|
+
if (previewPath) {
|
|
869
|
+
await writeFile(previewPath, renderCompositionSvg(result.refinedDraft), "utf8");
|
|
870
|
+
}
|
|
871
|
+
return payload;
|
|
872
|
+
}
|
|
873
|
+
async function applySessionRefinement(options, dependencies) {
|
|
874
|
+
assertOnlyOptions(options, ["session", "revision", "plan", "output", "preview"], "apply with --session");
|
|
875
|
+
const sessionId = requireOption(options, "session");
|
|
876
|
+
const revision = requireInteger(options, "revision", 1);
|
|
877
|
+
const planPath = path.resolve(requireOption(options, "plan"));
|
|
878
|
+
const outputPath = options.output ? path.resolve(options.output) : null;
|
|
879
|
+
const previewPath = options.preview ? path.resolve(options.preview) : null;
|
|
880
|
+
assertArtifactTargets([
|
|
881
|
+
{ label: "output", filename: outputPath },
|
|
882
|
+
{ label: "preview", filename: previewPath },
|
|
883
|
+
], [{ label: "plan", filename: planPath }]);
|
|
884
|
+
const run = parseRefinementRun(await requestService(dependencies, compositionRefinementsPath(sessionId), {
|
|
885
|
+
method: "POST",
|
|
886
|
+
body: {
|
|
887
|
+
sourceDraftRevision: revision,
|
|
888
|
+
plan: await readJson(planPath),
|
|
889
|
+
},
|
|
890
|
+
}));
|
|
891
|
+
return writeSessionRefinementArtifacts(run, options, dependencies);
|
|
892
|
+
}
|
|
893
|
+
async function writeSessionRefinementArtifacts(run, options, dependencies) {
|
|
894
|
+
const outputPath = options.output ? path.resolve(options.output) : null;
|
|
895
|
+
const previewPath = options.preview ? path.resolve(options.preview) : null;
|
|
896
|
+
assertArtifactTargets([
|
|
897
|
+
{ label: "output", filename: outputPath },
|
|
898
|
+
{ label: "preview", filename: previewPath },
|
|
899
|
+
], []);
|
|
900
|
+
const payload = {
|
|
901
|
+
...run,
|
|
902
|
+
artifacts: {
|
|
903
|
+
result: outputPath,
|
|
904
|
+
previewSvg: previewPath,
|
|
905
|
+
},
|
|
906
|
+
};
|
|
907
|
+
if (outputPath)
|
|
908
|
+
await writeJson(outputPath, payload);
|
|
909
|
+
if (previewPath) {
|
|
910
|
+
await writeFile(previewPath, renderCompositionSvg(run.result.refinedDraft, await sessionImageSources(run.result.refinedDraft, run.sessionId, dependencies)), "utf8");
|
|
911
|
+
}
|
|
912
|
+
return payload;
|
|
913
|
+
}
|
|
914
|
+
async function writeSessionReference(options, dependencies) {
|
|
915
|
+
assertOnlyOptions(options, ["session", "run", "output"], "reference");
|
|
916
|
+
const sessionId = requireOption(options, "session");
|
|
917
|
+
const runId = requireOption(options, "run");
|
|
918
|
+
const outputPath = path.resolve(requireOption(options, "output"));
|
|
919
|
+
const run = parseRefinementRun(await requestService(dependencies, `${compositionRefinementsPath(sessionId)}/${encodeURIComponent(runId)}`));
|
|
920
|
+
const draft = run.result.refinedDraft;
|
|
921
|
+
await sharp(Buffer.from(renderCompositionReferenceSvg(draft, await sessionImageSources(draft, sessionId, dependencies)))).png().toFile(outputPath);
|
|
922
|
+
return {
|
|
923
|
+
version: 1,
|
|
924
|
+
kind: "composition-reference",
|
|
925
|
+
sessionId: run.sessionId,
|
|
926
|
+
refinementRunId: run.id,
|
|
927
|
+
sourceDraftRevision: run.sourceDraftRevision,
|
|
928
|
+
sourceFingerprint: run.sourceFingerprint,
|
|
929
|
+
artifact: {
|
|
930
|
+
path: outputPath,
|
|
931
|
+
mimeType: "image/png",
|
|
932
|
+
width: draft.frame.width,
|
|
933
|
+
height: draft.frame.height,
|
|
934
|
+
},
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
function parseOptions(args) {
|
|
938
|
+
const options = {};
|
|
939
|
+
const flags = new Set([
|
|
940
|
+
"--agent",
|
|
941
|
+
"--asset",
|
|
942
|
+
"--category",
|
|
943
|
+
"--camera",
|
|
944
|
+
"--box",
|
|
945
|
+
"--view",
|
|
946
|
+
"--change-revision",
|
|
947
|
+
"--check",
|
|
948
|
+
"--creator",
|
|
949
|
+
"--description",
|
|
950
|
+
"--draft",
|
|
951
|
+
"--expected-revision",
|
|
952
|
+
"--input",
|
|
953
|
+
"--kind",
|
|
954
|
+
"--mode",
|
|
955
|
+
"--name",
|
|
956
|
+
"--output",
|
|
957
|
+
"--pass",
|
|
958
|
+
"--plan",
|
|
959
|
+
"--preview",
|
|
960
|
+
"--project",
|
|
961
|
+
"--reference",
|
|
962
|
+
"--revision",
|
|
963
|
+
"--root",
|
|
964
|
+
"--run",
|
|
965
|
+
"--session",
|
|
966
|
+
"--state",
|
|
967
|
+
"--style",
|
|
968
|
+
"--style-revision",
|
|
969
|
+
"--session-revision",
|
|
970
|
+
"--summary",
|
|
971
|
+
"--title",
|
|
972
|
+
"--type",
|
|
973
|
+
"--unassigned",
|
|
974
|
+
]);
|
|
975
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
976
|
+
const flag = args[index];
|
|
977
|
+
if (!flags.has(flag))
|
|
978
|
+
throw new Error(`Unknown option: ${flag}.`);
|
|
979
|
+
const key = flagToOptionKey(flag);
|
|
980
|
+
if (options[key] !== undefined)
|
|
981
|
+
throw new Error(`Duplicate option: ${flag}.`);
|
|
982
|
+
if (flag === "--check") {
|
|
983
|
+
options.check = true;
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
if (flag === "--unassigned") {
|
|
987
|
+
options.unassigned = true;
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
const value = args[++index];
|
|
991
|
+
if (!value || value.startsWith("--"))
|
|
992
|
+
throw new Error(`${flag} requires a value.`);
|
|
993
|
+
options[key] = value;
|
|
994
|
+
}
|
|
995
|
+
return options;
|
|
996
|
+
}
|
|
997
|
+
function flagToOptionKey(flag) {
|
|
998
|
+
if (flag === "--style-revision")
|
|
999
|
+
return "styleRevision";
|
|
1000
|
+
if (flag === "--session-revision")
|
|
1001
|
+
return "sessionRevision";
|
|
1002
|
+
if (flag === "--change-revision")
|
|
1003
|
+
return "changeRevision";
|
|
1004
|
+
if (flag === "--expected-revision")
|
|
1005
|
+
return "expectedRevision";
|
|
1006
|
+
return flag.slice(2);
|
|
1007
|
+
}
|
|
1008
|
+
function requireOption(options, key) {
|
|
1009
|
+
const value = options[key];
|
|
1010
|
+
if (typeof value !== "string" || !value) {
|
|
1011
|
+
throw new Error(`--${optionKeyToFlag(key)} is required.`);
|
|
1012
|
+
}
|
|
1013
|
+
return value;
|
|
1014
|
+
}
|
|
1015
|
+
function requireInteger(options, key, minimum) {
|
|
1016
|
+
const value = requireOption(options, key);
|
|
1017
|
+
if (!/^(0|[1-9][0-9]*)$/.test(value) || Number(value) < minimum) {
|
|
1018
|
+
throw new Error(`--${optionKeyToFlag(key)} must be an integer of at least ${minimum}.`);
|
|
1019
|
+
}
|
|
1020
|
+
return Number(value);
|
|
1021
|
+
}
|
|
1022
|
+
function optionKeyToFlag(key) {
|
|
1023
|
+
if (key === "styleRevision")
|
|
1024
|
+
return "style-revision";
|
|
1025
|
+
if (key === "sessionRevision")
|
|
1026
|
+
return "session-revision";
|
|
1027
|
+
if (key === "changeRevision")
|
|
1028
|
+
return "change-revision";
|
|
1029
|
+
return key === "expectedRevision" ? "expected-revision" : key;
|
|
1030
|
+
}
|
|
1031
|
+
function parseGlobalOptions(args) {
|
|
1032
|
+
const options = {};
|
|
1033
|
+
let index = 0;
|
|
1034
|
+
while (args[index] === "--api-url" || args[index] === "--web-url") {
|
|
1035
|
+
const flag = args[index];
|
|
1036
|
+
const key = flag === "--api-url" ? "apiUrl" : "webUrl";
|
|
1037
|
+
if (options[key] !== undefined)
|
|
1038
|
+
throw new Error(`Duplicate option: ${flag}.`);
|
|
1039
|
+
const value = args[index + 1];
|
|
1040
|
+
if (!value || value.startsWith("--"))
|
|
1041
|
+
throw new Error(`${flag} requires a value.`);
|
|
1042
|
+
configuredHttpUrl(value, flag);
|
|
1043
|
+
options[key] = value;
|
|
1044
|
+
index += 2;
|
|
1045
|
+
}
|
|
1046
|
+
return { options, commandArgs: args.slice(index) };
|
|
1047
|
+
}
|
|
1048
|
+
function assertOnlyOptions(options, allowed, command) {
|
|
1049
|
+
const unexpected = Object.keys(options).filter((key) => options[key] !== undefined && !allowed.includes(key));
|
|
1050
|
+
if (unexpected.length > 0) {
|
|
1051
|
+
throw new Error(`${command} does not accept ${unexpected.map((key) => `--${optionKeyToFlag(key)}`).join(", ")}.`);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
function assertExclusiveProjectTarget(options, optional) {
|
|
1055
|
+
if (options.project && options.unassigned) {
|
|
1056
|
+
throw new Error("--project and --unassigned cannot be used together.");
|
|
1057
|
+
}
|
|
1058
|
+
if (!optional && !options.project && !options.unassigned) {
|
|
1059
|
+
throw new Error("Either --project or --unassigned is required.");
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
function assertArtifactTargets(artifacts, inputs) {
|
|
1063
|
+
const presentArtifacts = artifacts.filter((artifact) => Boolean(artifact.filename));
|
|
1064
|
+
for (const artifact of presentArtifacts) {
|
|
1065
|
+
const protectedInput = inputs.find((input) => input.filename === artifact.filename);
|
|
1066
|
+
if (protectedInput) {
|
|
1067
|
+
throw new Error(`--${artifact.label} must not overwrite the ${protectedInput.label} file.`);
|
|
1068
|
+
}
|
|
1069
|
+
const duplicate = presentArtifacts.find((candidate) => candidate !== artifact && candidate.filename === artifact.filename);
|
|
1070
|
+
if (duplicate) {
|
|
1071
|
+
throw new Error(`--${artifact.label} and --${duplicate.label} must use different files.`);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
async function requestService(dependencies, pathname, request = {}) {
|
|
1076
|
+
const url = new URL(pathname, configuredServiceUrl(dependencies));
|
|
1077
|
+
const fetcher = dependencies.fetch ?? globalThis.fetch;
|
|
1078
|
+
let response;
|
|
1079
|
+
try {
|
|
1080
|
+
response = await fetcher(url, {
|
|
1081
|
+
method: request.method ?? "GET",
|
|
1082
|
+
headers: request.body === undefined ? undefined : { "content-type": "application/json" },
|
|
1083
|
+
body: request.body === undefined ? undefined : JSON.stringify(request.body),
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
catch (error) {
|
|
1087
|
+
throw new CliServiceError("SERVICE_UNAVAILABLE", `Cannot connect to Human2AI service at ${url.origin}: ${errorMessage(error)}`, null);
|
|
1088
|
+
}
|
|
1089
|
+
if (response.ok && request.imageSource) {
|
|
1090
|
+
const mime = response.headers.get("content-type")?.split(";")[0].trim();
|
|
1091
|
+
if (!mime || !["image/png", "image/jpeg", "image/webp", "image/svg+xml"].includes(mime))
|
|
1092
|
+
throw invalidServiceResponse("Expected an image asset");
|
|
1093
|
+
return `data:${mime};base64,${Buffer.from(await response.arrayBuffer()).toString("base64")}`;
|
|
1094
|
+
}
|
|
1095
|
+
if (response.ok && request.png) {
|
|
1096
|
+
if (response.headers.get("content-type")?.split(";")[0].trim() !== "image/png")
|
|
1097
|
+
throw invalidServiceResponse("Expected a camera PNG");
|
|
1098
|
+
return Buffer.from(await response.arrayBuffer());
|
|
1099
|
+
}
|
|
1100
|
+
if (response.ok && request.svgSource) {
|
|
1101
|
+
if (response.headers.get("content-type")?.split(";")[0].trim() !== "image/svg+xml") {
|
|
1102
|
+
throw new CliServiceError("INVALID_IMAGE_ASSET", "Source is available only for SVG images.", 400);
|
|
1103
|
+
}
|
|
1104
|
+
return response.text();
|
|
1105
|
+
}
|
|
1106
|
+
const payload = response.status === 204 ? null : await readResponseJson(response);
|
|
1107
|
+
if (!response.ok) {
|
|
1108
|
+
const error = isRecord(payload) ? payload : {};
|
|
1109
|
+
const code = typeof error.code === "string" ? error.code : `HTTP_${response.status}`;
|
|
1110
|
+
const message = typeof error.message === "string"
|
|
1111
|
+
? error.message
|
|
1112
|
+
: `Human2AI service request failed with HTTP ${response.status}`;
|
|
1113
|
+
const details = Object.fromEntries(Object.entries(error).filter(([key]) => key !== "code" && key !== "message"));
|
|
1114
|
+
throw new CliServiceError(code, message, response.status, details);
|
|
1115
|
+
}
|
|
1116
|
+
return payload;
|
|
1117
|
+
}
|
|
1118
|
+
async function requestImageService(dependencies, pathname, body) {
|
|
1119
|
+
const url = new URL(pathname, configuredServiceUrl(dependencies));
|
|
1120
|
+
const fetcher = dependencies.fetch ?? globalThis.fetch;
|
|
1121
|
+
let response;
|
|
1122
|
+
try {
|
|
1123
|
+
response = await fetcher(url, {
|
|
1124
|
+
method: "POST",
|
|
1125
|
+
headers: { "content-type": "application/octet-stream" },
|
|
1126
|
+
body,
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
catch (error) {
|
|
1130
|
+
throw new CliServiceError("SERVICE_UNAVAILABLE", `Cannot connect to Human2AI service at ${url.origin}: ${errorMessage(error)}`, null);
|
|
1131
|
+
}
|
|
1132
|
+
const payload = await readResponseJson(response);
|
|
1133
|
+
if (!response.ok) {
|
|
1134
|
+
const error = isRecord(payload) ? payload : {};
|
|
1135
|
+
throw new CliServiceError(typeof error.code === "string" ? error.code : `HTTP_${response.status}`, typeof error.message === "string"
|
|
1136
|
+
? error.message
|
|
1137
|
+
: `Human2AI service request failed with HTTP ${response.status}`, response.status, Object.fromEntries(Object.entries(error).filter(([key]) => key !== "code" && key !== "message")));
|
|
1138
|
+
}
|
|
1139
|
+
return payload;
|
|
1140
|
+
}
|
|
1141
|
+
function configuredServiceUrl(dependencies) {
|
|
1142
|
+
const integration = dependencies.serviceUrl || process.env.HUMAN2AI_SERVER_URL
|
|
1143
|
+
? null
|
|
1144
|
+
: readIntegrationManifestSync(integrationStartDirectory(dependencies.cwd));
|
|
1145
|
+
return configuredHttpUrl(dependencies.serviceUrl
|
|
1146
|
+
?? process.env.HUMAN2AI_SERVER_URL
|
|
1147
|
+
?? integration?.service.apiUrl
|
|
1148
|
+
?? DEFAULT_SERVICE_URL, "HUMAN2AI_SERVER_URL");
|
|
1149
|
+
}
|
|
1150
|
+
function configuredWebUrl(dependencies) {
|
|
1151
|
+
const integration = dependencies.webUrl || process.env.HUMAN2AI_WEB_URL
|
|
1152
|
+
? null
|
|
1153
|
+
: readIntegrationManifestSync(integrationStartDirectory(dependencies.cwd));
|
|
1154
|
+
return configuredHttpUrl(dependencies.webUrl
|
|
1155
|
+
?? process.env.HUMAN2AI_WEB_URL
|
|
1156
|
+
?? integration?.service.webUrl
|
|
1157
|
+
?? configuredServiceUrl(dependencies).origin, "HUMAN2AI_WEB_URL");
|
|
1158
|
+
}
|
|
1159
|
+
function configuredHttpUrl(value, label) {
|
|
1160
|
+
let url;
|
|
1161
|
+
try {
|
|
1162
|
+
url = new URL(value);
|
|
1163
|
+
}
|
|
1164
|
+
catch {
|
|
1165
|
+
throw new Error(`Invalid ${label}: ${value}`);
|
|
1166
|
+
}
|
|
1167
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
1168
|
+
throw new Error(`${label} must use http or https.`);
|
|
1169
|
+
}
|
|
1170
|
+
return url;
|
|
1171
|
+
}
|
|
1172
|
+
async function readResponseJson(response) {
|
|
1173
|
+
try {
|
|
1174
|
+
return await response.json();
|
|
1175
|
+
}
|
|
1176
|
+
catch {
|
|
1177
|
+
throw new CliServiceError("INVALID_SERVICE_RESPONSE", `Human2AI service returned invalid JSON with HTTP ${response.status}`, response.status);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
function parseDraftVersion(input) {
|
|
1181
|
+
const value = requireRecord(input, "draft version");
|
|
1182
|
+
if (typeof value.id !== "string" ||
|
|
1183
|
+
typeof value.sessionId !== "string" ||
|
|
1184
|
+
!Number.isInteger(value.revision) ||
|
|
1185
|
+
typeof value.fingerprint !== "string" ||
|
|
1186
|
+
typeof value.createdAt !== "string") {
|
|
1187
|
+
throw invalidServiceResponse("draft version metadata is invalid");
|
|
1188
|
+
}
|
|
1189
|
+
return {
|
|
1190
|
+
id: value.id,
|
|
1191
|
+
sessionId: value.sessionId,
|
|
1192
|
+
revision: value.revision,
|
|
1193
|
+
fingerprint: value.fingerprint,
|
|
1194
|
+
draft: validateServiceDraft(value.draft),
|
|
1195
|
+
createdAt: value.createdAt,
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
function parseSession(input, expectedId) {
|
|
1199
|
+
const value = requireRecord(input, "session");
|
|
1200
|
+
if (value.id !== expectedId
|
|
1201
|
+
|| (value.projectId !== null && typeof value.projectId !== "string")
|
|
1202
|
+
|| (value.styleId != null && typeof value.styleId !== "string")
|
|
1203
|
+
|| typeof value.sessionType !== "string"
|
|
1204
|
+
|| !SESSION_TYPES.includes(value.sessionType)
|
|
1205
|
+
|| typeof value.title !== "string"
|
|
1206
|
+
|| !["draft", "interpreted", "approved", "exported"].includes(String(value.lifecycleStage))
|
|
1207
|
+
|| !Number.isInteger(value.revision)
|
|
1208
|
+
|| typeof value.createdAt !== "string"
|
|
1209
|
+
|| typeof value.updatedAt !== "string") {
|
|
1210
|
+
throw invalidServiceResponse("session metadata is invalid");
|
|
1211
|
+
}
|
|
1212
|
+
return { ...value, styleId: value.styleId ?? null };
|
|
1213
|
+
}
|
|
1214
|
+
function parseRefinementRun(input) {
|
|
1215
|
+
const value = requireRecord(input, "refinement run");
|
|
1216
|
+
const result = requireRecord(value.result, "refinement result");
|
|
1217
|
+
if (typeof value.id !== "string" ||
|
|
1218
|
+
typeof value.sessionId !== "string" ||
|
|
1219
|
+
typeof value.sourceDraftVersionId !== "string" ||
|
|
1220
|
+
!Number.isInteger(value.sourceDraftRevision) ||
|
|
1221
|
+
typeof value.sourceFingerprint !== "string" ||
|
|
1222
|
+
typeof value.createdAt !== "string") {
|
|
1223
|
+
throw invalidServiceResponse("refinement run metadata is invalid");
|
|
1224
|
+
}
|
|
1225
|
+
return {
|
|
1226
|
+
id: value.id,
|
|
1227
|
+
sessionId: value.sessionId,
|
|
1228
|
+
sourceDraftVersionId: value.sourceDraftVersionId,
|
|
1229
|
+
sourceDraftRevision: value.sourceDraftRevision,
|
|
1230
|
+
sourceFingerprint: value.sourceFingerprint,
|
|
1231
|
+
plan: value.plan,
|
|
1232
|
+
result: {
|
|
1233
|
+
...result,
|
|
1234
|
+
refinedDraft: validateServiceDraft(result.refinedDraft),
|
|
1235
|
+
},
|
|
1236
|
+
createdAt: value.createdAt,
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
function validateServiceDraft(input) {
|
|
1240
|
+
try {
|
|
1241
|
+
return validateDraft(input);
|
|
1242
|
+
}
|
|
1243
|
+
catch (error) {
|
|
1244
|
+
throw invalidServiceResponse(`composition draft is invalid: ${errorMessage(error)}`);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
function requireRecord(input, label) {
|
|
1248
|
+
if (!isRecord(input))
|
|
1249
|
+
throw invalidServiceResponse(`${label} is not an object`);
|
|
1250
|
+
return input;
|
|
1251
|
+
}
|
|
1252
|
+
function invalidServiceResponse(message) {
|
|
1253
|
+
return new CliServiceError("INVALID_SERVICE_RESPONSE", message, null);
|
|
1254
|
+
}
|
|
1255
|
+
function isRecord(input) {
|
|
1256
|
+
return Boolean(input) && typeof input === "object" && !Array.isArray(input);
|
|
1257
|
+
}
|
|
1258
|
+
function compositionDraftsPath(sessionId) {
|
|
1259
|
+
return `/api/v1/sessions/${encodeURIComponent(sessionId)}/composition/drafts`;
|
|
1260
|
+
}
|
|
1261
|
+
function uiSketchDraftsPath(sessionId) {
|
|
1262
|
+
return `/api/v1/sessions/${encodeURIComponent(sessionId)}/ui-sketch/drafts`;
|
|
1263
|
+
}
|
|
1264
|
+
function compositionRefinementsPath(sessionId) {
|
|
1265
|
+
return `/api/v1/sessions/${encodeURIComponent(sessionId)}/composition/refinements`;
|
|
1266
|
+
}
|
|
1267
|
+
async function readJson(filename) {
|
|
1268
|
+
return JSON.parse(await readFile(path.resolve(filename), "utf8"));
|
|
1269
|
+
}
|
|
1270
|
+
async function writeJson(filename, value) {
|
|
1271
|
+
await writeFile(path.resolve(filename), `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
1272
|
+
}
|
|
1273
|
+
function errorMessage(error) {
|
|
1274
|
+
return error instanceof Error ? error.message : String(error);
|
|
1275
|
+
}
|
|
1276
|
+
function usage() {
|
|
1277
|
+
return [
|
|
1278
|
+
"Usage:",
|
|
1279
|
+
" human2ai [--api-url <url>] [--web-url <url>] <command>",
|
|
1280
|
+
" human2ai web",
|
|
1281
|
+
...serviceUsage().split("\n").slice(1),
|
|
1282
|
+
...integrationUsage().split("\n").slice(1),
|
|
1283
|
+
...projectUsage().split("\n").slice(1),
|
|
1284
|
+
...sessionUsage().split("\n").slice(1),
|
|
1285
|
+
...captureUsage().split("\n").slice(1),
|
|
1286
|
+
...styleUsage().split("\n").slice(1),
|
|
1287
|
+
...imageUsage().split("\n").slice(1),
|
|
1288
|
+
" human2ai ui-layout standardize --input <document.json> --output <standardized.json> [--plan <alignment-plan.json>]",
|
|
1289
|
+
" human2ai ui-layout render --session <id> --revision <n> [--state <state-id>] --output <preview.png|preview.svg>",
|
|
1290
|
+
" human2ai spatial methods",
|
|
1291
|
+
" human2ai spatial inspect --session <id> --revision <n> [--output <inspection.json>]",
|
|
1292
|
+
" human2ai spatial apply --session <id> --revision <n> --input <operations.json> [--output <version.json>]",
|
|
1293
|
+
" human2ai spatial render --session <id> --revision <n> --camera <id> [--pass color|structure|depth|skeleton] --output <camera.png>",
|
|
1294
|
+
" human2ai spatial render --session <id> --revision <n> --box <id> [--view sheet|front|back|left|right|top|bottom] [--pass color|structure|depth|skeleton] --output <reference.png>",
|
|
1295
|
+
" human2ai composition methods",
|
|
1296
|
+
" human2ai composition save --session <id> --draft <draft.json> --expected-revision <n>",
|
|
1297
|
+
" human2ai composition drafts --session <id>",
|
|
1298
|
+
" human2ai composition inspect --draft <draft.json> [--preview <preview.svg>]",
|
|
1299
|
+
" human2ai composition inspect --session <id> --revision <n> [--preview <preview.svg>]",
|
|
1300
|
+
" human2ai composition apply --draft <draft.json> --plan <plan.json> [--output <result.json>] [--preview <preview.svg>]",
|
|
1301
|
+
" human2ai composition apply --session <id> --revision <n> --plan <plan.json> [--output <run.json>] [--preview <preview.svg>]",
|
|
1302
|
+
" human2ai composition refinements --session <id>",
|
|
1303
|
+
" human2ai composition refinement --session <id> --run <id> [--output <run.json>] [--preview <preview.svg>]",
|
|
1304
|
+
" human2ai composition reference --session <id> --run <id> --output <reference.png>",
|
|
1305
|
+
].join("\n");
|
|
1306
|
+
}
|
|
1307
|
+
function imageUsage() {
|
|
1308
|
+
return [
|
|
1309
|
+
"Image usage:",
|
|
1310
|
+
" human2ai image upload --session <id> --input <image-file>",
|
|
1311
|
+
" human2ai image source --session <id> --asset <id> [--output <image.svg>]",
|
|
1312
|
+
].join("\n");
|
|
1313
|
+
}
|
|
1314
|
+
function styleUsage() {
|
|
1315
|
+
return [
|
|
1316
|
+
"Style usage:",
|
|
1317
|
+
" human2ai style list [--category <visual|ui>] [--creator <user|agent>]",
|
|
1318
|
+
" human2ai style get --style <id>",
|
|
1319
|
+
" human2ai style create --name <name> --category <visual|ui> --description <text> [--summary <sentence>]",
|
|
1320
|
+
" human2ai style update --style <id> --expected-revision <n> [--name <name>] [--category <visual|ui>] [--description <text>] [--summary <sentence>]",
|
|
1321
|
+
" human2ai style add-reference --style <id> --input <image> --expected-revision <n>",
|
|
1322
|
+
" human2ai style remove-reference --style <id> --reference <id> --expected-revision <n>",
|
|
1323
|
+
" human2ai style delete --style <id> --expected-revision <n>",
|
|
1324
|
+
].join("\n");
|
|
1325
|
+
}
|
|
1326
|
+
function captureUsage() {
|
|
1327
|
+
return [
|
|
1328
|
+
"Capture usage:",
|
|
1329
|
+
" human2ai capture list --session <id>",
|
|
1330
|
+
" human2ai capture get --session <id> --kind <kind> --revision <n>",
|
|
1331
|
+
" human2ai capture save --session <id> --kind <kind> --input <document.json> --expected-revision <n> [--style <id> --style-revision <n> --session-revision <n>]",
|
|
1332
|
+
" human2ai capture undo --session <id> --kind <kind> --change-revision <n> --expected-revision <n>",
|
|
1333
|
+
].join("\n");
|
|
1334
|
+
}
|
|
1335
|
+
function serviceUsage() {
|
|
1336
|
+
return [
|
|
1337
|
+
"Service usage:",
|
|
1338
|
+
" human2ai service status",
|
|
1339
|
+
].join("\n");
|
|
1340
|
+
}
|
|
1341
|
+
function projectUsage() {
|
|
1342
|
+
return [
|
|
1343
|
+
"Project usage:",
|
|
1344
|
+
" human2ai project list",
|
|
1345
|
+
" human2ai project create --name <name> [--description <text>]",
|
|
1346
|
+
" human2ai project get --project <id>",
|
|
1347
|
+
" human2ai project update --project <id> --expected-revision <n> [--name <name>] [--description <text>]",
|
|
1348
|
+
].join("\n");
|
|
1349
|
+
}
|
|
1350
|
+
function sessionUsage() {
|
|
1351
|
+
return [
|
|
1352
|
+
"Session usage:",
|
|
1353
|
+
" human2ai session list [--project <id> | --unassigned]",
|
|
1354
|
+
" human2ai session create --type <image-composition|ui-layout|spatial> --title <title> [--project <id>]",
|
|
1355
|
+
" human2ai session get --session <id>",
|
|
1356
|
+
" human2ai session connect --session <id>",
|
|
1357
|
+
" human2ai session open --session <id>",
|
|
1358
|
+
" human2ai session style --session <id>",
|
|
1359
|
+
" human2ai session bind-style --session <id> --style <id> --expected-revision <n>",
|
|
1360
|
+
" human2ai session unbind-style --session <id> --expected-revision <n>",
|
|
1361
|
+
" human2ai session move --session <id> (--project <id> | --unassigned) --expected-revision <n>",
|
|
1362
|
+
].join("\n");
|
|
1363
|
+
}
|
|
1364
|
+
async function main() {
|
|
1365
|
+
try {
|
|
1366
|
+
const invocation = parseGlobalOptions(process.argv.slice(2));
|
|
1367
|
+
if (invocation.commandArgs[0] === "web") {
|
|
1368
|
+
if (invocation.options.apiUrl || invocation.options.webUrl) {
|
|
1369
|
+
throw new Error("human2ai web does not accept --api-url or --web-url.");
|
|
1370
|
+
}
|
|
1371
|
+
await web(invocation.commandArgs.slice(1));
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
const result = await executeCli(process.argv.slice(2));
|
|
1375
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
1376
|
+
}
|
|
1377
|
+
catch (error) {
|
|
1378
|
+
const payload = {
|
|
1379
|
+
error: {
|
|
1380
|
+
code: error instanceof CliServiceError || error instanceof CliIntegrationError
|
|
1381
|
+
? error.code
|
|
1382
|
+
: error instanceof RefinementConstraintError
|
|
1383
|
+
? "REFINEMENT_CONSTRAINT"
|
|
1384
|
+
: "INVALID_REQUEST",
|
|
1385
|
+
message: errorMessage(error),
|
|
1386
|
+
...(error instanceof CliServiceError || error instanceof CliIntegrationError
|
|
1387
|
+
? error.details
|
|
1388
|
+
: {}),
|
|
1389
|
+
...(error instanceof RefinementConstraintError ? { audit: error.audit } : {}),
|
|
1390
|
+
},
|
|
1391
|
+
};
|
|
1392
|
+
process.stderr.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
1393
|
+
process.exitCode = 1;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
async function openExternalUrl(url) {
|
|
1397
|
+
const target = configuredHttpUrl(url, "session UI URL").toString();
|
|
1398
|
+
const command = process.platform === "darwin"
|
|
1399
|
+
? { executable: "open", args: [target] }
|
|
1400
|
+
: process.platform === "win32"
|
|
1401
|
+
? { executable: "cmd", args: ["/c", "start", "", target] }
|
|
1402
|
+
: { executable: "xdg-open", args: [target] };
|
|
1403
|
+
await new Promise((resolve, reject) => {
|
|
1404
|
+
const child = spawn(command.executable, command.args, {
|
|
1405
|
+
detached: true,
|
|
1406
|
+
stdio: "ignore",
|
|
1407
|
+
});
|
|
1408
|
+
child.once("error", reject);
|
|
1409
|
+
child.once("spawn", () => {
|
|
1410
|
+
child.unref();
|
|
1411
|
+
resolve();
|
|
1412
|
+
});
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
async function web(args) {
|
|
1416
|
+
if (args.length > 0)
|
|
1417
|
+
throw new Error(usage());
|
|
1418
|
+
const { startHuman2AiWeb } = await import("../server/runtime.js");
|
|
1419
|
+
const running = await startHuman2AiWeb();
|
|
1420
|
+
if (running.status === "already-running") {
|
|
1421
|
+
process.stdout.write(`${JSON.stringify({ status: running.status, url: running.url }, null, 2)}\n`);
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
process.stdout.write(`${JSON.stringify({ status: "listening", url: running.url }, null, 2)}\n`);
|
|
1425
|
+
let closing = false;
|
|
1426
|
+
const close = async () => {
|
|
1427
|
+
if (closing)
|
|
1428
|
+
return;
|
|
1429
|
+
closing = true;
|
|
1430
|
+
try {
|
|
1431
|
+
await running.server.close();
|
|
1432
|
+
}
|
|
1433
|
+
catch (error) {
|
|
1434
|
+
process.stderr.write(`${JSON.stringify({ error: { code: "SHUTDOWN_FAILED", message: errorMessage(error) } }, null, 2)}\n`);
|
|
1435
|
+
process.exitCode = 1;
|
|
1436
|
+
}
|
|
1437
|
+
};
|
|
1438
|
+
process.once("SIGINT", () => void close());
|
|
1439
|
+
process.once("SIGTERM", () => void close());
|
|
1440
|
+
}
|
|
1441
|
+
if (isMainModule()) {
|
|
1442
|
+
await main();
|
|
1443
|
+
}
|
|
1444
|
+
export function isMainModule(entryPath = process.argv[1]) {
|
|
1445
|
+
if (!entryPath)
|
|
1446
|
+
return false;
|
|
1447
|
+
try {
|
|
1448
|
+
return (realpathSync(path.resolve(entryPath)) ===
|
|
1449
|
+
realpathSync(fileURLToPath(import.meta.url)));
|
|
1450
|
+
}
|
|
1451
|
+
catch {
|
|
1452
|
+
return false;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
//# sourceMappingURL=main.js.map
|