@telepat/ideon 0.1.18 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/ideon.js +365 -1036
  2. package/package.json +2 -1
package/dist/ideon.js CHANGED
@@ -111,7 +111,7 @@ var modelSettingsSchema = z.object({
111
111
  topP: z.number().min(0).max(1).default(1)
112
112
  });
113
113
  var baseT2ISettingsSchema = z.object({
114
- modelId: z.string().default("black-forest-labs/flux-schnell"),
114
+ modelId: z.string().default("flux"),
115
115
  inputOverrides: z.record(z.string(), z.unknown()).default({})
116
116
  });
117
117
  var notificationsSettingsSchema = z.object({
@@ -1362,7 +1362,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
1362
1362
  // package.json
1363
1363
  var package_default = {
1364
1364
  name: "@telepat/ideon",
1365
- version: "0.1.18",
1365
+ version: "0.1.19",
1366
1366
  description: "CLI for generating rich articles and images from ideas.",
1367
1367
  type: "module",
1368
1368
  repository: {
@@ -1420,6 +1420,7 @@ var package_default = {
1420
1420
  dependencies: {
1421
1421
  "@ant-design/icons": "^6.1.1",
1422
1422
  "@modelcontextprotocol/sdk": "^1.29.0",
1423
+ "@telepat/limn": "^0.1.5",
1423
1424
  antd: "^6.3.4",
1424
1425
  commander: "^14.0.1",
1425
1426
  "env-paths": "^3.0.0",
@@ -2874,65 +2875,8 @@ function normalizeGeneratedSection(content, label2) {
2874
2875
  return normalized.replace(/^```(?:markdown)?\s*/i, "").replace(/```\s*$/i, "").trim();
2875
2876
  }
2876
2877
 
2877
- // src/images/replicateClient.ts
2878
- import Replicate from "replicate";
2879
- var ReplicateClient = class {
2880
- client;
2881
- constructor(apiToken) {
2882
- this.client = new Replicate({ auth: apiToken });
2883
- }
2884
- async runModel(model, input, options = {}) {
2885
- let lastError = null;
2886
- const startedAtMs = Date.now();
2887
- let attempts = 0;
2888
- let retries = 0;
2889
- let retryBackoffMs = 0;
2890
- for (let attempt = 0; attempt < 3; attempt += 1) {
2891
- attempts = attempt + 1;
2892
- try {
2893
- const output = await this.client.run(model, { input });
2894
- options.onMetrics?.({
2895
- durationMs: Date.now() - startedAtMs,
2896
- attempts,
2897
- retries,
2898
- retryBackoffMs,
2899
- modelId: model
2900
- });
2901
- return output;
2902
- } catch (error) {
2903
- lastError = error instanceof Error ? error : new Error("Unknown Replicate client error.");
2904
- if (attempt < 2 && shouldRetryError(lastError)) {
2905
- const backoff = backoffMs(attempt);
2906
- retries += 1;
2907
- retryBackoffMs += backoff;
2908
- options.onRetry?.({
2909
- attempts,
2910
- retries,
2911
- retryBackoffMs,
2912
- backoffMs: backoff,
2913
- errorMessage: lastError.message,
2914
- modelId: model
2915
- });
2916
- await wait(backoff);
2917
- continue;
2918
- }
2919
- break;
2920
- }
2921
- }
2922
- throw lastError ?? new Error("Replicate request failed.");
2923
- }
2924
- };
2925
- function shouldRetryError(error) {
2926
- return /timeout|network|fetch|temporarily|rate|429|500|502|503|504/i.test(error.message);
2927
- }
2928
- function backoffMs(attempt) {
2929
- return 500 * (attempt + 1);
2930
- }
2931
- function wait(ms) {
2932
- return new Promise((resolve) => {
2933
- setTimeout(resolve, ms);
2934
- });
2935
- }
2878
+ // src/pipeline/runner.ts
2879
+ import { Limn } from "@telepat/limn";
2936
2880
 
2937
2881
  // src/images/renderImages.ts
2938
2882
  import { writeFile as writeFile4 } from "fs/promises";
@@ -2961,11 +2905,11 @@ function buildImagePromptMessages(plan, image, section) {
2961
2905
  `Section excerpt: ${section.body.slice(0, 500)}`
2962
2906
  );
2963
2907
  }
2964
- userLines.push("Write one strong prompt for a clean editorial illustration. Avoid text overlays or watermarks.");
2908
+ userLines.push("Write one strong visual prompt describing the image in natural language.");
2965
2909
  return [
2966
2910
  {
2967
2911
  role: "system",
2968
- content: "You write concise, high-signal prompts for text-to-image models. The prompt should be vivid, compositionally clear, and suitable for editorial illustration. Return only the requested JSON."
2912
+ content: "You write concise, high-signal prompts for text-to-image models. The prompt should be vivid and compositionally clear. Do not include any words, letters, numbers, logos, watermarks, or signage in the image, unless text in the image was explicitly requested. Return only the requested JSON."
2969
2913
  },
2970
2914
  {
2971
2915
  role: "user",
@@ -2974,468 +2918,6 @@ function buildImagePromptMessages(plan, image, section) {
2974
2918
  ];
2975
2919
  }
2976
2920
 
2977
- // src/models/t2i/definitions/bytedance__seedream-4.json
2978
- var bytedance_seedream_4_default = {
2979
- modelId: "bytedance/seedream-4",
2980
- provider: "replicate",
2981
- displayName: "Seedream 4",
2982
- category: "image-generation",
2983
- pricingSourceUrl: "https://replicate.com/bytedance/seedream-4",
2984
- pricingNotes: "Replicate pricing: $0.03 per output image.",
2985
- pricing: {
2986
- usdPerSecond: null,
2987
- usdPer1kInputTokens: null,
2988
- usdPer1kOutputTokens: null
2989
- },
2990
- pricingRules: {
2991
- basis: "output_image_count",
2992
- usdPerImage: 0.03
2993
- },
2994
- inputOptions: {
2995
- userConfigurable: ["size", "sequential_image_generation", "max_images", "enhance_prompt"],
2996
- pipelineManaged: ["prompt", "aspect_ratio"],
2997
- fields: {
2998
- size: {
2999
- type: "string",
3000
- default: "2K",
3001
- enum: ["1K", "2K", "4K"]
3002
- },
3003
- sequential_image_generation: {
3004
- type: "string",
3005
- default: "disabled",
3006
- enum: ["disabled", "auto"]
3007
- },
3008
- max_images: {
3009
- type: "integer",
3010
- default: 1,
3011
- minimum: 1,
3012
- maximum: 15
3013
- },
3014
- enhance_prompt: {
3015
- type: "boolean",
3016
- default: true
3017
- },
3018
- prompt: {
3019
- type: "string",
3020
- required: true
3021
- },
3022
- aspect_ratio: {
3023
- type: "string",
3024
- required: true,
3025
- enum: ["1:1", "16:9", "9:16"]
3026
- }
3027
- }
3028
- }
3029
- };
3030
-
3031
- // src/models/t2i/definitions/black-forest-labs__flux-2-pro.json
3032
- var black_forest_labs_flux_2_pro_default = {
3033
- modelId: "black-forest-labs/flux-2-pro",
3034
- provider: "replicate",
3035
- displayName: "FLUX 2 Pro",
3036
- category: "image-generation",
3037
- pricingSourceUrl: "https://replicate.com/black-forest-labs/flux-2-pro",
3038
- pricingNotes: "Replicate lists multi-property pricing for run and input/output megapixels; this model currently uses best-effort cost fallback when exact rule mapping is unavailable.",
3039
- pricing: {
3040
- usdPerSecond: null,
3041
- usdPer1kInputTokens: null,
3042
- usdPer1kOutputTokens: null
3043
- },
3044
- inputOptions: {
3045
- userConfigurable: ["safety_tolerance", "seed", "output_format", "output_quality"],
3046
- pipelineManaged: ["prompt", "aspect_ratio", "width", "height"],
3047
- fields: {
3048
- safety_tolerance: {
3049
- type: "integer",
3050
- default: 2,
3051
- minimum: 1,
3052
- maximum: 5
3053
- },
3054
- seed: {
3055
- type: "integer",
3056
- nullable: true
3057
- },
3058
- output_format: {
3059
- type: "string",
3060
- default: "webp",
3061
- enum: ["webp", "png", "jpg", "jpeg"]
3062
- },
3063
- output_quality: {
3064
- type: "integer",
3065
- default: 80,
3066
- minimum: 0,
3067
- maximum: 100
3068
- },
3069
- prompt: {
3070
- type: "string",
3071
- required: true
3072
- },
3073
- aspect_ratio: {
3074
- type: "string",
3075
- required: true,
3076
- enum: ["custom"]
3077
- },
3078
- width: {
3079
- type: "integer",
3080
- required: true,
3081
- minimum: 256,
3082
- maximum: 2048
3083
- },
3084
- height: {
3085
- type: "integer",
3086
- required: true,
3087
- minimum: 256,
3088
- maximum: 2048
3089
- }
3090
- }
3091
- }
3092
- };
3093
-
3094
- // src/models/t2i/definitions/black-forest-labs__flux-schnell.json
3095
- var black_forest_labs_flux_schnell_default = {
3096
- modelId: "black-forest-labs/flux-schnell",
3097
- provider: "replicate",
3098
- displayName: "FLUX Schnell",
3099
- category: "image-generation",
3100
- pricingSourceUrl: "https://replicate.com/black-forest-labs/flux-schnell",
3101
- pricingNotes: "Replicate pricing: $3 per 1000 output images.",
3102
- pricing: {
3103
- usdPerSecond: null,
3104
- usdPer1kInputTokens: null,
3105
- usdPer1kOutputTokens: null
3106
- },
3107
- pricingRules: {
3108
- basis: "output_image_count",
3109
- usdPerImage: 3e-3
3110
- },
3111
- inputOptions: {
3112
- userConfigurable: ["num_outputs", "num_inference_steps", "seed", "output_format", "output_quality", "disable_safety_checker", "go_fast", "megapixels"],
3113
- pipelineManaged: ["prompt", "aspect_ratio"],
3114
- fields: {
3115
- num_outputs: {
3116
- type: "integer",
3117
- default: 1,
3118
- minimum: 1,
3119
- maximum: 4
3120
- },
3121
- num_inference_steps: {
3122
- type: "integer",
3123
- default: 4,
3124
- minimum: 1,
3125
- maximum: 4
3126
- },
3127
- seed: {
3128
- type: "integer",
3129
- nullable: true
3130
- },
3131
- output_format: {
3132
- type: "string",
3133
- default: "webp",
3134
- enum: ["webp", "jpg", "png"]
3135
- },
3136
- output_quality: {
3137
- type: "integer",
3138
- default: 80,
3139
- minimum: 0,
3140
- maximum: 100
3141
- },
3142
- disable_safety_checker: {
3143
- type: "boolean",
3144
- default: false
3145
- },
3146
- go_fast: {
3147
- type: "boolean",
3148
- default: true
3149
- },
3150
- megapixels: {
3151
- type: "string",
3152
- default: "1",
3153
- allowAnyString: true,
3154
- recommendedValues: ["1"]
3155
- },
3156
- prompt: {
3157
- type: "string",
3158
- required: true
3159
- },
3160
- aspect_ratio: {
3161
- type: "string",
3162
- required: true,
3163
- enum: ["1:1", "16:9", "9:16"]
3164
- }
3165
- }
3166
- }
3167
- };
3168
-
3169
- // src/models/t2i/definitions/google__nano-banana-pro.json
3170
- var google_nano_banana_pro_default = {
3171
- modelId: "google/nano-banana-pro",
3172
- provider: "replicate",
3173
- displayName: "Nano Banana Pro",
3174
- category: "image-generation",
3175
- pricingSourceUrl: "https://replicate.com/google/nano-banana-pro",
3176
- pricingNotes: "Replicate pricing by target resolution: 1K/2K=$0.15 per output image, 4K=$0.30 per output image, fallback tier=$0.035 per output image.",
3177
- pricing: {
3178
- usdPerSecond: null,
3179
- usdPer1kInputTokens: null,
3180
- usdPer1kOutputTokens: null
3181
- },
3182
- pricingRules: {
3183
- basis: "output_image_resolution",
3184
- tiers: [
3185
- {
3186
- resolution: "1K",
3187
- usdPerImage: 0.15
3188
- },
3189
- {
3190
- resolution: "2K",
3191
- usdPerImage: 0.15
3192
- },
3193
- {
3194
- resolution: "4K",
3195
- usdPerImage: 0.3
3196
- },
3197
- {
3198
- resolution: "fallback",
3199
- usdPerImage: 0.035
3200
- }
3201
- ]
3202
- },
3203
- inputOptions: {
3204
- userConfigurable: ["resolution", "output_format", "safety_filter_level", "allow_fallback_model"],
3205
- pipelineManaged: ["prompt", "aspect_ratio"],
3206
- fields: {
3207
- resolution: {
3208
- type: "string",
3209
- default: "2K",
3210
- enum: ["1K", "2K", "4K"]
3211
- },
3212
- output_format: {
3213
- type: "string",
3214
- default: "jpg",
3215
- enum: ["jpg", "png", "webp"]
3216
- },
3217
- safety_filter_level: {
3218
- type: "string",
3219
- default: "block_only_high",
3220
- enum: ["block_low_and_above", "block_medium_and_above", "block_only_high"]
3221
- },
3222
- allow_fallback_model: {
3223
- type: "boolean",
3224
- default: false
3225
- },
3226
- prompt: {
3227
- type: "string",
3228
- required: true
3229
- },
3230
- aspect_ratio: {
3231
- type: "string",
3232
- required: true,
3233
- enum: ["1:1", "16:9", "9:16"]
3234
- }
3235
- }
3236
- }
3237
- };
3238
-
3239
- // src/models/t2i/definitions/prunaai__z-image-turbo.json
3240
- var prunaai_z_image_turbo_default = {
3241
- modelId: "prunaai/z-image-turbo",
3242
- provider: "replicate",
3243
- displayName: "Z Image Turbo",
3244
- category: "image-generation",
3245
- pricingSourceUrl: "https://replicate.com/prunaai/z-image-turbo",
3246
- pricingNotes: "Replicate pricing is tiered by output image resolution (megapixels).",
3247
- pricing: {
3248
- usdPerSecond: null,
3249
- usdPer1kInputTokens: null,
3250
- usdPer1kOutputTokens: null
3251
- },
3252
- pricingRules: {
3253
- basis: "output_image_megapixels",
3254
- tiers: [
3255
- {
3256
- maxMegapixels: 0.5,
3257
- usdPerImage: 25e-4
3258
- },
3259
- {
3260
- maxMegapixels: 1,
3261
- usdPerImage: 5e-3
3262
- },
3263
- {
3264
- maxMegapixels: 2,
3265
- usdPerImage: 0.01
3266
- },
3267
- {
3268
- maxMegapixels: 3,
3269
- usdPerImage: 0.015
3270
- },
3271
- {
3272
- maxMegapixels: 4,
3273
- usdPerImage: 0.02
3274
- }
3275
- ]
3276
- },
3277
- inputOptions: {
3278
- userConfigurable: ["num_inference_steps", "guidance_scale", "seed", "go_fast", "output_format", "output_quality"],
3279
- pipelineManaged: ["prompt", "width", "height"],
3280
- fields: {
3281
- num_inference_steps: {
3282
- type: "integer",
3283
- default: 8,
3284
- minimum: 1,
3285
- maximum: 50
3286
- },
3287
- guidance_scale: {
3288
- type: "number",
3289
- default: 0,
3290
- minimum: 0,
3291
- maximum: 20
3292
- },
3293
- seed: {
3294
- type: "integer",
3295
- nullable: true
3296
- },
3297
- go_fast: {
3298
- type: "boolean",
3299
- default: false
3300
- },
3301
- output_format: {
3302
- type: "string",
3303
- default: "jpg",
3304
- enum: ["jpg", "jpeg", "png", "webp"]
3305
- },
3306
- output_quality: {
3307
- type: "integer",
3308
- default: 80,
3309
- minimum: 0,
3310
- maximum: 100
3311
- },
3312
- prompt: {
3313
- type: "string",
3314
- required: true
3315
- },
3316
- width: {
3317
- type: "integer",
3318
- required: true,
3319
- minimum: 64,
3320
- maximum: 2048
3321
- },
3322
- height: {
3323
- type: "integer",
3324
- required: true,
3325
- minimum: 64,
3326
- maximum: 2048
3327
- }
3328
- }
3329
- }
3330
- };
3331
-
3332
- // src/models/t2i/registry.ts
3333
- var modelDefinitions = [
3334
- black_forest_labs_flux_schnell_default,
3335
- black_forest_labs_flux_2_pro_default,
3336
- bytedance_seedream_4_default,
3337
- google_nano_banana_pro_default,
3338
- prunaai_z_image_turbo_default
3339
- ];
3340
- function getSupportedT2IModels() {
3341
- return modelDefinitions;
3342
- }
3343
- function getT2IModel(modelId) {
3344
- const model = modelDefinitions.find((entry) => entry.modelId === modelId);
3345
- if (!model) {
3346
- throw new Error(`Unsupported T2I model: ${modelId}`);
3347
- }
3348
- return model;
3349
- }
3350
-
3351
- // src/models/t2i/options.ts
3352
- function getT2IFieldDefault(modelId, fieldName) {
3353
- const field = getFieldDefinition(modelId, fieldName);
3354
- return field?.default;
3355
- }
3356
- function sanitizeT2IOverrides(modelId, overrides) {
3357
- const model = getT2IModel(modelId);
3358
- return Object.fromEntries(
3359
- Object.entries(overrides).flatMap(([fieldName, value2]) => {
3360
- if (!model.inputOptions.userConfigurable.includes(fieldName)) {
3361
- return [];
3362
- }
3363
- const sanitized = coerceT2IFieldValue(modelId, fieldName, value2);
3364
- return sanitized === void 0 ? [] : [[fieldName, sanitized]];
3365
- })
3366
- );
3367
- }
3368
- function coerceT2IFieldValue(modelId, fieldName, value2) {
3369
- const field = getFieldDefinition(modelId, fieldName);
3370
- if (!field) {
3371
- return void 0;
3372
- }
3373
- if (value2 === null) {
3374
- return field.nullable ? null : void 0;
3375
- }
3376
- if (value2 === void 0) {
3377
- return void 0;
3378
- }
3379
- if (field.type === "boolean") {
3380
- return coerceBoolean(value2);
3381
- }
3382
- if (field.type === "integer") {
3383
- return clampNumber(parseFiniteNumber(value2, true), field.minimum, field.maximum, true, field.nullable);
3384
- }
3385
- if (field.type === "number") {
3386
- return clampNumber(parseFiniteNumber(value2, false), field.minimum, field.maximum, false, field.nullable);
3387
- }
3388
- if (field.type === "string" || typeof value2 === "string") {
3389
- const normalized = String(value2).trim();
3390
- if (!normalized) {
3391
- return field.nullable ? null : void 0;
3392
- }
3393
- if (field.enum && field.enum.length > 0 && !field.allowAnyString && !field.enum.includes(normalized)) {
3394
- return void 0;
3395
- }
3396
- return normalized;
3397
- }
3398
- return value2;
3399
- }
3400
- function getFieldDefinition(modelId, fieldName) {
3401
- const model = getT2IModel(modelId);
3402
- return model.inputOptions.fields[fieldName];
3403
- }
3404
- function coerceBoolean(value2) {
3405
- if (typeof value2 === "boolean") {
3406
- return value2;
3407
- }
3408
- if (typeof value2 === "string") {
3409
- if (value2.trim() === "true") {
3410
- return true;
3411
- }
3412
- if (value2.trim() === "false") {
3413
- return false;
3414
- }
3415
- }
3416
- return void 0;
3417
- }
3418
- function parseFiniteNumber(value2, integer) {
3419
- const parsed = typeof value2 === "number" ? value2 : Number(String(value2).trim());
3420
- if (!Number.isFinite(parsed)) {
3421
- return void 0;
3422
- }
3423
- return integer ? Math.round(parsed) : parsed;
3424
- }
3425
- function clampNumber(value2, minimum, maximum, integer, nullable) {
3426
- if (value2 === void 0) {
3427
- return nullable ? null : void 0;
3428
- }
3429
- let next = value2;
3430
- if (minimum !== void 0) {
3431
- next = Math.max(minimum, next);
3432
- }
3433
- if (maximum !== void 0) {
3434
- next = Math.min(maximum, next);
3435
- }
3436
- return integer ? Math.round(next) : next;
3437
- }
3438
-
3439
2921
  // src/pipeline/analytics.ts
3440
2922
  var LLM_USD_PER_1K_TOKENS = {
3441
2923
  // AUTO-GENERATED:OPENROUTER_PRICING_START
@@ -3461,64 +2943,6 @@ function estimateLlmCostUsd(modelId, usage) {
3461
2943
  const usd = promptTokens / 1e3 * pricing.input + completionTokens / 1e3 * pricing.output;
3462
2944
  return { usd, source: "estimated" };
3463
2945
  }
3464
- function estimateImageCostUsd(modelId, input, imageCount) {
3465
- const model = getT2IModel(modelId);
3466
- const pricingRules = "pricingRules" in model ? model.pricingRules : void 0;
3467
- if (!pricingRules) {
3468
- return { usd: null, source: "unavailable" };
3469
- }
3470
- if (pricingRules.basis === "output_image_count" && "usdPerImage" in pricingRules && typeof pricingRules.usdPerImage === "number") {
3471
- return {
3472
- usd: pricingRules.usdPerImage * imageCount,
3473
- source: "estimated"
3474
- };
3475
- }
3476
- if (pricingRules.basis === "output_image_resolution" && "tiers" in pricingRules && Array.isArray(pricingRules.tiers)) {
3477
- const resolution = typeof input.resolution === "string" ? input.resolution : "fallback";
3478
- const tiers = pricingRules.tiers;
3479
- const tier = tiers.find((entry) => entry.resolution === resolution) ?? tiers.find((entry) => entry.resolution === "fallback");
3480
- if (tier?.usdPerImage !== void 0) {
3481
- return {
3482
- usd: tier.usdPerImage * imageCount,
3483
- source: "estimated"
3484
- };
3485
- }
3486
- }
3487
- if (pricingRules.basis === "output_image_megapixels" && "tiers" in pricingRules && Array.isArray(pricingRules.tiers)) {
3488
- const megapixels = resolveMegapixels(input);
3489
- const tiers = pricingRules.tiers;
3490
- const tier = tiers.find((entry) => megapixels <= entry.maxMegapixels);
3491
- if (tier?.usdPerImage !== void 0) {
3492
- return {
3493
- usd: tier.usdPerImage * imageCount,
3494
- source: "estimated"
3495
- };
3496
- }
3497
- }
3498
- return { usd: null, source: "unavailable" };
3499
- }
3500
- function resolveMegapixels(input) {
3501
- const explicit = toNumber(input.megapixels);
3502
- if (explicit !== null) {
3503
- return explicit;
3504
- }
3505
- const width = toNumber(input.width);
3506
- const height = toNumber(input.height);
3507
- if (width !== null && height !== null && width > 0 && height > 0) {
3508
- return width * height / 1e6;
3509
- }
3510
- return 1;
3511
- }
3512
- function toNumber(value2) {
3513
- if (typeof value2 === "number" && Number.isFinite(value2)) {
3514
- return value2;
3515
- }
3516
- if (typeof value2 === "string" && value2.trim() !== "") {
3517
- const parsed = Number(value2);
3518
- return Number.isFinite(parsed) ? parsed : null;
3519
- }
3520
- return null;
3521
- }
3522
2946
  function sumKnownCosts(values) {
3523
2947
  const known = values.filter((value2) => typeof value2 === "number");
3524
2948
  if (known.length !== values.length) {
@@ -3594,7 +3018,7 @@ async function expandImagePrompts({
3594
3018
  const dryRunStartMs = Date.now();
3595
3019
  prompts.push({
3596
3020
  ...image,
3597
- prompt: `${image.description}, editorial illustration, detailed lighting, modern magazine art direction`
3021
+ prompt: `${image.description}`
3598
3022
  });
3599
3023
  onPromptComplete?.({
3600
3024
  imageId: image.id,
@@ -3661,29 +3085,26 @@ async function expandImagePrompts({
3661
3085
  async function renderExpandedImages({
3662
3086
  prompts,
3663
3087
  settings,
3664
- replicate,
3088
+ limn,
3665
3089
  markdownPath,
3666
3090
  assetDir,
3667
3091
  dryRun,
3668
3092
  onProgress,
3669
3093
  onRenderComplete,
3670
- onInteraction,
3671
- onRetry
3094
+ onInteraction
3672
3095
  }) {
3673
3096
  const renderedImages = [];
3674
3097
  for (let index = 0; index < prompts.length; index += 1) {
3675
3098
  const prompt = prompts[index];
3676
3099
  onProgress?.(`Rendering image ${index + 1}/${prompts.length} with ${settings.t2i.modelId}`);
3677
- const fileName = `${prompt.kind === "cover" ? "cover" : `inline-${prompt.anchorAfterSection}`}-${index + 1}.${resolveOutputFormat(settings)}`;
3100
+ const fileName = `${prompt.kind === "cover" ? "cover" : `inline-${prompt.anchorAfterSection}`}-${index + 1}.png`;
3678
3101
  const outputPath = path6.join(assetDir, fileName);
3679
- if (dryRun || !replicate) {
3102
+ if (dryRun || !limn) {
3680
3103
  const dryRunStartMs = Date.now();
3681
3104
  await writeFile4(outputPath, `Placeholder image for: ${prompt.prompt}
3682
3105
  `, "utf8");
3683
3106
  const outputBytes = Buffer.byteLength(`Placeholder image for: ${prompt.prompt}
3684
3107
  `, "utf8");
3685
- const dryRunInput = createReplicateInput(settings, prompt.prompt, prompt.kind);
3686
- const dryRunCost = estimateImageCostUsd(settings.t2i.modelId, dryRunInput, 1);
3687
3108
  renderedImages.push({
3688
3109
  ...prompt,
3689
3110
  outputPath,
@@ -3698,13 +3119,13 @@ async function renderExpandedImages({
3698
3119
  retries: 0,
3699
3120
  retryBackoffMs: 0,
3700
3121
  outputBytes,
3701
- costUsd: dryRunCost.usd,
3702
- costSource: dryRunCost.source
3122
+ costUsd: null,
3123
+ costSource: "unavailable"
3703
3124
  });
3704
3125
  onInteraction?.({
3705
3126
  stageId: "images",
3706
3127
  operationId: `images:${prompt.id}`,
3707
- provider: "replicate-dry-run",
3128
+ provider: "limn-dry-run",
3708
3129
  modelId: settings.t2i.modelId,
3709
3130
  kind: prompt.kind,
3710
3131
  startedAt: new Date(dryRunStartMs).toISOString(),
@@ -3715,92 +3136,79 @@ async function renderExpandedImages({
3715
3136
  retryBackoffMs: 0,
3716
3137
  status: "succeeded",
3717
3138
  prompt: prompt.prompt,
3718
- input: dryRunInput,
3139
+ input: {},
3719
3140
  errorMessage: null
3720
3141
  });
3721
3142
  continue;
3722
3143
  }
3723
- const input = createReplicateInput(settings, prompt.prompt, prompt.kind);
3144
+ const family = settings.t2i.modelId;
3724
3145
  const renderStartedAtMs = Date.now();
3725
- let runDurationMs = 0;
3726
- let runAttempts = 1;
3727
- let runRetries = 0;
3728
- let runRetryBackoffMs = 0;
3729
3146
  try {
3730
- const output = await replicate.runModel(settings.t2i.modelId, input, {
3731
- onMetrics(metrics) {
3732
- runDurationMs = metrics.durationMs;
3733
- runAttempts = metrics.attempts;
3734
- runRetries = metrics.retries;
3735
- runRetryBackoffMs = metrics.retryBackoffMs;
3736
- },
3737
- onRetry(event) {
3738
- onRetry?.({
3739
- imageId: prompt.id,
3740
- kind: prompt.kind,
3741
- retries: event.retries,
3742
- errorMessage: event.errorMessage
3743
- });
3744
- }
3147
+ const result = await limn.generate(prompt.prompt, family, {
3148
+ replicateModel: settings.t2i.modelId,
3149
+ aspectRatio: "16:9"
3745
3150
  });
3746
- const bytes = await normalizeReplicateOutput(output);
3747
- if (bytes.byteLength < MIN_IMAGE_BYTES) {
3151
+ const ext = mimeTypeToExtension(result.mimeType);
3152
+ const liveFileName = `${prompt.kind === "cover" ? "cover" : `inline-${prompt.anchorAfterSection}`}-${index + 1}.${ext}`;
3153
+ const liveOutputPath = path6.join(assetDir, liveFileName);
3154
+ if (result.image.byteLength < MIN_IMAGE_BYTES) {
3748
3155
  throw new Error(
3749
- `Image ${index + 1} download appears corrupted: only ${bytes.byteLength} bytes received.`
3156
+ `Image ${index + 1} download appears corrupted: only ${result.image.byteLength} bytes received.`
3750
3157
  );
3751
3158
  }
3752
- await writeFile4(outputPath, bytes);
3159
+ await writeFile4(liveOutputPath, result.image);
3753
3160
  renderedImages.push({
3754
3161
  ...prompt,
3755
- outputPath,
3756
- relativePath: relativeAssetPath(markdownPath, outputPath)
3162
+ outputPath: liveOutputPath,
3163
+ relativePath: relativeAssetPath(markdownPath, liveOutputPath)
3757
3164
  });
3758
- const estimatedCost = estimateImageCostUsd(settings.t2i.modelId, input, 1);
3165
+ const costSource = result.analytics.costSource === "unknown" ? "unavailable" : "estimated";
3759
3166
  onRenderComplete?.({
3760
3167
  imageId: prompt.id,
3761
3168
  kind: prompt.kind,
3762
- modelId: settings.t2i.modelId,
3763
- durationMs: runDurationMs,
3764
- attempts: runAttempts,
3765
- retries: runRetries,
3766
- retryBackoffMs: runRetryBackoffMs,
3767
- outputBytes: bytes.byteLength,
3768
- costUsd: estimatedCost.usd,
3769
- costSource: estimatedCost.source
3169
+ modelId: result.modelSlug,
3170
+ durationMs: result.analytics.totalDurationMs,
3171
+ attempts: 1,
3172
+ retries: 0,
3173
+ retryBackoffMs: 0,
3174
+ outputBytes: result.image.byteLength,
3175
+ costUsd: result.analytics.totalEstimatedCostUsd,
3176
+ costSource
3770
3177
  });
3771
3178
  onInteraction?.({
3772
3179
  stageId: "images",
3773
3180
  operationId: `images:${prompt.id}`,
3774
- provider: "replicate",
3775
- modelId: settings.t2i.modelId,
3181
+ provider: "limn",
3182
+ modelId: result.modelSlug,
3776
3183
  kind: prompt.kind,
3777
3184
  startedAt: new Date(renderStartedAtMs).toISOString(),
3778
3185
  endedAt: (/* @__PURE__ */ new Date()).toISOString(),
3779
- durationMs: runDurationMs || Date.now() - renderStartedAtMs,
3780
- attempts: runAttempts,
3781
- retries: runRetries,
3782
- retryBackoffMs: runRetryBackoffMs,
3186
+ durationMs: result.analytics.totalDurationMs,
3187
+ attempts: 1,
3188
+ retries: 0,
3189
+ retryBackoffMs: 0,
3783
3190
  status: "succeeded",
3784
3191
  prompt: prompt.prompt,
3785
- input,
3192
+ input: {},
3786
3193
  errorMessage: null
3787
3194
  });
3788
3195
  } catch (error) {
3196
+ const durationMs = Date.now() - renderStartedAtMs;
3789
3197
  onInteraction?.({
3790
3198
  stageId: "images",
3791
3199
  operationId: `images:${prompt.id}`,
3792
- provider: "replicate",
3200
+ provider: "limn",
3793
3201
  modelId: settings.t2i.modelId,
3794
3202
  kind: prompt.kind,
3795
3203
  startedAt: new Date(renderStartedAtMs).toISOString(),
3796
3204
  endedAt: (/* @__PURE__ */ new Date()).toISOString(),
3797
- durationMs: runDurationMs || Date.now() - renderStartedAtMs,
3798
- attempts: runAttempts,
3799
- retries: runRetries,
3800
- retryBackoffMs: runRetryBackoffMs,
3205
+ durationMs,
3206
+ attempts: 1,
3207
+ retries: 0,
3208
+ retryBackoffMs: 0,
3801
3209
  status: "failed",
3802
3210
  prompt: prompt.prompt,
3803
- input,
3211
+ input: {},
3804
3212
  errorMessage: error instanceof Error ? error.message : "Unknown image render error."
3805
3213
  });
3806
3214
  throw error;
@@ -3832,128 +3240,10 @@ function mergeLlmMetrics(left, right) {
3832
3240
  }
3833
3241
  };
3834
3242
  }
3835
- function createReplicateInput(settings, prompt, kind) {
3836
- const model = getT2IModel(settings.t2i.modelId);
3837
- const overrides = sanitizeT2IOverrides(settings.t2i.modelId, settings.t2i.inputOverrides);
3838
- const input = { ...overrides, prompt };
3839
- if (model.inputOptions.pipelineManaged.includes("aspect_ratio")) {
3840
- input.aspect_ratio = kind === "cover" ? "16:9" : "16:9";
3841
- }
3842
- if (model.inputOptions.pipelineManaged.includes("width")) {
3843
- input.width = 1536;
3844
- }
3845
- if (model.inputOptions.pipelineManaged.includes("height")) {
3846
- input.height = 864;
3847
- }
3848
- if (!("output_format" in input)) {
3849
- const fallback = getT2IFieldDefault(settings.t2i.modelId, "output_format");
3850
- if (typeof fallback === "string") {
3851
- input.output_format = fallback;
3852
- }
3853
- }
3854
- if (!("num_outputs" in input) && "num_outputs" in model.inputOptions.fields) {
3855
- input.num_outputs = coerceT2IFieldValue(settings.t2i.modelId, "num_outputs", getT2IFieldDefault(settings.t2i.modelId, "num_outputs")) ?? 1;
3856
- }
3857
- if (!("max_images" in input) && "max_images" in model.inputOptions.fields) {
3858
- input.max_images = coerceT2IFieldValue(settings.t2i.modelId, "max_images", getT2IFieldDefault(settings.t2i.modelId, "max_images")) ?? 1;
3859
- }
3860
- return input;
3861
- }
3862
- function resolveOutputFormat(settings) {
3863
- const outputFormat = coerceT2IFieldValue(settings.t2i.modelId, "output_format", settings.t2i.inputOverrides.output_format);
3864
- if (typeof outputFormat === "string") {
3865
- return outputFormat === "jpeg" ? "jpg" : outputFormat;
3866
- }
3867
- const fallback = getT2IFieldDefault(settings.t2i.modelId, "output_format");
3868
- const normalizedFallback = typeof fallback === "string" ? fallback : "png";
3869
- return normalizedFallback === "jpeg" ? "jpg" : normalizedFallback;
3870
- }
3871
- async function normalizeReplicateOutput(output) {
3872
- const first = Array.isArray(output) ? output[0] : output;
3873
- if (!first) {
3874
- throw new Error("Replicate returned no image output.");
3875
- }
3876
- if (typeof first === "string") {
3877
- return fetchBytes(first);
3878
- }
3879
- if (first instanceof URL) {
3880
- return fetchBytes(first.toString());
3881
- }
3882
- if (first instanceof Uint8Array) {
3883
- return first;
3884
- }
3885
- if (first instanceof ArrayBuffer) {
3886
- return new Uint8Array(first);
3887
- }
3888
- if (typeof Blob !== "undefined" && first instanceof Blob) {
3889
- return new Uint8Array(await first.arrayBuffer());
3890
- }
3891
- if (typeof ReadableStream !== "undefined" && first instanceof ReadableStream) {
3892
- return new Uint8Array(await new Response(first).arrayBuffer());
3893
- }
3894
- const fromBlobMethod = await maybeBytesFromBlobMethod(first);
3895
- if (fromBlobMethod) {
3896
- return fromBlobMethod;
3897
- }
3898
- const fromUrlMethod = await maybeBytesFromUrlMethod(first);
3899
- if (fromUrlMethod) {
3900
- return fromUrlMethod;
3901
- }
3902
- const fromArrayBufferMethod = await maybeBytesFromArrayBufferMethod(first);
3903
- if (fromArrayBufferMethod) {
3904
- return fromArrayBufferMethod;
3905
- }
3906
- throw new Error("Unsupported Replicate output format.");
3907
- }
3908
- async function maybeBytesFromBlobMethod(value2) {
3909
- if (!isRecord(value2) || typeof value2.blob !== "function") {
3910
- return null;
3911
- }
3912
- const blobLike = await value2.blob();
3913
- if (typeof Blob === "undefined" || !(blobLike instanceof Blob)) {
3914
- return null;
3915
- }
3916
- return new Uint8Array(await blobLike.arrayBuffer());
3917
- }
3918
- async function maybeBytesFromUrlMethod(value2) {
3919
- if (!isRecord(value2) || typeof value2.url !== "function") {
3920
- return null;
3921
- }
3922
- const urlLike = value2.url();
3923
- if (typeof urlLike === "string") {
3924
- return fetchBytes(urlLike);
3925
- }
3926
- if (urlLike instanceof URL) {
3927
- return fetchBytes(urlLike.toString());
3928
- }
3929
- return null;
3930
- }
3931
- async function maybeBytesFromArrayBufferMethod(value2) {
3932
- if (!isRecord(value2) || typeof value2.arrayBuffer !== "function") {
3933
- return null;
3934
- }
3935
- const data = await value2.arrayBuffer();
3936
- if (data instanceof ArrayBuffer) {
3937
- return new Uint8Array(data);
3938
- }
3939
- return null;
3940
- }
3941
- function isRecord(value2) {
3942
- return typeof value2 === "object" && value2 !== null;
3943
- }
3944
- async function fetchBytes(url) {
3945
- const controller = new AbortController();
3946
- const timer = setTimeout(() => controller.abort(), 6e4);
3947
- timer.unref();
3948
- try {
3949
- const response = await fetch(url, { signal: controller.signal });
3950
- if (!response.ok) {
3951
- throw new Error(`Failed to download generated asset from ${url}`);
3952
- }
3953
- return new Uint8Array(await response.arrayBuffer());
3954
- } finally {
3955
- clearTimeout(timer);
3956
- }
3243
+ function mimeTypeToExtension(mimeType) {
3244
+ if (mimeType === "image/jpeg") return "jpg";
3245
+ if (mimeType === "image/webp") return "webp";
3246
+ return "png";
3957
3247
  }
3958
3248
 
3959
3249
  // src/llm/openRouterClient.ts
@@ -4002,9 +3292,9 @@ var OpenRouterClient = class {
4002
3292
  return structured;
4003
3293
  } catch (parseError) {
4004
3294
  if (attempt < 2 && shouldRetryStructuredParseError(parseError)) {
4005
- const backoff = backoffMs2(attempt);
3295
+ const backoff = backoffMs(attempt);
4006
3296
  aggregatedMetrics = recordParseRetryMetrics(aggregatedMetrics, backoff);
4007
- await wait2(backoff);
3297
+ await wait(backoff);
4008
3298
  continue;
4009
3299
  }
4010
3300
  throw parseError;
@@ -4116,7 +3406,7 @@ var OpenRouterClient = class {
4116
3406
  if (!response.ok) {
4117
3407
  const message = json?.error?.message ?? `OpenRouter request failed with status ${response.status}`;
4118
3408
  if (shouldRetryStatus(response.status) && attempt < 2) {
4119
- const backoff = backoffMs2(attempt);
3409
+ const backoff = backoffMs(attempt);
4120
3410
  retries += 1;
4121
3411
  retryBackoffMs += backoff;
4122
3412
  onInteraction?.({
@@ -4136,14 +3426,14 @@ var OpenRouterClient = class {
4136
3426
  responseBody: responseBodyRaw,
4137
3427
  errorMessage: message
4138
3428
  });
4139
- await wait2(backoff);
3429
+ await wait(backoff);
4140
3430
  continue;
4141
3431
  }
4142
3432
  throw new Error(message);
4143
3433
  }
4144
3434
  const content = json.choices?.[0]?.message?.content;
4145
3435
  if (!content && attempt < 2) {
4146
- const backoff = backoffMs2(attempt);
3436
+ const backoff = backoffMs(attempt);
4147
3437
  retries += 1;
4148
3438
  retryBackoffMs += backoff;
4149
3439
  onInteraction?.({
@@ -4163,7 +3453,7 @@ var OpenRouterClient = class {
4163
3453
  responseBody: responseBodyRaw,
4164
3454
  errorMessage: "OpenRouter returned an empty response."
4165
3455
  });
4166
- await wait2(backoff);
3456
+ await wait(backoff);
4167
3457
  continue;
4168
3458
  }
4169
3459
  onInteraction?.({
@@ -4218,11 +3508,11 @@ var OpenRouterClient = class {
4218
3508
  responseBody: responseBodyRaw,
4219
3509
  errorMessage: lastError.message
4220
3510
  });
4221
- if (attempt < 2 && shouldRetryError2(lastError)) {
4222
- const backoff = backoffMs2(attempt);
3511
+ if (attempt < 2 && shouldRetryError(lastError)) {
3512
+ const backoff = backoffMs(attempt);
4223
3513
  retries += 1;
4224
3514
  retryBackoffMs += backoff;
4225
- await wait2(backoff);
3515
+ await wait(backoff);
4226
3516
  continue;
4227
3517
  }
4228
3518
  } finally {
@@ -4318,7 +3608,7 @@ function isStructuredOutputCompatibilityError(message) {
4318
3608
  function shouldRetryStatus(status) {
4319
3609
  return status === 408 || status === 409 || status === 429 || status >= 500;
4320
3610
  }
4321
- function shouldRetryError2(error) {
3611
+ function shouldRetryError(error) {
4322
3612
  return /timeout|network|fetch|temporarily|aborted/i.test(error.message);
4323
3613
  }
4324
3614
  function shouldRetryStructuredParseError(error) {
@@ -4342,7 +3632,7 @@ function normalizeClientError(error, timeoutMs) {
4342
3632
  }
4343
3633
  return new Error("Unknown OpenRouter client error.");
4344
3634
  }
4345
- function backoffMs2(attempt) {
3635
+ function backoffMs(attempt) {
4346
3636
  return 500 * (attempt + 1);
4347
3637
  }
4348
3638
  function aggregateLlmMetrics(total, next) {
@@ -4376,7 +3666,7 @@ function sumNullableNumber(left, right) {
4376
3666
  }
4377
3667
  return left + right;
4378
3668
  }
4379
- function wait2(ms) {
3669
+ function wait(ms) {
4380
3670
  return new Promise((resolve) => {
4381
3671
  setTimeout(resolve, ms);
4382
3672
  });
@@ -4663,7 +3953,6 @@ async function runPipelineShell(input, options = {}) {
4663
3953
  const stageTracking = /* @__PURE__ */ new Map();
4664
3954
  const stageRetryState = /* @__PURE__ */ new Map();
4665
3955
  const llmOperationRetryState = /* @__PURE__ */ new Map();
4666
- const imageOperationRetryState = /* @__PURE__ */ new Map();
4667
3956
  stageTracking.set("shared-brief", {
4668
3957
  startedAtMs: runStartedAtMs,
4669
3958
  endedAtMs: null,
@@ -4739,7 +4028,11 @@ async function runPipelineShell(input, options = {}) {
4739
4028
  const openRouter = dryRun ? null : new OpenRouterClient(requireSecret(input.config.secrets.openRouterApiKey, "OpenRouter API key"));
4740
4029
  const canRenderImagesLive = Boolean(input.config.secrets.replicateApiToken);
4741
4030
  const imageDryRun = dryRun || !canRenderImagesLive;
4742
- const replicate = imageDryRun ? null : new ReplicateClient(requireSecret(input.config.secrets.replicateApiToken, "Replicate API token"));
4031
+ const limn = imageDryRun ? null : new Limn({
4032
+ openrouterApiKey: input.config.secrets.openRouterApiKey ?? void 0,
4033
+ replicateApiKey: requireSecret(input.config.secrets.replicateApiToken, "Replicate API token"),
4034
+ openrouterModel: input.config.settings.model
4035
+ });
4743
4036
  let contentBrief = writeSession.contentBrief;
4744
4037
  let plan = writeSession.plan;
4745
4038
  let text = writeSession.text;
@@ -5172,7 +4465,7 @@ async function runPipelineShell(input, options = {}) {
5172
4465
  const renderedImages = await renderExpandedImages({
5173
4466
  prompts: imagePrompts,
5174
4467
  settings: input.config.settings,
5175
- replicate,
4468
+ limn,
5176
4469
  markdownPath: primaryMarkdownPath,
5177
4470
  assetDir: sharedAssetDir,
5178
4471
  dryRun: imageDryRun,
@@ -5195,15 +4488,6 @@ async function runPipelineShell(input, options = {}) {
5195
4488
  recordStageCost(stageTracking, "images", metrics.costUsd, metrics.costSource);
5196
4489
  addStageRetries(stageTracking, "images", metrics.retries);
5197
4490
  },
5198
- onRetry(event) {
5199
- const operationKey = `images:${event.imageId}`;
5200
- const previousRetries = imageOperationRetryState.get(operationKey) ?? 0;
5201
- if (event.retries <= previousRetries) {
5202
- return;
5203
- }
5204
- imageOperationRetryState.set(operationKey, event.retries);
5205
- applyRetryUpdate("images", event.retries - previousRetries, event.errorMessage);
5206
- },
5207
4491
  onProgress(detail) {
5208
4492
  stages[4] = {
5209
4493
  ...stages[4],
@@ -5265,7 +4549,7 @@ async function runPipelineShell(input, options = {}) {
5265
4549
  const renderedImages = await renderExpandedImages({
5266
4550
  prompts: imagePrompts,
5267
4551
  settings: input.config.settings,
5268
- replicate,
4552
+ limn,
5269
4553
  markdownPath: primaryMarkdownPath,
5270
4554
  assetDir: sharedAssetDir,
5271
4555
  dryRun: imageDryRun,
@@ -5288,15 +4572,6 @@ async function runPipelineShell(input, options = {}) {
5288
4572
  recordStageCost(stageTracking, "images", metrics.costUsd, metrics.costSource);
5289
4573
  addStageRetries(stageTracking, "images", metrics.retries);
5290
4574
  },
5291
- onRetry(event) {
5292
- const operationKey = `images:${event.imageId}`;
5293
- const previousRetries = imageOperationRetryState.get(operationKey) ?? 0;
5294
- if (event.retries <= previousRetries) {
5295
- return;
5296
- }
5297
- imageOperationRetryState.set(operationKey, event.retries);
5298
- applyRetryUpdate("images", event.retries - previousRetries, event.errorMessage);
5299
- },
5300
4575
  onProgress(detail) {
5301
4576
  stages[4] = {
5302
4577
  ...stages[4],
@@ -6011,13 +5286,13 @@ function buildPrimaryCoverPrompt(contentBrief, primaryContentType, primaryMarkdo
6011
5286
  description: `Cover image for ${primaryContentType}`,
6012
5287
  anchorAfterSection: null,
6013
5288
  prompt: [
6014
- `Editorial cover image for ${primaryContentType}.`,
5289
+ `Cover image for ${primaryContentType}.`,
6015
5290
  `Core angle: ${contentBrief.description}`,
6016
5291
  `Audience: ${contentBrief.targetAudience}`,
6017
5292
  `Promise: ${contentBrief.corePromise}`,
6018
5293
  `Voice: ${contentBrief.voiceNotes}`,
6019
5294
  `Primary excerpt: ${markdownExcerpt}`,
6020
- "Cinematic composition, clear focal subject, no text overlays, high visual clarity."
5295
+ "Do not include any words, letters, numbers, logos, watermarks, or signage in the image."
6021
5296
  ].join(" ")
6022
5297
  };
6023
5298
  }
@@ -7236,62 +6511,240 @@ async function startIdeonMcpServer() {
7236
6511
  } catch (error) {
7237
6512
  return formatToolError(error);
7238
6513
  }
7239
- }
7240
- );
7241
- server.registerTool(
7242
- "ideon_config_unset",
7243
- {
7244
- title: "Ideon Config Unset",
7245
- description: "Reset a setting to its default or delete a stored secret.",
7246
- inputSchema: configUnsetToolInputSchema
7247
- },
7248
- async (input) => {
7249
- try {
7250
- if (!isConfigKey(input.key)) {
7251
- throw new ReportedError(`Unsupported config key: ${input.key}`);
6514
+ }
6515
+ );
6516
+ server.registerTool(
6517
+ "ideon_config_unset",
6518
+ {
6519
+ title: "Ideon Config Unset",
6520
+ description: "Reset a setting to its default or delete a stored secret.",
6521
+ inputSchema: configUnsetToolInputSchema
6522
+ },
6523
+ async (input) => {
6524
+ try {
6525
+ if (!isConfigKey(input.key)) {
6526
+ throw new ReportedError(`Unsupported config key: ${input.key}`);
6527
+ }
6528
+ await configUnset(input.key);
6529
+ return {
6530
+ content: [
6531
+ {
6532
+ type: "text",
6533
+ text: `Unset ${input.key}.`
6534
+ }
6535
+ ],
6536
+ structuredContent: {
6537
+ key: input.key,
6538
+ updated: true
6539
+ }
6540
+ };
6541
+ } catch (error) {
6542
+ return formatToolError(error);
6543
+ }
6544
+ }
6545
+ );
6546
+ const transport = new StdioServerTransport();
6547
+ await server.connect(transport);
6548
+ }
6549
+ function formatToolError(error) {
6550
+ const message = error instanceof Error ? error.message : "Unknown error";
6551
+ return {
6552
+ content: [{ type: "text", text: message }],
6553
+ isError: true
6554
+ };
6555
+ }
6556
+
6557
+ // src/cli/commands/mcp.ts
6558
+ async function runMcpServeCommand() {
6559
+ await startIdeonMcpServer();
6560
+ }
6561
+
6562
+ // src/cli/commands/settings.tsx
6563
+ import { render } from "ink";
6564
+
6565
+ // src/cli/flows/settingsFlow.tsx
6566
+ import { useEffect, useMemo, useState } from "react";
6567
+ import { Box, Text, useApp, useInput } from "ink";
6568
+ import SelectInput from "ink-select-input";
6569
+ import TextInput from "ink-text-input";
6570
+
6571
+ // src/images/limnModelCatalog.ts
6572
+ import { getSupportedModelCatalog } from "@telepat/limn";
6573
+ function getLimnGenerationModels() {
6574
+ return getSupportedModelCatalog().filter((entry) => entry.generationEnabled);
6575
+ }
6576
+
6577
+ // src/cli/flows/settingsFlowLogic.ts
6578
+ function handleMenuSelect(action, settings, secrets, setEditing, setShowModelSelect, setMenuMode, onDone, exit) {
6579
+ switch (action) {
6580
+ case "openrouter":
6581
+ setEditing({ key: action, label: "OpenRouter API key", value: secrets.openRouterApiKey ?? "" });
6582
+ return;
6583
+ case "replicate":
6584
+ setEditing({ key: action, label: "Replicate API token", value: secrets.replicateApiToken ?? "" });
6585
+ return;
6586
+ case "llm-model":
6587
+ setEditing({ key: action, label: "LLM model", value: settings.model });
6588
+ return;
6589
+ case "notifications-enabled":
6590
+ setEditing({ key: action, label: "Notifications > OS notifications enabled (true|false)", value: String(settings.notifications.enabled) });
6591
+ return;
6592
+ case "temperature":
6593
+ setEditing({ key: action, label: "Temperature", value: String(settings.modelSettings.temperature) });
6594
+ return;
6595
+ case "maxTokens":
6596
+ setEditing({ key: action, label: "Max tokens", value: String(settings.modelSettings.maxTokens) });
6597
+ return;
6598
+ case "topP":
6599
+ setEditing({ key: action, label: "Top p", value: String(settings.modelSettings.topP) });
6600
+ return;
6601
+ case "markdownOutputDir":
6602
+ setEditing({ key: action, label: "Markdown output directory", value: settings.markdownOutputDir });
6603
+ return;
6604
+ case "assetOutputDir":
6605
+ setEditing({ key: action, label: "Asset output directory", value: settings.assetOutputDir });
6606
+ return;
6607
+ case "t2i-settings":
6608
+ setMenuMode("t2i");
6609
+ return;
6610
+ case "t2i-model":
6611
+ setShowModelSelect(true);
6612
+ return;
6613
+ case "t2i-input-overrides":
6614
+ setEditing({
6615
+ key: action,
6616
+ label: "T2I input overrides (JSON)",
6617
+ value: JSON.stringify(settings.t2i.inputOverrides, null, 2)
6618
+ });
6619
+ return;
6620
+ case "t2i-back":
6621
+ setMenuMode("main");
6622
+ return;
6623
+ case "save":
6624
+ onDone({ settings, secrets });
6625
+ exit();
6626
+ return;
6627
+ case "cancel":
6628
+ onDone(null);
6629
+ exit();
6630
+ return;
6631
+ }
6632
+ }
6633
+ function applyEdit(action, value2, settings, secrets, setSettings, setSecrets) {
6634
+ if (action === "openrouter") {
6635
+ setSecrets({ ...secrets, openRouterApiKey: value2.trim() || null });
6636
+ return true;
6637
+ }
6638
+ if (action === "replicate") {
6639
+ setSecrets({ ...secrets, replicateApiToken: value2.trim() || null });
6640
+ return true;
6641
+ }
6642
+ if (action === "llm-model") {
6643
+ setSettings({ ...settings, model: value2.trim() || settings.model });
6644
+ return true;
6645
+ }
6646
+ if (action === "notifications-enabled") {
6647
+ const parsed = parseBooleanOrFallback(value2, settings.notifications.enabled);
6648
+ setSettings({
6649
+ ...settings,
6650
+ notifications: {
6651
+ ...settings.notifications,
6652
+ enabled: parsed
6653
+ }
6654
+ });
6655
+ return true;
6656
+ }
6657
+ if (action === "temperature") {
6658
+ const nextTemperature = clampNumber(parseNumberOrFallback(value2, settings.modelSettings.temperature), 0, 2);
6659
+ setSettings({
6660
+ ...settings,
6661
+ modelSettings: {
6662
+ ...settings.modelSettings,
6663
+ temperature: nextTemperature
6664
+ }
6665
+ });
6666
+ return true;
6667
+ }
6668
+ if (action === "maxTokens") {
6669
+ const nextMaxTokens = Math.max(1, Math.round(parseNumberOrFallback(value2, settings.modelSettings.maxTokens)));
6670
+ setSettings({
6671
+ ...settings,
6672
+ modelSettings: {
6673
+ ...settings.modelSettings,
6674
+ maxTokens: nextMaxTokens
6675
+ }
6676
+ });
6677
+ return true;
6678
+ }
6679
+ if (action === "topP") {
6680
+ const nextTopP = clampNumber(parseNumberOrFallback(value2, settings.modelSettings.topP), 0, 1);
6681
+ setSettings({
6682
+ ...settings,
6683
+ modelSettings: {
6684
+ ...settings.modelSettings,
6685
+ topP: nextTopP
6686
+ }
6687
+ });
6688
+ return true;
6689
+ }
6690
+ if (action === "markdownOutputDir") {
6691
+ setSettings({ ...settings, markdownOutputDir: value2.trim() || settings.markdownOutputDir });
6692
+ return true;
6693
+ }
6694
+ if (action === "assetOutputDir") {
6695
+ setSettings({ ...settings, assetOutputDir: value2.trim() || settings.assetOutputDir });
6696
+ return true;
6697
+ }
6698
+ if (action === "t2i-input-overrides") {
6699
+ const trimmed = value2.trim();
6700
+ if (trimmed.length === 0) {
6701
+ setSettings({
6702
+ ...settings,
6703
+ t2i: {
6704
+ ...settings.t2i,
6705
+ inputOverrides: {}
7252
6706
  }
7253
- await configUnset(input.key);
7254
- return {
7255
- content: [
7256
- {
7257
- type: "text",
7258
- text: `Unset ${input.key}.`
7259
- }
7260
- ],
7261
- structuredContent: {
7262
- key: input.key,
7263
- updated: true
7264
- }
7265
- };
7266
- } catch (error) {
7267
- return formatToolError(error);
6707
+ });
6708
+ return true;
6709
+ }
6710
+ try {
6711
+ const parsed = JSON.parse(trimmed);
6712
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
6713
+ return false;
7268
6714
  }
6715
+ setSettings({
6716
+ ...settings,
6717
+ t2i: {
6718
+ ...settings.t2i,
6719
+ inputOverrides: parsed
6720
+ }
6721
+ });
6722
+ return true;
6723
+ } catch {
6724
+ return false;
7269
6725
  }
7270
- );
7271
- const transport = new StdioServerTransport();
7272
- await server.connect(transport);
6726
+ }
6727
+ return false;
7273
6728
  }
7274
- function formatToolError(error) {
7275
- const message = error instanceof Error ? error.message : "Unknown error";
7276
- return {
7277
- content: [{ type: "text", text: message }],
7278
- isError: true
7279
- };
6729
+ function parseNumberOrFallback(value2, fallback) {
6730
+ const parsed = Number(value2.trim());
6731
+ return Number.isFinite(parsed) ? parsed : fallback;
7280
6732
  }
7281
-
7282
- // src/cli/commands/mcp.ts
7283
- async function runMcpServeCommand() {
7284
- await startIdeonMcpServer();
6733
+ function clampNumber(value2, minimum, maximum) {
6734
+ return Math.min(maximum, Math.max(minimum, value2));
6735
+ }
6736
+ function parseBooleanOrFallback(value2, fallback) {
6737
+ const normalized = value2.trim().toLowerCase();
6738
+ if (normalized === "true") {
6739
+ return true;
6740
+ }
6741
+ if (normalized === "false") {
6742
+ return false;
6743
+ }
6744
+ return fallback;
7285
6745
  }
7286
-
7287
- // src/cli/commands/settings.tsx
7288
- import { render } from "ink";
7289
6746
 
7290
6747
  // src/cli/flows/settingsFlow.tsx
7291
- import { useMemo, useState } from "react";
7292
- import { Box, Text, useApp, useInput } from "ink";
7293
- import SelectInput from "ink-select-input";
7294
- import TextInput from "ink-text-input";
7295
6748
  import { jsx, jsxs } from "react/jsx-runtime";
7296
6749
  function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
7297
6750
  const { exit } = useApp();
@@ -7299,7 +6752,8 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
7299
6752
  const [secrets, setSecrets] = useState(initialSecrets);
7300
6753
  const [editing, setEditing] = useState(null);
7301
6754
  const [showModelSelect, setShowModelSelect] = useState(false);
7302
- const currentModel = getT2IModel(settings.t2i.modelId);
6755
+ const [menuMode, setMenuMode] = useState("main");
6756
+ const currentModelEntry = getLimnGenerationModels().find((m) => m.family === settings.t2i.modelId) ?? getLimnGenerationModels()[0];
7303
6757
  useInput((input, key) => {
7304
6758
  if (key.escape) {
7305
6759
  if (editing) {
@@ -7308,6 +6762,11 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
7308
6762
  }
7309
6763
  if (showModelSelect) {
7310
6764
  setShowModelSelect(false);
6765
+ return;
6766
+ }
6767
+ if (menuMode === "t2i") {
6768
+ setMenuMode("main");
6769
+ return;
7311
6770
  }
7312
6771
  }
7313
6772
  if (key.ctrl && input === "c") {
@@ -7315,11 +6774,28 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
7315
6774
  exit();
7316
6775
  }
7317
6776
  });
6777
+ const formatT2iOverridesSummary = (overrides) => {
6778
+ const count = Object.keys(overrides).length;
6779
+ return count === 0 ? "none" : `${count} override${count === 1 ? "" : "s"}`;
6780
+ };
7318
6781
  const menuItems = useMemo(() => {
7319
- const t2iItems = currentModel.inputOptions.userConfigurable.map((fieldName) => ({
7320
- label: `${fieldName}: ${formatValue(settings.t2i.inputOverrides[fieldName] ?? getT2IFieldDefault(settings.t2i.modelId, fieldName))}`,
7321
- value: `t2i:${fieldName}`
7322
- }));
6782
+ const t2iSubmenu = [
6783
+ {
6784
+ label: `T2I model: ${currentModelEntry?.displayName ?? settings.t2i.modelId}`,
6785
+ value: "t2i-model"
6786
+ },
6787
+ {
6788
+ label: `T2I input overrides: ${formatT2iOverridesSummary(settings.t2i.inputOverrides)}`,
6789
+ value: "t2i-input-overrides"
6790
+ },
6791
+ {
6792
+ label: "Back",
6793
+ value: "t2i-back"
6794
+ }
6795
+ ];
6796
+ if (menuMode === "t2i") {
6797
+ return t2iSubmenu;
6798
+ }
7323
6799
  return [
7324
6800
  {
7325
6801
  label: `OpenRouter API key: ${secrets.openRouterApiKey ? "stored in keychain" : "missing"}`,
@@ -7358,10 +6834,9 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
7358
6834
  value: "assetOutputDir"
7359
6835
  },
7360
6836
  {
7361
- label: `T2I model: ${currentModel.displayName}`,
7362
- value: "t2i-model"
6837
+ label: `T2I settings: ${currentModelEntry?.displayName ?? settings.t2i.modelId}`,
6838
+ value: "t2i-settings"
7363
6839
  },
7364
- ...t2iItems,
7365
6840
  {
7366
6841
  label: "Save and exit",
7367
6842
  value: "save"
@@ -7371,11 +6846,11 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
7371
6846
  value: "cancel"
7372
6847
  }
7373
6848
  ];
7374
- }, [currentModel, secrets.openRouterApiKey, secrets.replicateApiToken, settings]);
6849
+ }, [currentModelEntry, menuMode, secrets.openRouterApiKey, secrets.replicateApiToken, settings]);
7375
6850
  if (showModelSelect) {
7376
- const items = getSupportedT2IModels().map((model) => ({
7377
- label: `${model.displayName} (${model.modelId})`,
7378
- value: model.modelId
6851
+ const items = getLimnGenerationModels().map((model) => ({
6852
+ label: `${model.displayName} (${model.family})`,
6853
+ value: model.family
7379
6854
  }));
7380
6855
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
7381
6856
  /* @__PURE__ */ jsx(Text, { bold: true, color: "cyanBright", children: "Choose T2I Model" }),
@@ -7389,7 +6864,7 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
7389
6864
  ...current,
7390
6865
  t2i: {
7391
6866
  modelId: item.value,
7392
- inputOverrides: sanitizeT2IOverrides(item.value, current.t2i.inputOverrides)
6867
+ inputOverrides: {}
7393
6868
  }
7394
6869
  }));
7395
6870
  setShowModelSelect(false);
@@ -7404,8 +6879,11 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
7404
6879
  {
7405
6880
  editing,
7406
6881
  onSubmit: (value2) => {
7407
- applyEdit(editing.key, value2, settings, secrets, setSettings, setSecrets);
7408
- setEditing(null);
6882
+ const accepted = applyEdit(editing.key, value2, settings, secrets, setSettings, setSecrets);
6883
+ if (accepted) {
6884
+ setEditing(null);
6885
+ }
6886
+ return accepted;
7409
6887
  },
7410
6888
  onCancel: () => {
7411
6889
  setEditing(null);
@@ -7416,7 +6894,13 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
7416
6894
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
7417
6895
  /* @__PURE__ */ jsx(Text, { bold: true, color: "cyanBright", children: "Ideon Settings" }),
7418
6896
  /* @__PURE__ */ jsx(Text, { color: "gray", children: "Enter to edit. Esc backs out of nested menus. Ctrl+C cancels." }),
7419
- /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx(SelectInput, { items: menuItems, onSelect: (item) => handleMenuSelect(item.value, settings, secrets, setEditing, setShowModelSelect, onDone, exit) }) })
6897
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx(
6898
+ SelectInput,
6899
+ {
6900
+ items: menuItems,
6901
+ onSelect: (item) => handleMenuSelect(item.value, settings, secrets, setEditing, setShowModelSelect, setMenuMode, onDone, exit)
6902
+ }
6903
+ ) })
7420
6904
  ] });
7421
6905
  }
7422
6906
  function EditorView({
@@ -7425,6 +6909,11 @@ function EditorView({
7425
6909
  onCancel
7426
6910
  }) {
7427
6911
  const [value2, setValue] = useState(editing.value);
6912
+ const [error, setError] = useState(null);
6913
+ useEffect(() => {
6914
+ setValue(editing.value);
6915
+ setError(null);
6916
+ }, [editing]);
7428
6917
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
7429
6918
  /* @__PURE__ */ jsx(Text, { bold: true, color: "cyanBright", children: editing.label }),
7430
6919
  /* @__PURE__ */ jsx(Text, { color: "gray", children: "Enter saves. Blank value clears nullable secrets and overrides. Esc cancels." }),
@@ -7436,11 +6925,15 @@ function EditorView({
7436
6925
  value: value2,
7437
6926
  onChange: setValue,
7438
6927
  onSubmit: (nextValue) => {
7439
- onSubmit(nextValue);
6928
+ const accepted = onSubmit(nextValue);
6929
+ if (!accepted) {
6930
+ setError("Invalid JSON. Please enter an object or leave blank to clear.");
6931
+ }
7440
6932
  }
7441
6933
  }
7442
6934
  )
7443
6935
  ] }),
6936
+ error ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "red", children: error }) }) : null,
7444
6937
  /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
7445
6938
  "Current value: ",
7446
6939
  editing.value || "(empty)"
@@ -7448,170 +6941,6 @@ function EditorView({
7448
6941
  /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: "Press Esc to return without changes." }) })
7449
6942
  ] });
7450
6943
  }
7451
- function handleMenuSelect(action, settings, secrets, setEditing, setShowModelSelect, onDone, exit) {
7452
- switch (action) {
7453
- case "openrouter":
7454
- setEditing({ key: action, label: "OpenRouter API key", value: secrets.openRouterApiKey ?? "" });
7455
- return;
7456
- case "replicate":
7457
- setEditing({ key: action, label: "Replicate API token", value: secrets.replicateApiToken ?? "" });
7458
- return;
7459
- case "llm-model":
7460
- setEditing({ key: action, label: "LLM model", value: settings.model });
7461
- return;
7462
- case "notifications-enabled":
7463
- setEditing({ key: action, label: "Notifications > OS notifications enabled (true|false)", value: String(settings.notifications.enabled) });
7464
- return;
7465
- case "temperature":
7466
- setEditing({ key: action, label: "Temperature", value: String(settings.modelSettings.temperature) });
7467
- return;
7468
- case "maxTokens":
7469
- setEditing({ key: action, label: "Max tokens", value: String(settings.modelSettings.maxTokens) });
7470
- return;
7471
- case "topP":
7472
- setEditing({ key: action, label: "Top p", value: String(settings.modelSettings.topP) });
7473
- return;
7474
- case "markdownOutputDir":
7475
- setEditing({ key: action, label: "Markdown output directory", value: settings.markdownOutputDir });
7476
- return;
7477
- case "assetOutputDir":
7478
- setEditing({ key: action, label: "Asset output directory", value: settings.assetOutputDir });
7479
- return;
7480
- case "t2i-model":
7481
- setShowModelSelect(true);
7482
- return;
7483
- case "save":
7484
- onDone({ settings, secrets });
7485
- exit();
7486
- return;
7487
- case "cancel":
7488
- onDone(null);
7489
- exit();
7490
- return;
7491
- default:
7492
- if (action.startsWith("t2i:")) {
7493
- const fieldName = action.slice(4);
7494
- const currentModel = getT2IModel(settings.t2i.modelId);
7495
- setEditing({
7496
- key: action,
7497
- label: `${currentModel.displayName} \u2022 ${fieldName}`,
7498
- value: formatEditorValue(settings.t2i.inputOverrides[fieldName] ?? getT2IFieldDefault(settings.t2i.modelId, fieldName))
7499
- });
7500
- }
7501
- }
7502
- }
7503
- function applyEdit(action, value2, settings, secrets, setSettings, setSecrets) {
7504
- if (action === "openrouter") {
7505
- setSecrets({ ...secrets, openRouterApiKey: value2.trim() || null });
7506
- return;
7507
- }
7508
- if (action === "replicate") {
7509
- setSecrets({ ...secrets, replicateApiToken: value2.trim() || null });
7510
- return;
7511
- }
7512
- if (action === "llm-model") {
7513
- setSettings({ ...settings, model: value2.trim() || settings.model });
7514
- return;
7515
- }
7516
- if (action === "notifications-enabled") {
7517
- const parsed = parseBooleanOrFallback(value2, settings.notifications.enabled);
7518
- setSettings({
7519
- ...settings,
7520
- notifications: {
7521
- ...settings.notifications,
7522
- enabled: parsed
7523
- }
7524
- });
7525
- return;
7526
- }
7527
- if (action === "temperature") {
7528
- const nextTemperature = clampNumber2(parseNumberOrFallback(value2, settings.modelSettings.temperature), 0, 2);
7529
- setSettings({
7530
- ...settings,
7531
- modelSettings: {
7532
- ...settings.modelSettings,
7533
- temperature: nextTemperature
7534
- }
7535
- });
7536
- return;
7537
- }
7538
- if (action === "maxTokens") {
7539
- const nextMaxTokens = Math.max(1, Math.round(parseNumberOrFallback(value2, settings.modelSettings.maxTokens)));
7540
- setSettings({
7541
- ...settings,
7542
- modelSettings: {
7543
- ...settings.modelSettings,
7544
- maxTokens: nextMaxTokens
7545
- }
7546
- });
7547
- return;
7548
- }
7549
- if (action === "topP") {
7550
- const nextTopP = clampNumber2(parseNumberOrFallback(value2, settings.modelSettings.topP), 0, 1);
7551
- setSettings({
7552
- ...settings,
7553
- modelSettings: {
7554
- ...settings.modelSettings,
7555
- topP: nextTopP
7556
- }
7557
- });
7558
- return;
7559
- }
7560
- if (action === "markdownOutputDir") {
7561
- setSettings({ ...settings, markdownOutputDir: value2.trim() || settings.markdownOutputDir });
7562
- return;
7563
- }
7564
- if (action === "assetOutputDir") {
7565
- setSettings({ ...settings, assetOutputDir: value2.trim() || settings.assetOutputDir });
7566
- return;
7567
- }
7568
- if (action.startsWith("t2i:")) {
7569
- const fieldName = action.slice(4);
7570
- const parsedValue = coerceT2IFieldValue(settings.t2i.modelId, fieldName, value2);
7571
- const nextOverrides = { ...settings.t2i.inputOverrides };
7572
- if (parsedValue === void 0) {
7573
- delete nextOverrides[fieldName];
7574
- } else {
7575
- nextOverrides[fieldName] = parsedValue;
7576
- }
7577
- setSettings({
7578
- ...settings,
7579
- t2i: {
7580
- ...settings.t2i,
7581
- inputOverrides: nextOverrides
7582
- }
7583
- });
7584
- }
7585
- }
7586
- function formatEditorValue(value2) {
7587
- if (value2 === null || value2 === void 0) {
7588
- return "";
7589
- }
7590
- return String(value2);
7591
- }
7592
- function formatValue(value2) {
7593
- if (value2 === null || value2 === void 0 || value2 === "") {
7594
- return "(default)";
7595
- }
7596
- return String(value2);
7597
- }
7598
- function parseNumberOrFallback(value2, fallback) {
7599
- const parsed = Number(value2.trim());
7600
- return Number.isFinite(parsed) ? parsed : fallback;
7601
- }
7602
- function clampNumber2(value2, minimum, maximum) {
7603
- return Math.min(maximum, Math.max(minimum, value2));
7604
- }
7605
- function parseBooleanOrFallback(value2, fallback) {
7606
- const normalized = value2.trim().toLowerCase();
7607
- if (normalized === "true") {
7608
- return true;
7609
- }
7610
- if (normalized === "false") {
7611
- return false;
7612
- }
7613
- return fallback;
7614
- }
7615
6944
 
7616
6945
  // src/cli/commands/settings.tsx
7617
6946
  import { jsx as jsx2 } from "react/jsx-runtime";
@@ -9565,7 +8894,7 @@ async function runServeCommand(options) {
9565
8894
  }
9566
8895
 
9567
8896
  // src/cli/commands/write.tsx
9568
- import React4, { useEffect as useEffect2, useState as useState4 } from "react";
8897
+ import React4, { useEffect as useEffect3, useState as useState4 } from "react";
9569
8898
  import { render as render2, useApp as useApp3 } from "ink";
9570
8899
  import { createInterface } from "readline/promises";
9571
8900
 
@@ -9692,7 +9021,7 @@ function FinalSummary({
9692
9021
  }
9693
9022
 
9694
9023
  // src/cli/ui/stageRow.tsx
9695
- import { useEffect, useState as useState2 } from "react";
9024
+ import { useEffect as useEffect2, useState as useState2 } from "react";
9696
9025
  import { Box as Box3, Text as Text3 } from "ink";
9697
9026
 
9698
9027
  // src/cli/ui/progressVisibility.ts
@@ -9766,7 +9095,7 @@ function StageRow({
9766
9095
  maxVisibleItems = 12
9767
9096
  }) {
9768
9097
  const [frameIndex, setFrameIndex] = useState2(0);
9769
- useEffect(() => {
9098
+ useEffect2(() => {
9770
9099
  if (!isActive || stage.status !== "running") {
9771
9100
  setFrameIndex(0);
9772
9101
  return;
@@ -9827,7 +9156,7 @@ function ItemRows({
9827
9156
  }
9828
9157
  function ItemRow({ item, isActive }) {
9829
9158
  const [frameIndex, setFrameIndex] = useState2(0);
9830
- useEffect(() => {
9159
+ useEffect2(() => {
9831
9160
  if (!isActive || item.status !== "running") {
9832
9161
  setFrameIndex(0);
9833
9162
  return;
@@ -10430,7 +9759,7 @@ function WriteApp({
10430
9759
  );
10431
9760
  const [result, setResult] = useState4(null);
10432
9761
  const [errorMessage, setErrorMessage] = useState4(null);
10433
- useEffect2(() => {
9762
+ useEffect3(() => {
10434
9763
  let mounted = true;
10435
9764
  void (async () => {
10436
9765
  try {
@@ -10480,7 +9809,7 @@ function WriteApp({
10480
9809
  mounted = false;
10481
9810
  };
10482
9811
  }, [dryRun, enrichLinks2, input, links, unlinks, maxLinks, maxImages, onError, runMode]);
10483
- useEffect2(() => {
9812
+ useEffect3(() => {
10484
9813
  if (!result && !errorMessage) {
10485
9814
  return;
10486
9815
  }