@tarileo/avileo-catalog-cli 0.3.3 → 0.3.5

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/dist/index.js CHANGED
@@ -3046,13 +3046,13 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
3046
3046
  }
3047
3047
  return propValues;
3048
3048
  });
3049
- const isObject3 = isObject;
3049
+ const isObject4 = isObject;
3050
3050
  const catchall = def.catchall;
3051
3051
  let value;
3052
3052
  inst._zod.parse = (payload, ctx) => {
3053
3053
  value ?? (value = _normalized.value);
3054
3054
  const input = payload.value;
3055
- if (!isObject3(input)) {
3055
+ if (!isObject4(input)) {
3056
3056
  payload.issues.push({
3057
3057
  expected: "object",
3058
3058
  code: "invalid_type",
@@ -3179,7 +3179,7 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
3179
3179
  return (payload, ctx) => fn(shape, payload, ctx);
3180
3180
  };
3181
3181
  let fastpass;
3182
- const isObject3 = isObject;
3182
+ const isObject4 = isObject;
3183
3183
  const jit = !globalConfig.jitless;
3184
3184
  const allowsEval2 = allowsEval;
3185
3185
  const fastEnabled = jit && allowsEval2.value;
@@ -3188,7 +3188,7 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
3188
3188
  inst._zod.parse = (payload, ctx) => {
3189
3189
  value ?? (value = _normalized.value);
3190
3190
  const input = payload.value;
3191
- if (!isObject3(input)) {
3191
+ if (!isObject4(input)) {
3192
3192
  payload.issues.push({
3193
3193
  expected: "object",
3194
3194
  code: "invalid_type",
@@ -14686,7 +14686,7 @@ var AgentHttpClient = class {
14686
14686
  } catch {
14687
14687
  abort.cancel();
14688
14688
  if (retry && attempt < 2) {
14689
- await new Promise((resolve) => setTimeout(resolve, retryDelayMs(attempt)));
14689
+ await new Promise((resolve2) => setTimeout(resolve2, retryDelayMs(attempt)));
14690
14690
  continue;
14691
14691
  }
14692
14692
  throw new AgentNetworkError("No se pudo conectar con la API de Avileo", {
@@ -14697,7 +14697,7 @@ var AgentHttpClient = class {
14697
14697
  }
14698
14698
  abort.cancel();
14699
14699
  if (retry && isRetryableStatus(response.status) && attempt < 2) {
14700
- await new Promise((resolve) => setTimeout(resolve, retryDelayMs(attempt, response)));
14700
+ await new Promise((resolve2) => setTimeout(resolve2, retryDelayMs(attempt, response)));
14701
14701
  continue;
14702
14702
  }
14703
14703
  let payload;
@@ -14822,6 +14822,73 @@ var UNIT_CATALOG = {
14822
14822
  };
14823
14823
  var UNIT_SLUGS = Object.keys(UNIT_CATALOG);
14824
14824
  var UnitSlugSchema = external_exports.enum(UNIT_SLUGS);
14825
+ var ASSET_METADATA_NAMESPACE = "avileoAsset";
14826
+ var nullableText = (max) => external_exports.string().trim().min(1).max(max).nullable();
14827
+ var tagsSchema = external_exports.array(
14828
+ external_exports.string().trim().min(1).max(48).refine((tag) => !tag.includes(","), "Cada etiqueta debe ir separada, sin comas").transform((tag) => tag.toLowerCase())
14829
+ ).max(20).transform((tags) => [...new Set(tags)]);
14830
+ var AssetVisualSchema = external_exports.object({
14831
+ view: external_exports.enum(["front", "back", "side", "detail", "packaging", "lifestyle", "other"]).nullable(),
14832
+ background: external_exports.enum(["light", "dark", "transparent", "mixed", "other"]).nullable(),
14833
+ hasTransparency: external_exports.boolean().nullable(),
14834
+ containsText: external_exports.boolean().nullable(),
14835
+ containsPrices: external_exports.boolean().nullable()
14836
+ }).strict();
14837
+ var AssetPresentationSchema = external_exports.object({
14838
+ version: external_exports.literal(1),
14839
+ title: nullableText(160),
14840
+ alt: nullableText(500),
14841
+ tags: tagsSchema,
14842
+ visual: AssetVisualSchema,
14843
+ reviewStatus: external_exports.enum(["incomplete", "reviewed"])
14844
+ }).strict();
14845
+ var hasValues = (value) => Object.values(value).some((item) => item !== void 0);
14846
+ var AssetPresentationPatchSchema = AssetPresentationSchema.omit({ version: true }).partial().extend({
14847
+ visual: AssetVisualSchema.partial().refine(hasValues, "Indica al menos un cambio visual").optional()
14848
+ }).strict().refine(hasValues, "Indica al menos un cambio de metadata");
14849
+ var AssetMetadataUpdateInputSchema = external_exports.object({
14850
+ expectedRevision: external_exports.string().trim().min(1).max(128),
14851
+ description: nullableText(1e3).optional(),
14852
+ presentation: AssetPresentationPatchSchema.optional()
14853
+ }).strict().refine(
14854
+ (input) => input.description !== void 0 || input.presentation !== void 0,
14855
+ "Indica al menos un cambio"
14856
+ );
14857
+ var PublicAssetPresentationSchema = AssetPresentationSchema.omit({ reviewStatus: true });
14858
+ var readableVisualSchema = external_exports.object({
14859
+ view: AssetVisualSchema.shape.view.catch(null),
14860
+ background: AssetVisualSchema.shape.background.catch(null),
14861
+ hasTransparency: AssetVisualSchema.shape.hasTransparency.catch(null),
14862
+ containsText: AssetVisualSchema.shape.containsText.catch(null),
14863
+ containsPrices: AssetVisualSchema.shape.containsPrices.catch(null)
14864
+ });
14865
+ var readablePresentationSchema = external_exports.object({
14866
+ version: external_exports.literal(1),
14867
+ title: AssetPresentationSchema.shape.title.catch(null),
14868
+ alt: AssetPresentationSchema.shape.alt.catch(null),
14869
+ tags: AssetPresentationSchema.shape.tags.catch(() => []),
14870
+ visual: readableVisualSchema.catch(() => readableVisualSchema.parse({})),
14871
+ reviewStatus: AssetPresentationSchema.shape.reviewStatus.catch("incomplete")
14872
+ });
14873
+ var metadataEnvelopeSchema = external_exports.object({
14874
+ [ASSET_METADATA_NAMESPACE]: readablePresentationSchema
14875
+ });
14876
+ var CatalogAssetSchema = external_exports.object({
14877
+ id: external_exports.string().uuid(),
14878
+ filename: external_exports.string(),
14879
+ originalName: external_exports.string(),
14880
+ description: external_exports.string().nullable(),
14881
+ url: external_exports.string().url(),
14882
+ mimeType: external_exports.string(),
14883
+ sizeBytes: external_exports.number().int().nonnegative(),
14884
+ width: external_exports.number().int().nonnegative().nullable(),
14885
+ height: external_exports.number().int().nonnegative().nullable(),
14886
+ presentation: AssetPresentationSchema,
14887
+ revision: external_exports.string().min(1).max(128),
14888
+ createdAt: external_exports.string(),
14889
+ updatedAt: external_exports.string(),
14890
+ updatedByActorId: external_exports.string().nullable()
14891
+ }).strict();
14825
14892
  var uuidSchema = external_exports.string().uuid();
14826
14893
  var timestampSchema = external_exports.string().datetime({ offset: true });
14827
14894
  var nonEmptyTextSchema = external_exports.string().trim().min(1);
@@ -14844,7 +14911,13 @@ var AgentAssetDtoSchema = external_exports.object({
14844
14911
  sizeBytes: external_exports.number().int().nonnegative(),
14845
14912
  url: external_exports.string().url(),
14846
14913
  description: nullableTextSchema,
14847
- createdAt: timestampSchema
14914
+ createdAt: timestampSchema,
14915
+ presentation: AssetPresentationSchema.optional(),
14916
+ revision: external_exports.string().min(1).max(128).optional(),
14917
+ updatedAt: timestampSchema.optional(),
14918
+ updatedByActorId: external_exports.string().nullable().optional(),
14919
+ width: external_exports.number().int().positive().nullable().optional(),
14920
+ height: external_exports.number().int().positive().nullable().optional()
14848
14921
  }).strict();
14849
14922
  var AgentCategoryDtoSchema = external_exports.object({
14850
14923
  id: uuidSchema,
@@ -14945,7 +15018,20 @@ var AgentProductFilterInputSchema = AgentPaginationInputSchema.extend({
14945
15018
  isActive: external_exports.coerce.boolean().optional()
14946
15019
  }).strict();
14947
15020
  var AgentAssetFilterInputSchema = AgentPaginationInputSchema.extend({
14948
- search: external_exports.string().trim().min(1).max(255).optional()
15021
+ search: external_exports.string().trim().min(1).max(255).optional(),
15022
+ tags: external_exports.union([external_exports.string().transform((value) => value.split(",")), external_exports.array(external_exports.string())]).pipe(
15023
+ external_exports.array(
15024
+ external_exports.string().trim().min(1).max(48).transform((tag) => tag.toLowerCase())
15025
+ ).max(20)
15026
+ ).transform((tags) => [...new Set(tags)]).optional(),
15027
+ containsPrices: external_exports.union([external_exports.boolean(), external_exports.enum(["true", "false"]).transform((value) => value === "true")]).optional(),
15028
+ reviewStatus: external_exports.enum(["incomplete", "reviewed"]).optional(),
15029
+ mimeType: external_exports.string().trim().min(1).max(100).optional()
15030
+ }).strict();
15031
+ var AgentUpdateAssetInputSchema = AssetMetadataUpdateInputSchema;
15032
+ var AgentUpdateAssetPayloadSchema = external_exports.object({
15033
+ assetId: uuidSchema,
15034
+ body: AgentUpdateAssetInputSchema
14949
15035
  }).strict();
14950
15036
  var AgentInventoryMovementFilterInputSchema = AgentPaginationInputSchema.extend({
14951
15037
  reason: external_exports.string().trim().min(1).max(500).optional()
@@ -14957,6 +15043,18 @@ var AgentTagAssignmentInputSchema = external_exports.object({
14957
15043
  var imageAssetIdsSchema = external_exports.array(uuidSchema).max(8).refine((assetIds) => new Set(assetIds).size === assetIds.length, {
14958
15044
  message: "Las im\xE1genes no pueden repetirse"
14959
15045
  });
15046
+ var AgentProductImageSourceSchema = external_exports.discriminatedUnion("kind", [
15047
+ external_exports.object({ kind: external_exports.literal("asset"), assetId: uuidSchema }).strict(),
15048
+ external_exports.object({ kind: external_exports.literal("temporary"), temporaryImageId: uuidSchema }).strict()
15049
+ ]);
15050
+ var productImagesSchema = external_exports.array(AgentProductImageSourceSchema).max(8).refine(
15051
+ (images) => new Set(
15052
+ images.map(
15053
+ (image) => image.kind === "asset" ? `asset:${image.assetId}` : `temporary:${image.temporaryImageId}`
15054
+ )
15055
+ ).size === images.length,
15056
+ { message: "Las im\xE1genes no pueden repetirse" }
15057
+ );
14960
15058
  var AgentCreateVariantInputSchema = external_exports.object({
14961
15059
  name: nonEmptyTextSchema.max(50),
14962
15060
  sku: external_exports.string().trim().min(1).max(50).optional(),
@@ -14997,8 +15095,11 @@ var AgentUpdateProductInputSchema = external_exports.object({
14997
15095
  isActive: external_exports.boolean().optional(),
14998
15096
  description: nullableTextSchema.optional(),
14999
15097
  imageAssetIds: imageAssetIdsSchema.optional(),
15098
+ images: productImagesSchema.optional(),
15000
15099
  tagAssignments: external_exports.array(AgentTagAssignmentInputSchema).max(20).optional()
15001
- }).strict().refine((input) => Object.keys(input).length > 0, {
15100
+ }).strict().refine((input) => input.images === void 0 || input.imageAssetIds === void 0, {
15101
+ message: "Usa images o imageAssetIds, no ambos"
15102
+ }).refine((input) => Object.keys(input).length > 0, {
15002
15103
  message: "Debes indicar al menos un campo para actualizar"
15003
15104
  });
15004
15105
  var AgentCreateCategoryInputSchema = external_exports.object({
@@ -15040,8 +15141,21 @@ var AgentBackendImportImageSourceSchema = external_exports.discriminatedUnion("k
15040
15141
  external_exports.object({
15041
15142
  kind: external_exports.literal("url"),
15042
15143
  url: httpsUrlSchema
15144
+ }).strict(),
15145
+ external_exports.object({
15146
+ kind: external_exports.literal("temporary"),
15147
+ temporaryImageId: uuidSchema
15043
15148
  }).strict()
15044
15149
  ]);
15150
+ var AgentCatalogImportTemporaryImageDtoSchema = external_exports.object({
15151
+ id: uuidSchema,
15152
+ url: external_exports.string().url(),
15153
+ filename: nonEmptyTextSchema.max(255),
15154
+ mimeType: nonEmptyTextSchema.max(100),
15155
+ sizeBytes: external_exports.number().int().positive(),
15156
+ contentHash: external_exports.string().regex(/^[a-f0-9]{64}$/),
15157
+ expiresAt: timestampSchema
15158
+ }).strict();
15045
15159
  var ExternalRecordRefSchema = external_exports.object({
15046
15160
  source: nonEmptyTextSchema.max(100),
15047
15161
  externalId: external_exports.string().trim().min(1).max(500).optional(),
@@ -15162,6 +15276,7 @@ var AGENT_OPERATION_NAMES = [
15162
15276
  "catalog.tags.delete",
15163
15277
  "catalog.assets.upload",
15164
15278
  "catalog.assets.import",
15279
+ "catalog.assets.update",
15165
15280
  "catalog.inventory.adjust",
15166
15281
  "catalog.inventory.set",
15167
15282
  "catalog.imports.preview",
@@ -15184,6 +15299,15 @@ var AgentOperationProposalInputSchema = external_exports.object({
15184
15299
  operationName: AgentOperationNameSchema,
15185
15300
  payload: external_exports.record(external_exports.string(), external_exports.unknown())
15186
15301
  }).strict();
15302
+ var AgentOperationBatchInputSchema = external_exports.object({
15303
+ operationIds: external_exports.array(external_exports.string().trim().toLowerCase().uuid()).min(1).max(50).transform((ids) => [...new Set(ids)])
15304
+ }).strict();
15305
+ var AgentOperationBatchDtoSchema = external_exports.object({
15306
+ id: uuidSchema,
15307
+ operationIds: external_exports.array(uuidSchema).min(1).max(50),
15308
+ approvalUrl: external_exports.string().url(),
15309
+ createdAt: timestampSchema
15310
+ }).strict();
15187
15311
  var AgentOperationProposalDtoSchema = external_exports.object({
15188
15312
  id: uuidSchema,
15189
15313
  operationName: AgentOperationNameSchema,
@@ -15377,6 +15501,25 @@ var AgentBusinessResource = class {
15377
15501
  return this.http.request("/api/v1/agent/business/profile");
15378
15502
  }
15379
15503
  };
15504
+ async function uploadTemporaryImage(http, file2, options = {}) {
15505
+ const body = new FormData();
15506
+ body.set("file", file2);
15507
+ const result = await http.request("/api/v1/agent/catalog/imports/images", {
15508
+ method: "POST",
15509
+ body,
15510
+ dispatchIdempotency: true,
15511
+ retry: options.retry
15512
+ });
15513
+ const parsed = AgentCatalogImportTemporaryImageDtoSchema.safeParse(result);
15514
+ if (!parsed.success) {
15515
+ throw new AgentError("La API de Avileo devolvi\xF3 una imagen temporal inv\xE1lida", {
15516
+ code: "INVALID_RESPONSE",
15517
+ status: 201,
15518
+ retryable: false
15519
+ });
15520
+ }
15521
+ return parsed.data;
15522
+ }
15380
15523
  var AgentProductsResource = class {
15381
15524
  constructor(http) {
15382
15525
  this.http = http;
@@ -15388,6 +15531,10 @@ var AgentProductsResource = class {
15388
15531
  get(productId) {
15389
15532
  return this.http.request(`/api/v1/agent/catalog/products/${encodeURIComponent(productId)}`);
15390
15533
  }
15534
+ /** Stage bytes for a product gallery proposal; this does not create a permanent asset. */
15535
+ uploadImage(file2, options) {
15536
+ return uploadTemporaryImage(this.http, file2, options);
15537
+ }
15391
15538
  create(input, approvalId) {
15392
15539
  return this.http.request("/api/v1/agent/catalog/products", {
15393
15540
  method: "POST",
@@ -15527,11 +15674,24 @@ var AgentAssetsResource = class {
15527
15674
  }
15528
15675
  http;
15529
15676
  list(filters = {}) {
15530
- return this.http.request(`/api/v1/agent/catalog/assets${queryString(filters)}`);
15677
+ return this.http.request(
15678
+ `/api/v1/agent/catalog/assets${queryString({
15679
+ ...filters,
15680
+ tags: filters.tags?.length ? filters.tags.join(",") : void 0
15681
+ })}`
15682
+ );
15531
15683
  }
15532
15684
  get(assetId) {
15533
15685
  return this.http.request(`/api/v1/agent/catalog/assets/${encodeURIComponent(assetId)}`);
15534
15686
  }
15687
+ update(assetId, input, approvalId) {
15688
+ return this.http.request(`/api/v1/agent/catalog/assets/${encodeURIComponent(assetId)}`, {
15689
+ method: "PATCH",
15690
+ body: input,
15691
+ dispatchIdempotency: true,
15692
+ approvalId
15693
+ });
15694
+ }
15535
15695
  upload(file2, approvalId) {
15536
15696
  const body = new FormData();
15537
15697
  body.set("file", file2);
@@ -15584,6 +15744,9 @@ var AgentImportsResource = class {
15584
15744
  this.http = http;
15585
15745
  }
15586
15746
  http;
15747
+ uploadImage(file2, options) {
15748
+ return uploadTemporaryImage(this.http, file2, options);
15749
+ }
15587
15750
  preview(input) {
15588
15751
  return this.http.request("/api/v1/agent/catalog/imports/preview", {
15589
15752
  method: "POST",
@@ -15625,6 +15788,15 @@ var AgentOperationsResource = class {
15625
15788
  get(operationId) {
15626
15789
  return this.http.request(`/api/v1/agent/operations/${encodeURIComponent(operationId)}`);
15627
15790
  }
15791
+ createBatch(input) {
15792
+ return this.http.request("/api/v1/agent/operation-batches", {
15793
+ method: "POST",
15794
+ body: input
15795
+ });
15796
+ }
15797
+ getBatch(batchId) {
15798
+ return this.http.request(`/api/v1/agent/operation-batches/${encodeURIComponent(batchId)}`);
15799
+ }
15628
15800
  apply(operationId) {
15629
15801
  return this.http.request(`/api/v1/agent/operations/${encodeURIComponent(operationId)}/apply`, {
15630
15802
  method: "POST",
@@ -16036,7 +16208,194 @@ function registerBusinessCommands(root, getClient) {
16036
16208
  // src/commands/catalog.ts
16037
16209
  import { createHash } from "crypto";
16038
16210
  import { readFile as readFile2, stat } from "fs/promises";
16039
- import { basename, extname } from "path";
16211
+ import { basename as basename2, extname } from "path";
16212
+
16213
+ // src/commands/import-images.ts
16214
+ import { randomUUID as randomUUID2 } from "crypto";
16215
+ import { constants } from "fs";
16216
+ import { open } from "fs/promises";
16217
+ import { basename, dirname as dirname2, resolve } from "path";
16218
+ var MAX_IMAGE_BYTES = 5 * 1024 * 1024;
16219
+ var MAX_TEMPORARY_IMAGES = 100;
16220
+ var MAX_TEMPORARY_BYTES = 100 * 1024 * 1024;
16221
+ function isObject3(value) {
16222
+ return value !== null && typeof value === "object" && !Array.isArray(value);
16223
+ }
16224
+ function invalidInput(message) {
16225
+ throw new CliProtocolError("CLI_VALIDATION_ERROR", message);
16226
+ }
16227
+ function imageMimeType(bytes) {
16228
+ if (bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
16229
+ return "image/png";
16230
+ }
16231
+ if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) {
16232
+ return "image/jpeg";
16233
+ }
16234
+ const signature = bytes.subarray(0, 6).toString("ascii");
16235
+ if (signature === "GIF87a" || signature === "GIF89a") return "image/gif";
16236
+ if (bytes.length >= 12 && bytes.subarray(0, 4).toString("ascii") === "RIFF" && bytes.subarray(8, 12).toString("ascii") === "WEBP") {
16237
+ return "image/webp";
16238
+ }
16239
+ return void 0;
16240
+ }
16241
+ async function readImage(path, remainingBytes) {
16242
+ const handle = await open(path, constants.O_RDONLY | constants.O_NONBLOCK).catch(
16243
+ () => invalidInput("No se pudo abrir una imagen local del cat\xE1logo")
16244
+ );
16245
+ try {
16246
+ const stats = await handle.stat();
16247
+ if (!stats.isFile() || stats.size === 0 || stats.size > MAX_IMAGE_BYTES) {
16248
+ invalidInput("Cada imagen local debe ser un archivo de entre 1 byte y 5 MiB");
16249
+ }
16250
+ if (stats.size > remainingBytes) {
16251
+ invalidInput("Las im\xE1genes locales no pueden superar 100 MiB en total");
16252
+ }
16253
+ const buffer = Buffer.alloc(Math.min(stats.size + 1, MAX_IMAGE_BYTES + 1));
16254
+ let length = 0;
16255
+ while (length < buffer.length) {
16256
+ const { bytesRead } = await handle.read(buffer, length, buffer.length - length, null);
16257
+ if (bytesRead === 0) break;
16258
+ length += bytesRead;
16259
+ }
16260
+ if (length === 0 || length !== stats.size || length > MAX_IMAGE_BYTES) {
16261
+ invalidInput("La imagen local cambi\xF3 durante la lectura o supera 5 MiB");
16262
+ }
16263
+ const bytes = buffer.subarray(0, length);
16264
+ const mimeType = imageMimeType(bytes);
16265
+ if (!mimeType) invalidInput("Solo se permiten im\xE1genes JPEG, PNG, WebP o GIF");
16266
+ return new File([bytes], basename(path), { type: mimeType });
16267
+ } catch (error51) {
16268
+ if (error51 instanceof CliProtocolError) throw error51;
16269
+ invalidInput("No se pudo leer una imagen local del cat\xE1logo");
16270
+ } finally {
16271
+ await handle.close();
16272
+ }
16273
+ }
16274
+ function localImageSources(inputPath) {
16275
+ const paths = /* @__PURE__ */ new Map();
16276
+ const placeholders = /* @__PURE__ */ new Map();
16277
+ const baseDirectory = dirname2(resolve(inputPath));
16278
+ const normalizeImages = (value) => {
16279
+ if (!Array.isArray(value)) return value;
16280
+ return value.map((source) => {
16281
+ if (!isObject3(source) || source.kind !== "file") return source;
16282
+ if (Object.keys(source).some((key) => key !== "kind" && key !== "path") || typeof source.path !== "string" || source.path.trim().length === 0 || source.path.includes("\0")) {
16283
+ invalidInput("Cada imagen local requiere \xFAnicamente kind: file y un path v\xE1lido");
16284
+ }
16285
+ const path = resolve(baseDirectory, source.path);
16286
+ let placeholder = paths.get(path);
16287
+ if (!placeholder) {
16288
+ placeholder = randomUUID2();
16289
+ paths.set(path, placeholder);
16290
+ placeholders.set(placeholder, path);
16291
+ }
16292
+ return { kind: "temporary", temporaryImageId: placeholder };
16293
+ });
16294
+ };
16295
+ return { normalizeImages, paths: placeholders };
16296
+ }
16297
+ async function readLocalImages(paths) {
16298
+ if (paths.size > MAX_TEMPORARY_IMAGES) {
16299
+ invalidInput("La importaci\xF3n no puede preparar m\xE1s de 100 im\xE1genes locales \xFAnicas");
16300
+ }
16301
+ const files = /* @__PURE__ */ new Map();
16302
+ let totalBytes = 0;
16303
+ for (const [placeholder, path] of paths) {
16304
+ const file2 = await readImage(path, MAX_TEMPORARY_BYTES - totalBytes);
16305
+ totalBytes += file2.size;
16306
+ files.set(placeholder, file2);
16307
+ }
16308
+ return files;
16309
+ }
16310
+ async function prepareCatalogImport(input, inputPath, imports) {
16311
+ const { normalizeImages, paths } = localImageSources(inputPath);
16312
+ const normalizeProduct = (value, includeVariants) => {
16313
+ if (!isObject3(value)) return value;
16314
+ return {
16315
+ ...value,
16316
+ ..."images" in value ? { images: normalizeImages(value.images) } : {},
16317
+ ...includeVariants && Array.isArray(value.variants) ? { variants: value.variants.map((variant) => normalizeProduct(variant, false)) } : {}
16318
+ };
16319
+ };
16320
+ const candidate = isObject3(input) && Array.isArray(input.products) ? { ...input, products: input.products.map((product) => normalizeProduct(product, true)) } : input;
16321
+ const parsed = AgentCatalogImportPreviewInputSchema.safeParse(candidate);
16322
+ if (!parsed.success) {
16323
+ invalidInput("La lista de importaci\xF3n contiene campos inv\xE1lidos o no permitidos");
16324
+ }
16325
+ const files = await readLocalImages(paths);
16326
+ const uploaded = /* @__PURE__ */ new Map();
16327
+ for (const [placeholder, file2] of files) {
16328
+ const temporary = await imports.uploadImage(file2);
16329
+ uploaded.set(placeholder, temporary.id);
16330
+ }
16331
+ const replaceImages = (images) => images?.map((source) => {
16332
+ if (source.kind !== "temporary") return source;
16333
+ const id = uploaded.get(source.temporaryImageId);
16334
+ return id ? { kind: "temporary", temporaryImageId: id } : source;
16335
+ });
16336
+ return {
16337
+ ...parsed.data,
16338
+ products: parsed.data.products.map((product) => ({
16339
+ ...product,
16340
+ ...product.images ? { images: replaceImages(product.images) } : {},
16341
+ ...product.variants ? {
16342
+ variants: product.variants.map((variant) => ({
16343
+ ...variant,
16344
+ ...variant.images ? { images: replaceImages(variant.images) } : {}
16345
+ }))
16346
+ } : {}
16347
+ }))
16348
+ };
16349
+ }
16350
+
16351
+ // src/commands/product-images.ts
16352
+ async function prepareProductUpdate(input, inputPath, allowFiles = true) {
16353
+ const { normalizeImages, paths } = localImageSources(inputPath);
16354
+ const candidate = {
16355
+ ...input,
16356
+ ..."images" in input ? { images: normalizeImages(input.images) } : {}
16357
+ };
16358
+ if (!allowFiles && paths.size > 0) {
16359
+ throw new CliProtocolError(
16360
+ "CLI_VALIDATION_ERROR",
16361
+ "Para aplicar im\xE1genes ya aprobadas, omite --from y usa --apply --approval-id. Los archivos cambiados requieren una nueva propuesta."
16362
+ );
16363
+ }
16364
+ const parsed = AgentUpdateProductInputSchema.safeParse(candidate);
16365
+ if (!parsed.success) {
16366
+ throw new CliProtocolError(
16367
+ "CLI_VALIDATION_ERROR",
16368
+ "La actualizaci\xF3n contiene campos inv\xE1lidos: usa images o imageAssetIds, con hasta 8 im\xE1genes \xFAnicas y sin URLs."
16369
+ );
16370
+ }
16371
+ const files = await readLocalImages(paths);
16372
+ return { body: parsed.data, files };
16373
+ }
16374
+ function approvedProductUpdate(operation, approvalId, productId) {
16375
+ const input = operation.input;
16376
+ if (operation.id !== approvalId || operation.operationName !== "catalog.products.update" || !input || typeof input !== "object" || Array.isArray(input) || input.productId !== productId || Object.keys(input).some((key) => key !== "productId" && key !== "body")) {
16377
+ throw new CliProtocolError(
16378
+ "CLI_VALIDATION_ERROR",
16379
+ "La aprobaci\xF3n no corresponde a esta actualizaci\xF3n de producto"
16380
+ );
16381
+ }
16382
+ if (operation.state !== "approved" || !Number.isFinite(Date.parse(operation.expiresAt)) || Date.parse(operation.expiresAt) <= Date.now()) {
16383
+ throw new CliProtocolError(
16384
+ "CLI_VALIDATION_ERROR",
16385
+ "La operaci\xF3n debe estar aprobada y vigente antes de aplicar"
16386
+ );
16387
+ }
16388
+ const parsed = AgentUpdateProductInputSchema.safeParse(input.body);
16389
+ if (!parsed.success) {
16390
+ throw new CliProtocolError(
16391
+ "CLI_VALIDATION_ERROR",
16392
+ "El payload aprobado no es una actualizaci\xF3n de producto v\xE1lida"
16393
+ );
16394
+ }
16395
+ return parsed.data;
16396
+ }
16397
+
16398
+ // src/commands/catalog.ts
16040
16399
  function requireId(value, name) {
16041
16400
  if (!value.trim()) throw new CliProtocolError("CLI_VALIDATION_ERROR", `${name} es obligatorio`);
16042
16401
  return value;
@@ -16114,21 +16473,54 @@ function registerProductCommands(catalog, getClient) {
16114
16473
  (approvalId) => getClient().products.create(input, approvalId)
16115
16474
  );
16116
16475
  });
16117
- withMutationOptions(product.command("update <productId>")).action(
16118
- async (productId, options, command) => {
16119
- const id = requireId(productId, "productId");
16120
- const input = await readMutationInput(options.from);
16121
- await applyOrPreview(
16122
- command,
16123
- input,
16124
- getClient,
16125
- "catalog.products.update",
16126
- { productId: id, body: input },
16127
- (approvalId) => getClient().products.update(id, input, approvalId),
16128
- () => getClient().products.get(id)
16129
- );
16476
+ withMutationOptions(
16477
+ product.command("update <productId>").option("--from <json-file>", "Objeto JSON estructurado"),
16478
+ false
16479
+ ).action(async (productId, options, command) => {
16480
+ const id = requireId(productId, "productId");
16481
+ const { approvalId: rawApprovalId } = command.optsWithGlobals();
16482
+ const approvalId = typeof rawApprovalId === "string" ? requireId(rawApprovalId, "approvalId") : void 0;
16483
+ if (!options.from) {
16484
+ if (!wantsApply(command) || !approvalId) {
16485
+ throw new CliProtocolError(
16486
+ "CLI_VALIDATION_ERROR",
16487
+ "--from es obligatorio salvo al aplicar con --apply --approval-id"
16488
+ );
16489
+ }
16490
+ const client = getClient();
16491
+ const operation = await client.operations.get(approvalId);
16492
+ const body = approvedProductUpdate(operation, approvalId, id);
16493
+ emitSuccess(await client.products.update(id, body, approvalId));
16494
+ return;
16130
16495
  }
16131
- );
16496
+ const input = await readMutationInput(options.from);
16497
+ const prepared = await prepareProductUpdate(input, options.from, approvalId === void 0);
16498
+ if (wantsApply(command) && !approvalId) {
16499
+ const uploaded = /* @__PURE__ */ new Map();
16500
+ for (const [placeholder, file2] of prepared.files) {
16501
+ const image = await getClient().products.uploadImage(file2);
16502
+ uploaded.set(placeholder, image.id);
16503
+ }
16504
+ if (prepared.body.images) {
16505
+ prepared.body.images = prepared.body.images.map((source) => {
16506
+ if (source.kind !== "temporary") return source;
16507
+ return {
16508
+ kind: "temporary",
16509
+ temporaryImageId: uploaded.get(source.temporaryImageId) ?? source.temporaryImageId
16510
+ };
16511
+ });
16512
+ }
16513
+ }
16514
+ await applyOrPreview(
16515
+ command,
16516
+ input,
16517
+ getClient,
16518
+ "catalog.products.update",
16519
+ { productId: id, body: prepared.body },
16520
+ (approvalId2) => getClient().products.update(id, prepared.body, approvalId2),
16521
+ () => getClient().products.get(id)
16522
+ );
16523
+ });
16132
16524
  withMutationOptions(product.command("deactivate <productId>"), false).action(
16133
16525
  async (productId, _options, command) => {
16134
16526
  const id = requireId(productId, "productId");
@@ -16305,10 +16697,38 @@ function registerTagCommands(catalog, getClient) {
16305
16697
  }
16306
16698
  function registerAssetCommands(catalog, getClient) {
16307
16699
  const asset = catalog.command("asset");
16308
- asset.command("list").option("--search <text>").option("--limit <number>", "M\xE1ximo de resultados", parseInteger).option("--offset <number>", "Desplazamiento", parseInteger).action(async (options) => emitSuccess(await getClient().assets.list(options)));
16700
+ asset.command("list").option("--search <text>").option("--tags <tags>", "Etiquetas separadas por comas; deben coincidir todas").option("--contains-prices <true|false>", "Presencia de precios conocida", parseBoolean).option("--review-status <status>", "incomplete o reviewed").option("--mime-type <type>", "Formato MIME o prefijo image/").option("--limit <number>", "M\xE1ximo de resultados", parseInteger).option("--offset <number>", "Desplazamiento", parseInteger).action(async (options) => {
16701
+ const parsed = AgentAssetFilterInputSchema.safeParse(options);
16702
+ if (!parsed.success) {
16703
+ throw new CliProtocolError("CLI_VALIDATION_ERROR", "Los filtros de assets no son v\xE1lidos");
16704
+ }
16705
+ emitSuccess(await getClient().assets.list(parsed.data));
16706
+ });
16309
16707
  asset.command("get <assetId>").action(async (assetId) => {
16310
16708
  emitSuccess(await getClient().assets.get(requireId(assetId, "assetId")));
16311
16709
  });
16710
+ withMutationOptions(
16711
+ asset.command("update <assetId>").option("--diff", "Incluye la ficha actual")
16712
+ ).action(async (assetId, options, command) => {
16713
+ const id = requireId(assetId, "assetId");
16714
+ const parsed = AssetMetadataUpdateInputSchema.safeParse(await readMutationInput(options.from));
16715
+ if (!parsed.success) {
16716
+ throw new CliProtocolError(
16717
+ "CLI_VALIDATION_ERROR",
16718
+ "Metadata inv\xE1lida: incluye expectedRevision y cambios de description o presentation"
16719
+ );
16720
+ }
16721
+ const input = parsed.data;
16722
+ await applyOrPreview(
16723
+ command,
16724
+ input,
16725
+ getClient,
16726
+ "catalog.assets.update",
16727
+ { assetId: id, body: input },
16728
+ (approvalId) => getClient().assets.update(id, input, approvalId),
16729
+ options.diff ? () => getClient().assets.get(id) : void 0
16730
+ );
16731
+ });
16312
16732
  withMutationOptions(
16313
16733
  asset.command("upload").requiredOption("--file <path>", "Archivo local"),
16314
16734
  false
@@ -16321,7 +16741,7 @@ function registerAssetCommands(catalog, getClient) {
16321
16741
  if (!wantsApply(command)) {
16322
16742
  emitSuccess({
16323
16743
  validated: "local",
16324
- file: basename(filePath),
16744
+ file: basename2(filePath),
16325
16745
  sizeBytes: fileStat.size
16326
16746
  });
16327
16747
  return;
@@ -16335,7 +16755,7 @@ function registerAssetCommands(catalog, getClient) {
16335
16755
  );
16336
16756
  }
16337
16757
  const contents = await readFile2(filePath);
16338
- const file2 = new File([contents], basename(filePath), { type: mimeType });
16758
+ const file2 = new File([contents], basename2(filePath), { type: mimeType });
16339
16759
  const rawApprovalId = command.optsWithGlobals().approvalId;
16340
16760
  if (typeof rawApprovalId === "string") {
16341
16761
  emitSuccess(await getClient().assets.upload(file2, requireId(rawApprovalId, "approvalId")));
@@ -16406,27 +16826,13 @@ function registerInventoryCommands(catalog, getClient) {
16406
16826
  }
16407
16827
  );
16408
16828
  }
16409
- function rejectLocalFileSources(value) {
16410
- if (Array.isArray(value)) {
16411
- for (const item of value) rejectLocalFileSources(item);
16412
- return;
16413
- }
16414
- if (!value || typeof value !== "object") return;
16415
- const source = value;
16416
- if (source.kind === "file") {
16417
- throw new CliProtocolError(
16418
- "CLI_VALIDATION_ERROR",
16419
- "Sube cada imagen local con catalog asset upload, apru\xE9bala y usa su assetId"
16420
- );
16421
- }
16422
- for (const nested of Object.values(source)) rejectLocalFileSources(nested);
16423
- }
16424
16829
  function registerImportCommands(catalog, getClient) {
16425
16830
  const imports = catalog.command("import");
16426
16831
  imports.command("preview").requiredOption("--from <json-file>", "Lista estructurada para vista previa").action(async (options) => {
16427
16832
  const input = await readMutationInput(options.from);
16428
- rejectLocalFileSources(input);
16429
- emitSuccess(await getClient().imports.preview(input));
16833
+ const client = getClient();
16834
+ const prepared = await prepareCatalogImport(input, options.from, client.imports);
16835
+ emitSuccess(await client.imports.preview(prepared));
16430
16836
  });
16431
16837
  withMutationOptions(imports.command("apply <importId>"), false).action(
16432
16838
  async (importId, _options, command) => {
@@ -16446,6 +16852,36 @@ function registerImportCommands(catalog, getClient) {
16446
16852
  });
16447
16853
  }
16448
16854
 
16855
+ // src/commands/operation-batches.ts
16856
+ function registerOperationBatchCommands(operation, getClient) {
16857
+ const batch = operation.command("batch").description("Agrupa propuestas para revisarlas con un enlace corto");
16858
+ batch.command("create").description("Crea un grupo de revisi\xF3n; no aprueba ni aplica sus operaciones").requiredOption(
16859
+ "--from <json-file>",
16860
+ "Objeto JSON con operationIds de las propuestas existentes"
16861
+ ).action(async (options) => {
16862
+ const parsed = AgentOperationBatchInputSchema.safeParse(
16863
+ await readMutationInput(options.from)
16864
+ );
16865
+ if (!parsed.success) {
16866
+ throw new CliProtocolError(
16867
+ "CLI_VALIDATION_ERROR",
16868
+ parsed.error.issues[0]?.message ?? "Grupo inv\xE1lido"
16869
+ );
16870
+ }
16871
+ emitSuccess(await getClient().operations.createBatch(parsed.data));
16872
+ });
16873
+ batch.command("get <id>").description("Consulta un grupo y recupera su enlace corto de revisi\xF3n").action(async (id) => {
16874
+ const parsed = AgentOperationBatchDtoSchema.shape.id.safeParse(id.trim());
16875
+ if (!parsed.success) {
16876
+ throw new CliProtocolError(
16877
+ "CLI_VALIDATION_ERROR",
16878
+ "El identificador del grupo debe ser un UUID v\xE1lido"
16879
+ );
16880
+ }
16881
+ emitSuccess(await getClient().operations.getBatch(parsed.data));
16882
+ });
16883
+ }
16884
+
16449
16885
  // src/commands/auth.ts
16450
16886
  import { spawn } from "child_process";
16451
16887
  function safeStartResult(start, browserOpened) {
@@ -16473,7 +16909,7 @@ function safeFinalResult(start, browserOpened, result) {
16473
16909
  };
16474
16910
  }
16475
16911
  function sleep(milliseconds) {
16476
- return new Promise((resolve) => setTimeout(resolve, milliseconds));
16912
+ return new Promise((resolve2) => setTimeout(resolve2, milliseconds));
16477
16913
  }
16478
16914
  function readAgentKeyFromTty(prompt = "Agent Key de Avileo: ") {
16479
16915
  if (!process.stdin.isTTY || !process.stderr.isTTY) {
@@ -16482,7 +16918,7 @@ function readAgentKeyFromTty(prompt = "Agent Key de Avileo: ") {
16482
16918
  "auth configure requiere una terminal interactiva; usa AVILEO_AGENT_API_KEY en CI/CD"
16483
16919
  );
16484
16920
  }
16485
- return new Promise((resolve, reject) => {
16921
+ return new Promise((resolve2, reject) => {
16486
16922
  let value = "";
16487
16923
  const input = process.stdin;
16488
16924
  const output = process.stderr;
@@ -16496,7 +16932,7 @@ function readAgentKeyFromTty(prompt = "Agent Key de Avileo: ") {
16496
16932
  cleanup();
16497
16933
  output.write("\n");
16498
16934
  if (result instanceof Error) reject(result);
16499
- else resolve(result);
16935
+ else resolve2(result);
16500
16936
  };
16501
16937
  const onData = (chunk) => {
16502
16938
  for (const character of String(chunk)) {
@@ -16527,12 +16963,12 @@ function readAgentKeyFromTty(prompt = "Agent Key de Avileo: ") {
16527
16963
  });
16528
16964
  }
16529
16965
  async function openWithExecutable(command, args) {
16530
- return new Promise((resolve) => {
16966
+ return new Promise((resolve2) => {
16531
16967
  const child = spawn(command, args, { stdio: "ignore", shell: false, detached: true });
16532
- child.once("error", () => resolve(false));
16968
+ child.once("error", () => resolve2(false));
16533
16969
  child.once("spawn", () => {
16534
16970
  child.unref();
16535
- resolve(true);
16971
+ resolve2(true);
16536
16972
  });
16537
16973
  });
16538
16974
  }
@@ -16700,6 +17136,7 @@ function createProgram(options = {}) {
16700
17136
  registerBusinessCommands(program, getClient);
16701
17137
  registerCatalogCommands(program, getClient);
16702
17138
  const operation = program.command("operation").description("Consulta y aplica operaciones aprobadas en Avileo");
17139
+ registerOperationBatchCommands(operation, getClient);
16703
17140
  operation.command("status <id>").description("Consulta el estado de una operaci\xF3n").action(async (id) => {
16704
17141
  if (!id.trim()) {
16705
17142
  throw new CliProtocolError("CLI_VALIDATION_ERROR", "id es obligatorio");