@howells/motif-sdk 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,16 +8,22 @@ Public Node SDK for Motif fal.ai generation, editing, utility tools, and model m
8
8
  npm install @howells/motif-sdk
9
9
  ```
10
10
 
11
- ## Usage
11
+ ## Generate Images
12
12
 
13
13
  ```ts
14
- import { MotifServer, FAL_TOOLS, MODELS } from "@howells/motif-sdk";
14
+ import { MotifServer } from "@howells/motif-sdk";
15
15
 
16
- const motif = new MotifServer(process.env.FAL_KEY!);
16
+ const motif = new MotifServer({
17
+ apiKey: process.env.FAL_KEY!,
18
+ retries: 3,
19
+ timeout: 120_000,
20
+ });
17
21
 
18
22
  const result = await motif.generate({
19
23
  model: "banana2",
20
24
  prompt: "editorial product photo",
25
+ resolution: "2K",
26
+ enableGoogleSearch: true,
21
27
  ephemeral: true,
22
28
  });
23
29
 
@@ -28,13 +34,88 @@ if (result.isErr()) {
28
34
  console.log(result.value.images[0]?.url);
29
35
  ```
30
36
 
37
+ Every async SDK method returns `Result<T, MotifError>` from `neverthrow`. Methods do not throw for fal request failures; check `isErr()` / `isOk()`.
38
+
39
+ ## Dry-Run Request Bodies
40
+
41
+ Use `buildGenerateBody` or `motif.buildRequestBody()` when you need the exact fal endpoint and request body without making an API call.
42
+
43
+ ```ts
44
+ import { buildGenerateBody } from "@howells/motif-sdk";
45
+
46
+ const preview = buildGenerateBody({
47
+ model: "gpt2",
48
+ prompt: "change the wall color",
49
+ editImageUrls: ["https://example.com/interior.png"],
50
+ imageSize: "1536x1024",
51
+ maskImageUrl: "https://example.com/wall-mask.png",
52
+ quality: "auto",
53
+ syncMode: true,
54
+ });
55
+
56
+ console.log(preview.endpoint);
57
+ console.log(preview.body);
58
+ ```
59
+
60
+ ## Queue, Upload, and Cleanup
61
+
62
+ ```ts
63
+ const job = await motif.submitGeneration({
64
+ model: "gpt2",
65
+ prompt: "gallery poster",
66
+ });
67
+
68
+ if (job.isOk()) {
69
+ const status = await motif.getJobStatus(job.value.endpoint, job.value.requestId);
70
+ const completed = await motif.getJobResult(job.value.endpoint, job.value.requestId);
71
+ }
72
+
73
+ const uploaded = await motif.uploadToFalCdn(fileBytes, {
74
+ contentType: "image/png",
75
+ fileName: "reference.png",
76
+ });
77
+
78
+ const deleted = await motif.deletePayloads("fal-request-id");
79
+ ```
80
+
81
+ ## Utility Tools and Video
82
+
83
+ ```ts
84
+ const mask = await motif.runTool({
85
+ tool: "sam3-image",
86
+ input: "https://example.com/input.png",
87
+ options: { prompt: "shoe", max_masks: 2 },
88
+ });
89
+
90
+ const videoJob = await motif.submitVideo({
91
+ imageUrl: "https://example.com/frame.png",
92
+ prompt: "slow cinematic push-in",
93
+ duration: 5,
94
+ generateAudio: false,
95
+ });
96
+ ```
97
+
31
98
  ## Main Exports
32
99
 
33
100
  - `MotifServer` - Result-returning fal client for generation, queue jobs, upload, utility tools, and payload deletion.
34
101
  - `buildGenerateBody` - Pure fal request normalization for dry runs and tests.
35
- - `MODELS` - Motif model aliases, fal endpoints, capabilities, pricing, and benchmarks.
36
- - `FAL_TOOLS` - Normalized fal utility endpoints such as SAM, depth, upscaling, moderation, and background removal.
102
+ - `MODELS`, `GENERATION_MODELS`, `UTILITY_MODELS`, `VIDEO_MODELS` - Motif model aliases, fal endpoints, capabilities, pricing, and benchmarks.
103
+ - `FAL_TOOLS`, `FAL_TOOL_IDS`, `buildFalToolRequest`, `isFalToolId` - Normalized fal utility endpoints such as SAM, depth, upscaling, moderation, and background removal.
104
+ - `ASPECT_RATIOS`, `RESOLUTIONS`, `FORMAT_PRESETS`, `aspectToGptSize`, `aspectToFalImageSize` - Shared sizing metadata and normalization helpers.
105
+ - `IMAGE_TEXT_TO_IMAGE_TOP_20`, `IMAGE_EDITING_TOP_20`, `VIDEO_TEXT_TO_VIDEO_TOP_15`, `VIDEO_IMAGE_TO_VIDEO_TOP_15` - Bundled Artificial Analysis snapshots.
106
+ - `estimateCost`, `estimateVideoCost` - Local cost estimates used by CLI dry runs and SDK previews.
37
107
  - `getFalKeyFromEnv` - `@howells/envy` backed `FAL_KEY` parsing.
108
+ - Re-exported `neverthrow` helpers: `ok`, `err`, `Result`, `ResultAsync`.
109
+
110
+ ## Common Types
111
+
112
+ The package exports public types for generation, processing, queue, metadata, and utility tools:
113
+
114
+ - `GenerateOptions`, `MotifResponse`, `MotifImage`
115
+ - `UpscaleOptions`, `RemoveBackgroundOptions`, `VideoOptions`, `VideoResponse`
116
+ - `QueuedJob`, `JobStatus`, `MotifServerConfig`
117
+ - `ModelConfig`, `AspectRatio`, `Resolution`, `ImageSize`, `ImageQuality`, `BackgroundMode`, `ThinkingLevel`
118
+ - `FalToolConfig`, `FalToolId`, `FalToolRequest`, `FalToolRunOptions`
38
119
 
39
120
  ## Testing
40
121
 
@@ -48,4 +129,3 @@ Live fal canaries are opt-in:
48
129
  ```bash
49
130
  RUN_FAL_CANARY=1 pnpm --filter @howells/motif-sdk test -- tests/fal-canary.test.ts
50
131
  ```
51
-
package/dist/index.cjs CHANGED
@@ -21,6 +21,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  ASPECT_RATIOS: () => ASPECT_RATIOS,
24
+ CREATIVE_FIELDS: () => CREATIVE_FIELDS,
25
+ CREATIVE_TAXONOMY: () => CREATIVE_TAXONOMY,
26
+ CreativeOptionError: () => CreativeOptionError,
24
27
  FAL_TOOLS: () => FAL_TOOLS,
25
28
  FAL_TOOLS_CHECKED_AT: () => FAL_TOOLS_CHECKED_AT,
26
29
  FAL_TOOL_IDS: () => FAL_TOOL_IDS,
@@ -42,6 +45,7 @@ __export(index_exports, {
42
45
  aspectToGptSize: () => aspectToGptSize,
43
46
  buildFalToolRequest: () => buildFalToolRequest,
44
47
  buildGenerateBody: () => buildGenerateBody,
48
+ enrichPrompt: () => enrichPrompt,
45
49
  err: () => import_neverthrow2.err,
46
50
  estimateCost: () => estimateCost,
47
51
  estimateVideoCost: () => estimateVideoCost,
@@ -49,7 +53,8 @@ __export(index_exports, {
49
53
  isFalToolId: () => isFalToolId,
50
54
  motifEnvSchema: () => motifEnvSchema,
51
55
  ok: () => import_neverthrow2.ok,
52
- parseMotifEnv: () => parseMotifEnv
56
+ parseMotifEnv: () => parseMotifEnv,
57
+ sanitizePrompt: () => sanitizePrompt
53
58
  });
54
59
  module.exports = __toCommonJS(index_exports);
55
60
  var import_neverthrow2 = require("neverthrow");
@@ -969,6 +974,136 @@ function estimateVideoCost(durationSeconds = 5, generateAudio = true) {
969
974
  return perSecond * durationSeconds;
970
975
  }
971
976
 
977
+ // src/creative.ts
978
+ var CONTROL_CHAR_REGEX = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
979
+ var CreativeOptionError = class extends Error {
980
+ availableIds;
981
+ code = "INVALID_OPTION";
982
+ field;
983
+ value;
984
+ constructor(details) {
985
+ super(
986
+ `Unknown creative ${details.field}: ${details.value}. Available: ${details.availableIds.join(", ")}`
987
+ );
988
+ this.name = "CreativeOptionError";
989
+ this.availableIds = details.availableIds;
990
+ this.field = details.field;
991
+ this.value = details.value;
992
+ }
993
+ };
994
+ var CREATIVE_FIELDS = [
995
+ "recipe",
996
+ "shot",
997
+ "lighting",
998
+ "genre",
999
+ "camera",
1000
+ "color",
1001
+ "material",
1002
+ "motion"
1003
+ ];
1004
+ var CREATIVE_TAXONOMY = {
1005
+ recipe: [
1006
+ {
1007
+ clause: "cinematic scene",
1008
+ description: "Frames the prompt as a cinematic still or scene.",
1009
+ id: "cinematic",
1010
+ label: "Cinematic"
1011
+ }
1012
+ ],
1013
+ shot: [
1014
+ {
1015
+ clause: "close-up composition with controlled depth of field",
1016
+ description: "Tight framing that emphasizes subject detail.",
1017
+ id: "close-up",
1018
+ label: "Close-up"
1019
+ }
1020
+ ],
1021
+ lighting: [
1022
+ {
1023
+ clause: "rim lighting with defined edge highlights",
1024
+ description: "Back or side light that separates the subject edge.",
1025
+ id: "rim",
1026
+ label: "Rim"
1027
+ }
1028
+ ],
1029
+ genre: [
1030
+ {
1031
+ clause: "film noir mood with high contrast shadows",
1032
+ description: "High-contrast cinematic mood with shadow-forward drama.",
1033
+ id: "film-noir",
1034
+ label: "Film noir"
1035
+ }
1036
+ ],
1037
+ camera: [
1038
+ {
1039
+ clause: "macro product photography with crisp surface detail",
1040
+ description: "Product-oriented macro camera language for close detail.",
1041
+ id: "macro-product",
1042
+ label: "Macro product"
1043
+ }
1044
+ ],
1045
+ color: [
1046
+ {
1047
+ clause: "monochrome palette with tonal contrast",
1048
+ description: "Black-and-white or single-channel tonal treatment.",
1049
+ id: "monochrome",
1050
+ label: "Monochrome"
1051
+ }
1052
+ ],
1053
+ material: [
1054
+ {
1055
+ clause: "reflective material surfaces with controlled highlights",
1056
+ description: "Emphasizes reflections and highlight control on surfaces.",
1057
+ id: "reflective",
1058
+ label: "Reflective"
1059
+ }
1060
+ ],
1061
+ motion: [
1062
+ {
1063
+ clause: "still composition with no motion blur",
1064
+ description: "Freezes the subject without implied movement.",
1065
+ id: "still",
1066
+ label: "Still"
1067
+ }
1068
+ ]
1069
+ };
1070
+ function sanitizePrompt(prompt) {
1071
+ return prompt.replace(CONTROL_CHAR_REGEX, "").replace(/\r\n/g, "\n").trim();
1072
+ }
1073
+ function enrichPrompt(options) {
1074
+ const basePrompt = sanitizePrompt(options.prompt);
1075
+ const clauses = [];
1076
+ const selected = {};
1077
+ for (const field of CREATIVE_FIELDS) {
1078
+ const optionId = options.creative?.[field];
1079
+ if (!optionId) {
1080
+ continue;
1081
+ }
1082
+ const option = CREATIVE_TAXONOMY[field].find(
1083
+ (candidate) => candidate.id === optionId
1084
+ );
1085
+ if (!option) {
1086
+ throw new CreativeOptionError({
1087
+ availableIds: CREATIVE_TAXONOMY[field].map((candidate) => candidate.id),
1088
+ field,
1089
+ value: optionId
1090
+ });
1091
+ }
1092
+ selected[field] = option.id;
1093
+ if (!clauses.includes(option.clause)) {
1094
+ clauses.push(option.clause);
1095
+ }
1096
+ }
1097
+ return {
1098
+ basePrompt,
1099
+ creative: {
1100
+ clauses,
1101
+ selected
1102
+ },
1103
+ prompt: clauses.length ? [basePrompt, ...clauses].join(", ") : basePrompt
1104
+ };
1105
+ }
1106
+
972
1107
  // src/env.ts
973
1108
  var import_envy = require("@howells/envy");
974
1109
  var import_zod = require("zod");
@@ -1135,6 +1270,7 @@ function buildGenerateBody(options) {
1135
1270
  const {
1136
1271
  prompt,
1137
1272
  model,
1273
+ creative,
1138
1274
  aspect = "1:1",
1139
1275
  resolution = "2K",
1140
1276
  numImages = 1,
@@ -1169,7 +1305,8 @@ function buildGenerateBody(options) {
1169
1305
  throw new Error(`Unknown model: ${model}`);
1170
1306
  }
1171
1307
  let endpoint = config.endpoint;
1172
- const body = { prompt };
1308
+ const enrichedPrompt = creative ? enrichPrompt({ prompt, creative }).prompt : prompt;
1309
+ const body = { prompt: enrichedPrompt };
1173
1310
  const hasEditImages = Boolean(editImageUrls?.length);
1174
1311
  const editImages = editImageUrls ?? [];
1175
1312
  const explicitAspect = options.aspect !== void 0;
@@ -2115,6 +2252,23 @@ var FAL_TOOLS = {
2115
2252
  output_format: "png"
2116
2253
  }
2117
2254
  },
2255
+ "sam3-1-image": {
2256
+ name: "SAM 3.1 Image",
2257
+ endpoint: "fal-ai/sam-3-1/image",
2258
+ task: "promptable image segmentation",
2259
+ category: "segmentation",
2260
+ description: "Segment image objects with text, point, or box prompts. SAM 3.1 adds Object Multiplex for faster multi-object tracking.",
2261
+ inputKind: "image",
2262
+ inputField: "image_url",
2263
+ outputKeys: ["image", "masks", "metadata", "scores", "boxes"],
2264
+ pricing: "$0.005/request",
2265
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/image",
2266
+ defaultOptions: {
2267
+ apply_mask: true,
2268
+ max_masks: 3,
2269
+ output_format: "png"
2270
+ }
2271
+ },
2118
2272
  "sam3-image-rle": {
2119
2273
  name: "SAM 3 Image RLE",
2120
2274
  endpoint: "fal-ai/sam-3/image-rle",
@@ -2743,6 +2897,9 @@ var MotifError = class extends Error {
2743
2897
  // Annotate the CommonJS export names for ESM import in node:
2744
2898
  0 && (module.exports = {
2745
2899
  ASPECT_RATIOS,
2900
+ CREATIVE_FIELDS,
2901
+ CREATIVE_TAXONOMY,
2902
+ CreativeOptionError,
2746
2903
  FAL_TOOLS,
2747
2904
  FAL_TOOLS_CHECKED_AT,
2748
2905
  FAL_TOOL_IDS,
@@ -2764,6 +2921,7 @@ var MotifError = class extends Error {
2764
2921
  aspectToGptSize,
2765
2922
  buildFalToolRequest,
2766
2923
  buildGenerateBody,
2924
+ enrichPrompt,
2767
2925
  err,
2768
2926
  estimateCost,
2769
2927
  estimateVideoCost,
@@ -2771,5 +2929,6 @@ var MotifError = class extends Error {
2771
2929
  isFalToolId,
2772
2930
  motifEnvSchema,
2773
2931
  ok,
2774
- parseMotifEnv
2932
+ parseMotifEnv,
2933
+ sanitizePrompt
2775
2934
  });
package/dist/index.d.cts CHANGED
@@ -3,6 +3,143 @@ export { Result, ResultAsync, err, ok } from 'neverthrow';
3
3
  import * as _howells_envy from '@howells/envy';
4
4
  import { z } from 'zod';
5
5
 
6
+ /**
7
+ * Creative direction fields applied to prompts in Motif's canonical order.
8
+ *
9
+ * The order matters when multiple fields are selected because prompt clauses
10
+ * are appended in this sequence for stable dry runs, tests, and history.
11
+ */
12
+ type CreativeField = "recipe" | "shot" | "lighting" | "genre" | "camera" | "color" | "material" | "motion";
13
+ /**
14
+ * Selected creative option ids keyed by direction field.
15
+ *
16
+ * Values must match option ids from `CREATIVE_TAXONOMY`; unknown ids throw a
17
+ * `CreativeOptionError` before any fal request body is built.
18
+ */
19
+ type CreativeDirection = Partial<Record<CreativeField, string>>;
20
+ /** A single selectable creative direction option exposed to CLI and MCP schemas. */
21
+ interface CreativeOption {
22
+ /** Prompt fragment appended when this option is selected. */
23
+ clause: string;
24
+ /** Human-facing explanation used in schema metadata and generated docs. */
25
+ description: string;
26
+ /** Stable machine id accepted by `CreativeDirection`. */
27
+ id: string;
28
+ /** Short display label for UIs and schema enum descriptions. */
29
+ label: string;
30
+ }
31
+ /** Structured details returned when a creative direction id is not recognized. */
32
+ interface CreativeOptionErrorDetails {
33
+ availableIds: string[];
34
+ code: "INVALID_OPTION";
35
+ field: CreativeField;
36
+ value: string;
37
+ }
38
+ /**
39
+ * Error thrown when prompt enrichment receives an unknown creative option id.
40
+ *
41
+ * The extra fields make CLI and agent callers able to show field-specific
42
+ * recovery hints without parsing the error message.
43
+ */
44
+ declare class CreativeOptionError extends Error implements CreativeOptionErrorDetails {
45
+ readonly availableIds: string[];
46
+ readonly code = "INVALID_OPTION";
47
+ readonly field: CreativeField;
48
+ readonly value: string;
49
+ constructor(details: Omit<CreativeOptionErrorDetails, "code">);
50
+ }
51
+ /** Canonical creative field order used for prompt enrichment and schema output. */
52
+ declare const CREATIVE_FIELDS: readonly ["recipe", "shot", "lighting", "genre", "camera", "color", "material", "motion"];
53
+ /**
54
+ * Built-in creative direction catalog.
55
+ *
56
+ * Each field contains the option ids accepted by `CreativeDirection` and the
57
+ * exact prompt clause that will be appended when selected.
58
+ */
59
+ declare const CREATIVE_TAXONOMY: {
60
+ readonly recipe: readonly [{
61
+ readonly clause: "cinematic scene";
62
+ readonly description: "Frames the prompt as a cinematic still or scene.";
63
+ readonly id: "cinematic";
64
+ readonly label: "Cinematic";
65
+ }];
66
+ readonly shot: readonly [{
67
+ readonly clause: "close-up composition with controlled depth of field";
68
+ readonly description: "Tight framing that emphasizes subject detail.";
69
+ readonly id: "close-up";
70
+ readonly label: "Close-up";
71
+ }];
72
+ readonly lighting: readonly [{
73
+ readonly clause: "rim lighting with defined edge highlights";
74
+ readonly description: "Back or side light that separates the subject edge.";
75
+ readonly id: "rim";
76
+ readonly label: "Rim";
77
+ }];
78
+ readonly genre: readonly [{
79
+ readonly clause: "film noir mood with high contrast shadows";
80
+ readonly description: "High-contrast cinematic mood with shadow-forward drama.";
81
+ readonly id: "film-noir";
82
+ readonly label: "Film noir";
83
+ }];
84
+ readonly camera: readonly [{
85
+ readonly clause: "macro product photography with crisp surface detail";
86
+ readonly description: "Product-oriented macro camera language for close detail.";
87
+ readonly id: "macro-product";
88
+ readonly label: "Macro product";
89
+ }];
90
+ readonly color: readonly [{
91
+ readonly clause: "monochrome palette with tonal contrast";
92
+ readonly description: "Black-and-white or single-channel tonal treatment.";
93
+ readonly id: "monochrome";
94
+ readonly label: "Monochrome";
95
+ }];
96
+ readonly material: readonly [{
97
+ readonly clause: "reflective material surfaces with controlled highlights";
98
+ readonly description: "Emphasizes reflections and highlight control on surfaces.";
99
+ readonly id: "reflective";
100
+ readonly label: "Reflective";
101
+ }];
102
+ readonly motion: readonly [{
103
+ readonly clause: "still composition with no motion blur";
104
+ readonly description: "Freezes the subject without implied movement.";
105
+ readonly id: "still";
106
+ readonly label: "Still";
107
+ }];
108
+ };
109
+ /** Result of applying creative direction to a base prompt. */
110
+ interface CreativePromptResult {
111
+ /** Sanitized user prompt before Motif adds creative clauses. */
112
+ basePrompt: string;
113
+ creative: {
114
+ /** Clauses appended to the prompt, de-duplicated in canonical field order. */
115
+ clauses: string[];
116
+ /** Validated option ids that were applied. */
117
+ selected: CreativeDirection;
118
+ };
119
+ /** Final prompt sent to fal after creative enrichment. */
120
+ prompt: string;
121
+ }
122
+ /** Input for Motif's prompt enrichment step. */
123
+ interface EnrichPromptOptions {
124
+ /** Optional selected creative option ids. */
125
+ creative?: CreativeDirection;
126
+ /** User-authored prompt before Motif normalization and enrichment. */
127
+ prompt: string;
128
+ }
129
+ /**
130
+ * Remove control characters and surrounding whitespace from a prompt.
131
+ *
132
+ * Newline style is normalized to `\n`; other text content is left unchanged.
133
+ */
134
+ declare function sanitizePrompt(prompt: string): string;
135
+ /**
136
+ * Append selected creative direction clauses to a prompt.
137
+ *
138
+ * Options are validated against `CREATIVE_TAXONOMY`, applied in
139
+ * `CREATIVE_FIELDS` order, and returned as metadata alongside the final prompt.
140
+ */
141
+ declare function enrichPrompt(options: EnrichPromptOptions): CreativePromptResult;
142
+
6
143
  /** ─── Model Types ─────────────────────────────────────────────── */
7
144
  type AspectRatio = "auto" | "8:1" | "4:1" | "21:9" | "16:9" | "3:2" | "4:3" | "5:4" | "1:1" | "4:5" | "3:4" | "2:3" | "9:16" | "1:4" | "1:8";
8
145
  type Resolution = "0.5K" | "1K" | "2K" | "4K";
@@ -104,6 +241,13 @@ interface GenerateOptions {
104
241
  aspect?: AspectRatio;
105
242
  /** GPT background mode where supported */
106
243
  background?: BackgroundMode;
244
+ /**
245
+ * Motif prompt enrichment choices applied before fal request construction.
246
+ *
247
+ * These options are not sent to fal directly; they append validated creative
248
+ * clauses to `prompt` and are omitted from the final request body.
249
+ */
250
+ creative?: CreativeDirection;
107
251
  editImageUrls?: string[];
108
252
  /** Ask fal not to store IO payloads, and expose request ids for deletion. */
109
253
  ephemeral?: boolean;
@@ -639,6 +783,23 @@ declare class MotifServer {
639
783
  readonly output_format: "png";
640
784
  };
641
785
  };
786
+ readonly "sam3-1-image": {
787
+ readonly name: "SAM 3.1 Image";
788
+ readonly endpoint: "fal-ai/sam-3-1/image";
789
+ readonly task: "promptable image segmentation";
790
+ readonly category: "segmentation";
791
+ readonly description: "Segment image objects with text, point, or box prompts. SAM 3.1 adds Object Multiplex for faster multi-object tracking.";
792
+ readonly inputKind: "image";
793
+ readonly inputField: "image_url";
794
+ readonly outputKeys: ["image", "masks", "metadata", "scores", "boxes"];
795
+ readonly pricing: "$0.005/request";
796
+ readonly sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/image";
797
+ readonly defaultOptions: {
798
+ readonly apply_mask: true;
799
+ readonly max_masks: 3;
800
+ readonly output_format: "png";
801
+ };
802
+ };
642
803
  readonly "sam3-image-rle": {
643
804
  readonly name: "SAM 3 Image RLE";
644
805
  readonly endpoint: "fal-ai/sam-3/image-rle";
@@ -1019,6 +1180,23 @@ declare const FAL_TOOLS: {
1019
1180
  readonly output_format: "png";
1020
1181
  };
1021
1182
  };
1183
+ readonly "sam3-1-image": {
1184
+ readonly name: "SAM 3.1 Image";
1185
+ readonly endpoint: "fal-ai/sam-3-1/image";
1186
+ readonly task: "promptable image segmentation";
1187
+ readonly category: "segmentation";
1188
+ readonly description: "Segment image objects with text, point, or box prompts. SAM 3.1 adds Object Multiplex for faster multi-object tracking.";
1189
+ readonly inputKind: "image";
1190
+ readonly inputField: "image_url";
1191
+ readonly outputKeys: ["image", "masks", "metadata", "scores", "boxes"];
1192
+ readonly pricing: "$0.005/request";
1193
+ readonly sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/image";
1194
+ readonly defaultOptions: {
1195
+ readonly apply_mask: true;
1196
+ readonly max_masks: 3;
1197
+ readonly output_format: "png";
1198
+ };
1199
+ };
1022
1200
  readonly "sam3-image-rle": {
1023
1201
  readonly name: "SAM 3 Image RLE";
1024
1202
  readonly endpoint: "fal-ai/sam-3/image-rle";
@@ -1137,4 +1315,4 @@ type FalToolId = (typeof FAL_TOOL_IDS)[number];
1137
1315
  declare function isFalToolId(tool: string): tool is FalToolId;
1138
1316
  declare function buildFalToolRequest(options: FalToolRunOptions): FalToolRequest;
1139
1317
 
1140
- export { ASPECT_RATIOS, type AspectRatio, type BackgroundMode, type CustomImageSize, FAL_TOOLS, FAL_TOOLS_CHECKED_AT, FAL_TOOL_IDS, FORMAT_PRESETS, type FalImageSizePreset, type FalToolConfig, type FalToolId, type FalToolInputKind, type FalToolRequest, type FalToolRunOptions, GENERATION_MODELS, type GenerateOptions, type GenerationModelName, type GptImageSize, IDEOGRAM_STYLES, IMAGE_EDITING_TOP_20, IMAGE_TEXT_TO_IMAGE_TOP_20, type ImageOutputFormat, type ImageQuality, type ImageSize, type JobStatus, type LeaderboardEntry, type LeaderboardSnapshot, MODELS, type ModelConfig, type ModelType, type MotifEnv, MotifError, type MotifImage, type MotifResponse, MotifServer, type MotifServerConfig, type QueuedJob, RECRAFT_STYLES, RESOLUTIONS, type RemoveBackgroundOptions, type Resolution, type SizeMode, type ThinkingLevel, type ToolResponse, type ToolRunOptions, UTILITY_MODELS, type UpscaleOptions, VIDEO_IMAGE_TO_VIDEO_TOP_15, VIDEO_MODELS, VIDEO_TEXT_TO_VIDEO_TOP_15, type VideoOptions, type VideoResponse, aspectToFalImageSize, aspectToGptSize, buildFalToolRequest, buildGenerateBody, estimateCost, estimateVideoCost, getFalKeyFromEnv, isFalToolId, motifEnvSchema, parseMotifEnv };
1318
+ export { ASPECT_RATIOS, type AspectRatio, type BackgroundMode, CREATIVE_FIELDS, CREATIVE_TAXONOMY, type CreativeDirection, type CreativeField, type CreativeOption, CreativeOptionError, type CreativeOptionErrorDetails, type CreativePromptResult, type CustomImageSize, type EnrichPromptOptions, FAL_TOOLS, FAL_TOOLS_CHECKED_AT, FAL_TOOL_IDS, FORMAT_PRESETS, type FalImageSizePreset, type FalToolConfig, type FalToolId, type FalToolInputKind, type FalToolRequest, type FalToolRunOptions, GENERATION_MODELS, type GenerateOptions, type GenerationModelName, type GptImageSize, IDEOGRAM_STYLES, IMAGE_EDITING_TOP_20, IMAGE_TEXT_TO_IMAGE_TOP_20, type ImageOutputFormat, type ImageQuality, type ImageSize, type JobStatus, type LeaderboardEntry, type LeaderboardSnapshot, MODELS, type ModelConfig, type ModelType, type MotifEnv, MotifError, type MotifImage, type MotifResponse, MotifServer, type MotifServerConfig, type QueuedJob, RECRAFT_STYLES, RESOLUTIONS, type RemoveBackgroundOptions, type Resolution, type SizeMode, type ThinkingLevel, type ToolResponse, type ToolRunOptions, UTILITY_MODELS, type UpscaleOptions, VIDEO_IMAGE_TO_VIDEO_TOP_15, VIDEO_MODELS, VIDEO_TEXT_TO_VIDEO_TOP_15, type VideoOptions, type VideoResponse, aspectToFalImageSize, aspectToGptSize, buildFalToolRequest, buildGenerateBody, enrichPrompt, estimateCost, estimateVideoCost, getFalKeyFromEnv, isFalToolId, motifEnvSchema, parseMotifEnv, sanitizePrompt };
package/dist/index.d.ts CHANGED
@@ -3,6 +3,143 @@ export { Result, ResultAsync, err, ok } from 'neverthrow';
3
3
  import * as _howells_envy from '@howells/envy';
4
4
  import { z } from 'zod';
5
5
 
6
+ /**
7
+ * Creative direction fields applied to prompts in Motif's canonical order.
8
+ *
9
+ * The order matters when multiple fields are selected because prompt clauses
10
+ * are appended in this sequence for stable dry runs, tests, and history.
11
+ */
12
+ type CreativeField = "recipe" | "shot" | "lighting" | "genre" | "camera" | "color" | "material" | "motion";
13
+ /**
14
+ * Selected creative option ids keyed by direction field.
15
+ *
16
+ * Values must match option ids from `CREATIVE_TAXONOMY`; unknown ids throw a
17
+ * `CreativeOptionError` before any fal request body is built.
18
+ */
19
+ type CreativeDirection = Partial<Record<CreativeField, string>>;
20
+ /** A single selectable creative direction option exposed to CLI and MCP schemas. */
21
+ interface CreativeOption {
22
+ /** Prompt fragment appended when this option is selected. */
23
+ clause: string;
24
+ /** Human-facing explanation used in schema metadata and generated docs. */
25
+ description: string;
26
+ /** Stable machine id accepted by `CreativeDirection`. */
27
+ id: string;
28
+ /** Short display label for UIs and schema enum descriptions. */
29
+ label: string;
30
+ }
31
+ /** Structured details returned when a creative direction id is not recognized. */
32
+ interface CreativeOptionErrorDetails {
33
+ availableIds: string[];
34
+ code: "INVALID_OPTION";
35
+ field: CreativeField;
36
+ value: string;
37
+ }
38
+ /**
39
+ * Error thrown when prompt enrichment receives an unknown creative option id.
40
+ *
41
+ * The extra fields make CLI and agent callers able to show field-specific
42
+ * recovery hints without parsing the error message.
43
+ */
44
+ declare class CreativeOptionError extends Error implements CreativeOptionErrorDetails {
45
+ readonly availableIds: string[];
46
+ readonly code = "INVALID_OPTION";
47
+ readonly field: CreativeField;
48
+ readonly value: string;
49
+ constructor(details: Omit<CreativeOptionErrorDetails, "code">);
50
+ }
51
+ /** Canonical creative field order used for prompt enrichment and schema output. */
52
+ declare const CREATIVE_FIELDS: readonly ["recipe", "shot", "lighting", "genre", "camera", "color", "material", "motion"];
53
+ /**
54
+ * Built-in creative direction catalog.
55
+ *
56
+ * Each field contains the option ids accepted by `CreativeDirection` and the
57
+ * exact prompt clause that will be appended when selected.
58
+ */
59
+ declare const CREATIVE_TAXONOMY: {
60
+ readonly recipe: readonly [{
61
+ readonly clause: "cinematic scene";
62
+ readonly description: "Frames the prompt as a cinematic still or scene.";
63
+ readonly id: "cinematic";
64
+ readonly label: "Cinematic";
65
+ }];
66
+ readonly shot: readonly [{
67
+ readonly clause: "close-up composition with controlled depth of field";
68
+ readonly description: "Tight framing that emphasizes subject detail.";
69
+ readonly id: "close-up";
70
+ readonly label: "Close-up";
71
+ }];
72
+ readonly lighting: readonly [{
73
+ readonly clause: "rim lighting with defined edge highlights";
74
+ readonly description: "Back or side light that separates the subject edge.";
75
+ readonly id: "rim";
76
+ readonly label: "Rim";
77
+ }];
78
+ readonly genre: readonly [{
79
+ readonly clause: "film noir mood with high contrast shadows";
80
+ readonly description: "High-contrast cinematic mood with shadow-forward drama.";
81
+ readonly id: "film-noir";
82
+ readonly label: "Film noir";
83
+ }];
84
+ readonly camera: readonly [{
85
+ readonly clause: "macro product photography with crisp surface detail";
86
+ readonly description: "Product-oriented macro camera language for close detail.";
87
+ readonly id: "macro-product";
88
+ readonly label: "Macro product";
89
+ }];
90
+ readonly color: readonly [{
91
+ readonly clause: "monochrome palette with tonal contrast";
92
+ readonly description: "Black-and-white or single-channel tonal treatment.";
93
+ readonly id: "monochrome";
94
+ readonly label: "Monochrome";
95
+ }];
96
+ readonly material: readonly [{
97
+ readonly clause: "reflective material surfaces with controlled highlights";
98
+ readonly description: "Emphasizes reflections and highlight control on surfaces.";
99
+ readonly id: "reflective";
100
+ readonly label: "Reflective";
101
+ }];
102
+ readonly motion: readonly [{
103
+ readonly clause: "still composition with no motion blur";
104
+ readonly description: "Freezes the subject without implied movement.";
105
+ readonly id: "still";
106
+ readonly label: "Still";
107
+ }];
108
+ };
109
+ /** Result of applying creative direction to a base prompt. */
110
+ interface CreativePromptResult {
111
+ /** Sanitized user prompt before Motif adds creative clauses. */
112
+ basePrompt: string;
113
+ creative: {
114
+ /** Clauses appended to the prompt, de-duplicated in canonical field order. */
115
+ clauses: string[];
116
+ /** Validated option ids that were applied. */
117
+ selected: CreativeDirection;
118
+ };
119
+ /** Final prompt sent to fal after creative enrichment. */
120
+ prompt: string;
121
+ }
122
+ /** Input for Motif's prompt enrichment step. */
123
+ interface EnrichPromptOptions {
124
+ /** Optional selected creative option ids. */
125
+ creative?: CreativeDirection;
126
+ /** User-authored prompt before Motif normalization and enrichment. */
127
+ prompt: string;
128
+ }
129
+ /**
130
+ * Remove control characters and surrounding whitespace from a prompt.
131
+ *
132
+ * Newline style is normalized to `\n`; other text content is left unchanged.
133
+ */
134
+ declare function sanitizePrompt(prompt: string): string;
135
+ /**
136
+ * Append selected creative direction clauses to a prompt.
137
+ *
138
+ * Options are validated against `CREATIVE_TAXONOMY`, applied in
139
+ * `CREATIVE_FIELDS` order, and returned as metadata alongside the final prompt.
140
+ */
141
+ declare function enrichPrompt(options: EnrichPromptOptions): CreativePromptResult;
142
+
6
143
  /** ─── Model Types ─────────────────────────────────────────────── */
7
144
  type AspectRatio = "auto" | "8:1" | "4:1" | "21:9" | "16:9" | "3:2" | "4:3" | "5:4" | "1:1" | "4:5" | "3:4" | "2:3" | "9:16" | "1:4" | "1:8";
8
145
  type Resolution = "0.5K" | "1K" | "2K" | "4K";
@@ -104,6 +241,13 @@ interface GenerateOptions {
104
241
  aspect?: AspectRatio;
105
242
  /** GPT background mode where supported */
106
243
  background?: BackgroundMode;
244
+ /**
245
+ * Motif prompt enrichment choices applied before fal request construction.
246
+ *
247
+ * These options are not sent to fal directly; they append validated creative
248
+ * clauses to `prompt` and are omitted from the final request body.
249
+ */
250
+ creative?: CreativeDirection;
107
251
  editImageUrls?: string[];
108
252
  /** Ask fal not to store IO payloads, and expose request ids for deletion. */
109
253
  ephemeral?: boolean;
@@ -639,6 +783,23 @@ declare class MotifServer {
639
783
  readonly output_format: "png";
640
784
  };
641
785
  };
786
+ readonly "sam3-1-image": {
787
+ readonly name: "SAM 3.1 Image";
788
+ readonly endpoint: "fal-ai/sam-3-1/image";
789
+ readonly task: "promptable image segmentation";
790
+ readonly category: "segmentation";
791
+ readonly description: "Segment image objects with text, point, or box prompts. SAM 3.1 adds Object Multiplex for faster multi-object tracking.";
792
+ readonly inputKind: "image";
793
+ readonly inputField: "image_url";
794
+ readonly outputKeys: ["image", "masks", "metadata", "scores", "boxes"];
795
+ readonly pricing: "$0.005/request";
796
+ readonly sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/image";
797
+ readonly defaultOptions: {
798
+ readonly apply_mask: true;
799
+ readonly max_masks: 3;
800
+ readonly output_format: "png";
801
+ };
802
+ };
642
803
  readonly "sam3-image-rle": {
643
804
  readonly name: "SAM 3 Image RLE";
644
805
  readonly endpoint: "fal-ai/sam-3/image-rle";
@@ -1019,6 +1180,23 @@ declare const FAL_TOOLS: {
1019
1180
  readonly output_format: "png";
1020
1181
  };
1021
1182
  };
1183
+ readonly "sam3-1-image": {
1184
+ readonly name: "SAM 3.1 Image";
1185
+ readonly endpoint: "fal-ai/sam-3-1/image";
1186
+ readonly task: "promptable image segmentation";
1187
+ readonly category: "segmentation";
1188
+ readonly description: "Segment image objects with text, point, or box prompts. SAM 3.1 adds Object Multiplex for faster multi-object tracking.";
1189
+ readonly inputKind: "image";
1190
+ readonly inputField: "image_url";
1191
+ readonly outputKeys: ["image", "masks", "metadata", "scores", "boxes"];
1192
+ readonly pricing: "$0.005/request";
1193
+ readonly sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/image";
1194
+ readonly defaultOptions: {
1195
+ readonly apply_mask: true;
1196
+ readonly max_masks: 3;
1197
+ readonly output_format: "png";
1198
+ };
1199
+ };
1022
1200
  readonly "sam3-image-rle": {
1023
1201
  readonly name: "SAM 3 Image RLE";
1024
1202
  readonly endpoint: "fal-ai/sam-3/image-rle";
@@ -1137,4 +1315,4 @@ type FalToolId = (typeof FAL_TOOL_IDS)[number];
1137
1315
  declare function isFalToolId(tool: string): tool is FalToolId;
1138
1316
  declare function buildFalToolRequest(options: FalToolRunOptions): FalToolRequest;
1139
1317
 
1140
- export { ASPECT_RATIOS, type AspectRatio, type BackgroundMode, type CustomImageSize, FAL_TOOLS, FAL_TOOLS_CHECKED_AT, FAL_TOOL_IDS, FORMAT_PRESETS, type FalImageSizePreset, type FalToolConfig, type FalToolId, type FalToolInputKind, type FalToolRequest, type FalToolRunOptions, GENERATION_MODELS, type GenerateOptions, type GenerationModelName, type GptImageSize, IDEOGRAM_STYLES, IMAGE_EDITING_TOP_20, IMAGE_TEXT_TO_IMAGE_TOP_20, type ImageOutputFormat, type ImageQuality, type ImageSize, type JobStatus, type LeaderboardEntry, type LeaderboardSnapshot, MODELS, type ModelConfig, type ModelType, type MotifEnv, MotifError, type MotifImage, type MotifResponse, MotifServer, type MotifServerConfig, type QueuedJob, RECRAFT_STYLES, RESOLUTIONS, type RemoveBackgroundOptions, type Resolution, type SizeMode, type ThinkingLevel, type ToolResponse, type ToolRunOptions, UTILITY_MODELS, type UpscaleOptions, VIDEO_IMAGE_TO_VIDEO_TOP_15, VIDEO_MODELS, VIDEO_TEXT_TO_VIDEO_TOP_15, type VideoOptions, type VideoResponse, aspectToFalImageSize, aspectToGptSize, buildFalToolRequest, buildGenerateBody, estimateCost, estimateVideoCost, getFalKeyFromEnv, isFalToolId, motifEnvSchema, parseMotifEnv };
1318
+ export { ASPECT_RATIOS, type AspectRatio, type BackgroundMode, CREATIVE_FIELDS, CREATIVE_TAXONOMY, type CreativeDirection, type CreativeField, type CreativeOption, CreativeOptionError, type CreativeOptionErrorDetails, type CreativePromptResult, type CustomImageSize, type EnrichPromptOptions, FAL_TOOLS, FAL_TOOLS_CHECKED_AT, FAL_TOOL_IDS, FORMAT_PRESETS, type FalImageSizePreset, type FalToolConfig, type FalToolId, type FalToolInputKind, type FalToolRequest, type FalToolRunOptions, GENERATION_MODELS, type GenerateOptions, type GenerationModelName, type GptImageSize, IDEOGRAM_STYLES, IMAGE_EDITING_TOP_20, IMAGE_TEXT_TO_IMAGE_TOP_20, type ImageOutputFormat, type ImageQuality, type ImageSize, type JobStatus, type LeaderboardEntry, type LeaderboardSnapshot, MODELS, type ModelConfig, type ModelType, type MotifEnv, MotifError, type MotifImage, type MotifResponse, MotifServer, type MotifServerConfig, type QueuedJob, RECRAFT_STYLES, RESOLUTIONS, type RemoveBackgroundOptions, type Resolution, type SizeMode, type ThinkingLevel, type ToolResponse, type ToolRunOptions, UTILITY_MODELS, type UpscaleOptions, VIDEO_IMAGE_TO_VIDEO_TOP_15, VIDEO_MODELS, VIDEO_TEXT_TO_VIDEO_TOP_15, type VideoOptions, type VideoResponse, aspectToFalImageSize, aspectToGptSize, buildFalToolRequest, buildGenerateBody, enrichPrompt, estimateCost, estimateVideoCost, getFalKeyFromEnv, isFalToolId, motifEnvSchema, parseMotifEnv, sanitizePrompt };
package/dist/index.js CHANGED
@@ -916,6 +916,136 @@ function estimateVideoCost(durationSeconds = 5, generateAudio = true) {
916
916
  return perSecond * durationSeconds;
917
917
  }
918
918
 
919
+ // src/creative.ts
920
+ var CONTROL_CHAR_REGEX = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
921
+ var CreativeOptionError = class extends Error {
922
+ availableIds;
923
+ code = "INVALID_OPTION";
924
+ field;
925
+ value;
926
+ constructor(details) {
927
+ super(
928
+ `Unknown creative ${details.field}: ${details.value}. Available: ${details.availableIds.join(", ")}`
929
+ );
930
+ this.name = "CreativeOptionError";
931
+ this.availableIds = details.availableIds;
932
+ this.field = details.field;
933
+ this.value = details.value;
934
+ }
935
+ };
936
+ var CREATIVE_FIELDS = [
937
+ "recipe",
938
+ "shot",
939
+ "lighting",
940
+ "genre",
941
+ "camera",
942
+ "color",
943
+ "material",
944
+ "motion"
945
+ ];
946
+ var CREATIVE_TAXONOMY = {
947
+ recipe: [
948
+ {
949
+ clause: "cinematic scene",
950
+ description: "Frames the prompt as a cinematic still or scene.",
951
+ id: "cinematic",
952
+ label: "Cinematic"
953
+ }
954
+ ],
955
+ shot: [
956
+ {
957
+ clause: "close-up composition with controlled depth of field",
958
+ description: "Tight framing that emphasizes subject detail.",
959
+ id: "close-up",
960
+ label: "Close-up"
961
+ }
962
+ ],
963
+ lighting: [
964
+ {
965
+ clause: "rim lighting with defined edge highlights",
966
+ description: "Back or side light that separates the subject edge.",
967
+ id: "rim",
968
+ label: "Rim"
969
+ }
970
+ ],
971
+ genre: [
972
+ {
973
+ clause: "film noir mood with high contrast shadows",
974
+ description: "High-contrast cinematic mood with shadow-forward drama.",
975
+ id: "film-noir",
976
+ label: "Film noir"
977
+ }
978
+ ],
979
+ camera: [
980
+ {
981
+ clause: "macro product photography with crisp surface detail",
982
+ description: "Product-oriented macro camera language for close detail.",
983
+ id: "macro-product",
984
+ label: "Macro product"
985
+ }
986
+ ],
987
+ color: [
988
+ {
989
+ clause: "monochrome palette with tonal contrast",
990
+ description: "Black-and-white or single-channel tonal treatment.",
991
+ id: "monochrome",
992
+ label: "Monochrome"
993
+ }
994
+ ],
995
+ material: [
996
+ {
997
+ clause: "reflective material surfaces with controlled highlights",
998
+ description: "Emphasizes reflections and highlight control on surfaces.",
999
+ id: "reflective",
1000
+ label: "Reflective"
1001
+ }
1002
+ ],
1003
+ motion: [
1004
+ {
1005
+ clause: "still composition with no motion blur",
1006
+ description: "Freezes the subject without implied movement.",
1007
+ id: "still",
1008
+ label: "Still"
1009
+ }
1010
+ ]
1011
+ };
1012
+ function sanitizePrompt(prompt) {
1013
+ return prompt.replace(CONTROL_CHAR_REGEX, "").replace(/\r\n/g, "\n").trim();
1014
+ }
1015
+ function enrichPrompt(options) {
1016
+ const basePrompt = sanitizePrompt(options.prompt);
1017
+ const clauses = [];
1018
+ const selected = {};
1019
+ for (const field of CREATIVE_FIELDS) {
1020
+ const optionId = options.creative?.[field];
1021
+ if (!optionId) {
1022
+ continue;
1023
+ }
1024
+ const option = CREATIVE_TAXONOMY[field].find(
1025
+ (candidate) => candidate.id === optionId
1026
+ );
1027
+ if (!option) {
1028
+ throw new CreativeOptionError({
1029
+ availableIds: CREATIVE_TAXONOMY[field].map((candidate) => candidate.id),
1030
+ field,
1031
+ value: optionId
1032
+ });
1033
+ }
1034
+ selected[field] = option.id;
1035
+ if (!clauses.includes(option.clause)) {
1036
+ clauses.push(option.clause);
1037
+ }
1038
+ }
1039
+ return {
1040
+ basePrompt,
1041
+ creative: {
1042
+ clauses,
1043
+ selected
1044
+ },
1045
+ prompt: clauses.length ? [basePrompt, ...clauses].join(", ") : basePrompt
1046
+ };
1047
+ }
1048
+
919
1049
  // src/env.ts
920
1050
  import { defineEnv } from "@howells/envy";
921
1051
  import { z } from "zod";
@@ -1082,6 +1212,7 @@ function buildGenerateBody(options) {
1082
1212
  const {
1083
1213
  prompt,
1084
1214
  model,
1215
+ creative,
1085
1216
  aspect = "1:1",
1086
1217
  resolution = "2K",
1087
1218
  numImages = 1,
@@ -1116,7 +1247,8 @@ function buildGenerateBody(options) {
1116
1247
  throw new Error(`Unknown model: ${model}`);
1117
1248
  }
1118
1249
  let endpoint = config.endpoint;
1119
- const body = { prompt };
1250
+ const enrichedPrompt = creative ? enrichPrompt({ prompt, creative }).prompt : prompt;
1251
+ const body = { prompt: enrichedPrompt };
1120
1252
  const hasEditImages = Boolean(editImageUrls?.length);
1121
1253
  const editImages = editImageUrls ?? [];
1122
1254
  const explicitAspect = options.aspect !== void 0;
@@ -2062,6 +2194,23 @@ var FAL_TOOLS = {
2062
2194
  output_format: "png"
2063
2195
  }
2064
2196
  },
2197
+ "sam3-1-image": {
2198
+ name: "SAM 3.1 Image",
2199
+ endpoint: "fal-ai/sam-3-1/image",
2200
+ task: "promptable image segmentation",
2201
+ category: "segmentation",
2202
+ description: "Segment image objects with text, point, or box prompts. SAM 3.1 adds Object Multiplex for faster multi-object tracking.",
2203
+ inputKind: "image",
2204
+ inputField: "image_url",
2205
+ outputKeys: ["image", "masks", "metadata", "scores", "boxes"],
2206
+ pricing: "$0.005/request",
2207
+ sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/image",
2208
+ defaultOptions: {
2209
+ apply_mask: true,
2210
+ max_masks: 3,
2211
+ output_format: "png"
2212
+ }
2213
+ },
2065
2214
  "sam3-image-rle": {
2066
2215
  name: "SAM 3 Image RLE",
2067
2216
  endpoint: "fal-ai/sam-3/image-rle",
@@ -2689,6 +2838,9 @@ var MotifError = class extends Error {
2689
2838
  };
2690
2839
  export {
2691
2840
  ASPECT_RATIOS,
2841
+ CREATIVE_FIELDS,
2842
+ CREATIVE_TAXONOMY,
2843
+ CreativeOptionError,
2692
2844
  FAL_TOOLS,
2693
2845
  FAL_TOOLS_CHECKED_AT,
2694
2846
  FAL_TOOL_IDS,
@@ -2710,6 +2862,7 @@ export {
2710
2862
  aspectToGptSize,
2711
2863
  buildFalToolRequest,
2712
2864
  buildGenerateBody,
2865
+ enrichPrompt,
2713
2866
  err2 as err,
2714
2867
  estimateCost,
2715
2868
  estimateVideoCost,
@@ -2717,5 +2870,6 @@ export {
2717
2870
  isFalToolId,
2718
2871
  motifEnvSchema,
2719
2872
  ok2 as ok,
2720
- parseMotifEnv
2873
+ parseMotifEnv,
2874
+ sanitizePrompt
2721
2875
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@howells/motif-sdk",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Public Node SDK for Motif fal.ai generation, editing, utility tools, and model metadata.",
5
5
  "license": "MIT",
6
6
  "author": "Daniel Howells",
@@ -37,6 +37,17 @@
37
37
  "publishConfig": {
38
38
  "access": "public"
39
39
  },
40
+ "scripts": {
41
+ "build": "tsup src/index.ts --format cjs,esm --dts",
42
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
43
+ "lint": "howells-lint",
44
+ "format": "howells-format",
45
+ "typecheck": "tsc --noEmit",
46
+ "test": "vitest run",
47
+ "test:watch": "vitest",
48
+ "test:coverage": "vitest run --coverage",
49
+ "prepack": "pnpm build"
50
+ },
40
51
  "dependencies": {
41
52
  "@howells/envy": "^0.3.7",
42
53
  "neverthrow": "^8.2.0",
@@ -50,15 +61,5 @@
50
61
  "tsup": "^8.0.0",
51
62
  "typescript": "^6.0.3",
52
63
  "vitest": "^4.1.5"
53
- },
54
- "scripts": {
55
- "build": "tsup src/index.ts --format cjs,esm --dts",
56
- "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
57
- "lint": "howells-lint",
58
- "format": "howells-format",
59
- "typecheck": "tsc --noEmit",
60
- "test": "vitest run",
61
- "test:watch": "vitest",
62
- "test:coverage": "vitest run --coverage"
63
64
  }
64
- }
65
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Daniel Howells
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.