monday-cli 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +665 -0
  2. package/README.md +209 -35
  3. package/dist/api/column-types.d.ts +81 -19
  4. package/dist/api/column-types.d.ts.map +1 -1
  5. package/dist/api/column-types.js +44 -11
  6. package/dist/api/column-types.js.map +1 -1
  7. package/dist/api/column-values.d.ts +22 -10
  8. package/dist/api/column-values.d.ts.map +1 -1
  9. package/dist/api/column-values.js +50 -20
  10. package/dist/api/column-values.js.map +1 -1
  11. package/dist/api/file-column-set.d.ts +613 -0
  12. package/dist/api/file-column-set.d.ts.map +1 -0
  13. package/dist/api/file-column-set.js +568 -0
  14. package/dist/api/file-column-set.js.map +1 -0
  15. package/dist/api/raw-write.d.ts +38 -17
  16. package/dist/api/raw-write.d.ts.map +1 -1
  17. package/dist/api/raw-write.js +62 -25
  18. package/dist/api/raw-write.js.map +1 -1
  19. package/dist/api/resolver-error-fold.d.ts +25 -0
  20. package/dist/api/resolver-error-fold.d.ts.map +1 -1
  21. package/dist/api/resolver-error-fold.js +56 -0
  22. package/dist/api/resolver-error-fold.js.map +1 -1
  23. package/dist/commands/board/column-create.d.ts +13 -3
  24. package/dist/commands/board/column-create.d.ts.map +1 -1
  25. package/dist/commands/board/column-create.js +27 -8
  26. package/dist/commands/board/column-create.js.map +1 -1
  27. package/dist/commands/item/create.d.ts +24 -8
  28. package/dist/commands/item/create.d.ts.map +1 -1
  29. package/dist/commands/item/create.js +601 -44
  30. package/dist/commands/item/create.js.map +1 -1
  31. package/dist/commands/item/set.d.ts +33 -3
  32. package/dist/commands/item/set.d.ts.map +1 -1
  33. package/dist/commands/item/set.js +193 -15
  34. package/dist/commands/item/set.js.map +1 -1
  35. package/dist/commands/item/update.d.ts +203 -3
  36. package/dist/commands/item/update.d.ts.map +1 -1
  37. package/dist/commands/item/update.js +1015 -68
  38. package/dist/commands/item/update.js.map +1 -1
  39. package/dist/commands/item/upload.d.ts.map +1 -1
  40. package/dist/commands/item/upload.js +16 -69
  41. package/dist/commands/item/upload.js.map +1 -1
  42. package/dist/commands/update/upload.d.ts.map +1 -1
  43. package/dist/commands/update/upload.js +9 -59
  44. package/dist/commands/update/upload.js.map +1 -1
  45. package/dist/utils/file-source.d.ts +93 -0
  46. package/dist/utils/file-source.d.ts.map +1 -0
  47. package/dist/utils/file-source.js +140 -0
  48. package/dist/utils/file-source.js.map +1 -0
  49. package/package.json +1 -1
@@ -23,14 +23,30 @@
23
23
  * v0.3 because the column-resolution path here assumes the
24
24
  * classic auto-generated-subitems-board model.
25
25
  *
26
- * **Single round-trip** (cli-design §5.8 — hard exit gate). Every
27
- * translated `--set` / `--set-raw` value bundles into one
28
- * `create_item.column_values` (or `create_subitem.column_values`)
29
- * parameter via `bundleColumnValues`; the CLI does **not** fall back
30
- * to `create_item` + `change_multiple_column_values` on partial
31
- * failure. Monday's server-side rejection of any value fails the
32
- * whole mutation, and no item is created agents retry with the
33
- * value fixed.
26
+ * **Single round-trip on the JSON-only path** (cli-design §5.8 —
27
+ * hard exit gate). Every translated non-file `--set` / `--set-raw`
28
+ * value bundles into one `create_item.column_values` (or
29
+ * `create_subitem.column_values`) parameter via `bundleColumnValues`;
30
+ * the CLI does **not** fall back to `create_item` +
31
+ * `change_multiple_column_values` on partial failure. Monday's
32
+ * server-side rejection of any value fails the whole mutation, and
33
+ * no item is created — agents retry with the value fixed.
34
+ *
35
+ * **Two-leg dispatch on the create-time file `--set` carve-out
36
+ * (v0.7-M43 D6 fold).** When any `--set <file-col>=<path>` is
37
+ * present, the action body partitions setEntries (non-file →
38
+ * leg-1's `column_values`; file → leg-2) and routes through
39
+ * `runItemCreateFileDispatch`: leg-1 `create_item` (or
40
+ * `create_subitem`) bundles the non-file column_values atomically;
41
+ * leg-2 `add_file_to_column` attaches the file via M31's multipart
42
+ * wire (reused verbatim through M38's `executeFileColumnSet`). The
43
+ * pair is non-atomic by construction; leg-2 failure surfaces
44
+ * `internal_error` with `details.reason:
45
+ * 'create_then_file_upload_partial_failure'` + `details.cause` +
46
+ * `details.created_item_id` echoing the orphan + a hint directing
47
+ * agents to retry leg-2 only OR rollback via `monday item delete`
48
+ * (cli-design §5.8 orphan-warn atomicity envelope, D1 closure). See
49
+ * `runItemCreateFileDispatch` below for the helper signature.
34
50
  *
35
51
  * **`--position` / `--relative-to` cross-validation.** Both flags
36
52
  * are required together (one without the other → `usage_error`).
@@ -50,22 +66,46 @@
50
66
  import { z } from 'zod';
51
67
  import { ensureSubcommand } from '../types.js';
52
68
  import { emitDryRun, emitMutation } from '../emit.js';
53
- import { resolveClient } from '../../api/resolve-client.js';
69
+ import { resolveClient, } from '../../api/resolve-client.js';
54
70
  import { BoardIdSchema, ItemIdSchema } from '../../types/ids.js';
55
71
  import { parseArgv } from '../parse-argv.js';
56
72
  import { ApiError, MondayCliError, UsageError } from '../../utils/errors.js';
57
73
  import { bundleColumnValues, } from '../../api/column-values.js';
58
74
  import { parseSetRawExpression, } from '../../api/raw-write.js';
59
75
  import { splitSetExpression } from '../../api/set-expression.js';
60
- import { buildResolutionContexts } from '../../api/resolution-context.js';
76
+ import { buildResolutionContexts, } from '../../api/resolution-context.js';
61
77
  import { lookupItemBoard, lookupItemBoardWithHierarchy, } from '../../api/item-board-lookup.js';
62
- import { SourceAggregator, mergeSourceWithPreflight, mergeCacheAge, } from '../../api/source-aggregator.js';
78
+ import { SourceAggregator, mergeCacheAge, mergeSourceWithPreflight, } from '../../api/source-aggregator.js';
63
79
  import { resolveAndTranslate } from '../../api/resolution-pass.js';
64
- import { foldAndRemap } from '../../api/resolver-error-fold.js';
80
+ import { executeFileColumnSet, preCheckM38FileDispatch, } from '../../api/file-column-set.js';
81
+ import { foldAndRemap, mergeResolverWarningsIntoError, } from '../../api/resolver-error-fold.js';
65
82
  import { planCreate } from '../../api/dry-run.js';
66
83
  import { loadBoardMetadata } from '../../api/board-metadata.js';
67
84
  import { assertResponseFieldPresent } from '../../api/response-root.js';
68
85
  import { unwrapOrThrow } from '../../utils/parse-boundary.js';
86
+ import { precheckLocalFile } from '../../utils/file-source.js';
87
+ import { invalidateBoard } from '../../api/cache.js';
88
+ /**
89
+ * Dedupes resolver warnings by `code + message + details.token`.
90
+ * v0.6-M38 IMPL round-2 P3-1 fix: M38 pre-check + downstream
91
+ * resolveAndTranslate / planCreate can both observe the same
92
+ * `stale_cache_refreshed` / `column_token_collision` warning;
93
+ * dedupe ensures each surfaces exactly once. Same shape as the
94
+ * bulk-update `dedupeWarnings` helper.
95
+ */
96
+ const dedupeCreateWarnings = (warnings) => {
97
+ const seen = new Set();
98
+ const out = [];
99
+ for (const w of warnings) {
100
+ const tokenKey = typeof w.details?.token === 'string' ? w.details.token : '';
101
+ const key = `${w.code}|${w.message}|${tokenKey}`;
102
+ if (seen.has(key))
103
+ continue;
104
+ seen.add(key);
105
+ out.push(w);
106
+ }
107
+ return out;
108
+ };
69
109
  // ============================================================
70
110
  // GraphQL mutations. The parent lookup + relative-to lookup queries
71
111
  // live in api/item-board-lookup.ts (R23 lift).
@@ -420,13 +460,13 @@ const resolveCreateMode = async (inputs) => {
420
460
  `creation is deferred. Use a classic board ` +
421
461
  `(hierarchy_type null/"classic"). v0.3 M28 Decision 11 closure: ` +
422
462
  `Monday's sub_items_board carries no subtasks column at API ` +
423
- `2026-01, so depth-2 subitems have no data-model home — v0.6 ` +
463
+ `2026-01, so depth-2 subitems have no data-model home — v0.8 ` +
424
464
  `picks the feature up if Monday surfaces the capability.`, {
425
465
  details: {
426
466
  parent_item_id: dispatch.parentItemId,
427
467
  parent_board_id: parent.boardId,
428
468
  hierarchy_type: parent.hierarchyType,
429
- deferred_to: 'v0.6',
469
+ deferred_to: 'v0.8',
430
470
  },
431
471
  });
432
472
  }
@@ -541,7 +581,7 @@ export const itemCreateCommand = {
541
581
  const parsed = parseArgv(itemCreateCommand.inputSchema, {
542
582
  ...opts,
543
583
  });
544
- const { client, globalFlags, apiVersion, toEmit } = resolveClient(ctx, program.opts());
584
+ const { client, globalFlags, apiVersion, multipart, toEmit } = resolveClient(ctx, program.opts());
545
585
  const dispatch = validateInputShape(parsed);
546
586
  // Argv-parse-time failures fire BEFORE any network call —
547
587
  // splits run on pure strings, JSON parse on `--set-raw` runs
@@ -578,57 +618,177 @@ export const itemCreateCommand = {
578
618
  ? createMode.subitemsBoardId
579
619
  : createMode.boardId;
580
620
  const { dateResolution, peopleResolution, tagResolution, relationResolution } = buildResolutionContexts({ client, ctx, globalFlags });
581
- if (globalFlags.dryRun) {
582
- const result = await planCreate({
621
+ // v0.6-M38 / v0.7-M43 D6 closure — create-time file-set
622
+ // dispatch routes through the column-resolution boundary's
623
+ // pre-check. Pre-checks setEntries against the resolved
624
+ // create-mode board (subitems board for subitem create;
625
+ // --board for top-level), then returns one of:
626
+ // - `kind: 'json'` — no file column in `--set` (existing
627
+ // bundled-create path runs below).
628
+ // - `kind: 'file_create'` — clean single-file `--set`
629
+ // entry; branches into the v0.7-M43 two-leg dispatch
630
+ // helper {@link runItemCreateFileDispatch} (carve-out
631
+ // fold from v0.6-M38's permanent rejection).
632
+ // - Throws `usage_error` with
633
+ // `details.reason: 'multi_file_set_unsupported'` on 2+
634
+ // file `--set` entries (universal mutex rule). The
635
+ // `'mixed_file_and_value_sets'` rule is SUPPRESSED on
636
+ // `'item_create'` callShape per the v0.7-M43 D6 mixed-
637
+ // rule asymmetry — `create_item` natively bundles
638
+ // non-file `column_values` atomically into leg-1.
639
+ // `--set-raw <file-col>=<json>` stays at
640
+ // `translateRawColumnValue`'s D3 permanent rejection (the
641
+ // pre-check inspects setEntries only).
642
+ let m38Source;
643
+ let m38CacheAge = null;
644
+ let m38Warnings = [];
645
+ let m38FileCreate;
646
+ if (setEntries.length > 0) {
647
+ const m38 = await preCheckM38FileDispatch({
583
648
  client,
584
- mode: createMode,
585
- name: parsed.name,
649
+ boardId: resolveBoardId,
586
650
  setEntries,
587
- ...(rawEntries.length === 0 ? {} : { rawEntries }),
651
+ setRawCount: rawEntries.length,
652
+ hasName: true,
653
+ callShape: 'item_create',
654
+ env: ctx.env,
655
+ noCache: globalFlags.noCache,
656
+ });
657
+ m38Source = m38.source;
658
+ m38CacheAge = m38.cacheAgeSeconds;
659
+ m38Warnings = m38.warnings;
660
+ if (m38.kind === 'file_create') {
661
+ m38FileCreate = m38;
662
+ }
663
+ }
664
+ // v0.6-M38 → v0.7-M43 D6 fold — branch into the two-leg
665
+ // dispatch helper. {@link runItemCreateFileDispatch} runs
666
+ // the upfront `precheckLocalFile` + partitions setEntries
667
+ // (non-file → leg-1 `column_values`, file → leg-2
668
+ // `add_file_to_column`) + dispatches leg-1 `create_item`
669
+ // / `create_subitem` then leg-2 `add_file_to_column` under
670
+ // the §5.8 orphan-warn atomicity envelope (D1 closure).
671
+ // Reaching this branch means argv parse + shape validation
672
+ // + duplicate-token check + create-mode resolution + M38
673
+ // pre-check all succeeded.
674
+ if (m38FileCreate !== undefined) {
675
+ await runItemCreateFileDispatch({
676
+ parsed,
677
+ client,
678
+ multipart,
679
+ ctx,
680
+ programOpts: program.opts(),
681
+ apiVersion,
682
+ createMode,
683
+ resolveBoardId,
684
+ setEntries,
685
+ rawEntries,
686
+ m38: m38FileCreate,
687
+ preflightSource: createModeResult.preflightSource,
688
+ preflightCacheAgeSeconds: createModeResult.preflightCacheAgeSeconds,
689
+ metaSource: m38Source,
690
+ metaCacheAgeSeconds: m38CacheAge,
691
+ preflightWarnings: m38Warnings,
588
692
  dateResolution,
589
693
  peopleResolution,
590
694
  tagResolution,
591
695
  relationResolution,
592
- env: ctx.env,
696
+ isDryRun: globalFlags.dryRun,
593
697
  noCache: globalFlags.noCache,
698
+ retries: globalFlags.retry,
699
+ toEmit,
594
700
  });
595
- // Dry-run envelope source folds three legs (Codex M9 P2 #1):
701
+ return;
702
+ }
703
+ if (globalFlags.dryRun) {
704
+ let result;
705
+ try {
706
+ result = await planCreate({
707
+ client,
708
+ mode: createMode,
709
+ name: parsed.name,
710
+ setEntries,
711
+ ...(rawEntries.length === 0 ? {} : { rawEntries }),
712
+ dateResolution,
713
+ peopleResolution,
714
+ tagResolution,
715
+ relationResolution,
716
+ env: ctx.env,
717
+ noCache: globalFlags.noCache,
718
+ });
719
+ }
720
+ catch (err) {
721
+ // Round-3 P3-1 fix: fold M38 pre-check warnings into
722
+ // the failure envelope's `details.resolver_warnings`.
723
+ if (err instanceof MondayCliError && m38Warnings.length > 0) {
724
+ throw mergeResolverWarningsIntoError(err, m38Warnings);
725
+ }
726
+ throw err;
727
+ }
728
+ // Dry-run envelope source folds four legs (Codex M9 P2 #1
729
+ // + v0.6-M38 IMPL round-1 P3-1-equivalent fix):
596
730
  // pre-planner network calls (parent lookup + parent-board
597
- // metadata + --relative-to verification) + planCreate's
731
+ // metadata + --relative-to verification) + the M38
732
+ // pre-check's column-resolution leg + planCreate's
598
733
  // column-resolution legs. `meta.source: "none"` is only
599
734
  // accurate when ZERO wire calls fired.
600
- const dryRunSource = mergeSourceWithPreflight(result.source, createModeResult.preflightSource);
601
- const dryRunCacheAge = mergeCacheAge(result.cacheAgeSeconds, createModeResult.preflightCacheAgeSeconds);
735
+ const dryRunSource = mergeSourceWithPreflight(mergeSourceWithPreflight(result.source, m38Source), createModeResult.preflightSource);
736
+ const dryRunCacheAge = mergeCacheAge(mergeCacheAge(m38CacheAge, result.cacheAgeSeconds), createModeResult.preflightCacheAgeSeconds);
737
+ // Round-2 P3-1 fix: thread M38 pre-check warnings into
738
+ // the dry-run envelope. Pre-check's `stale_cache_refreshed`
739
+ // / `column_token_collision` survive even though
740
+ // downstream `planCreate`'s resolveAndTranslate cache-hits
741
+ // suppress re-emission. Dedupe inline by code+message+
742
+ // token (small N).
602
743
  emitDryRun({
603
744
  ctx,
604
745
  programOpts: program.opts(),
605
746
  plannedChanges: result.plannedChanges,
606
747
  source: dryRunSource,
607
748
  cacheAgeSeconds: dryRunCacheAge,
608
- warnings: result.warnings,
749
+ warnings: dedupeCreateWarnings([...m38Warnings, ...result.warnings]),
609
750
  apiVersion,
610
751
  });
611
752
  return;
612
753
  }
613
- // Live create path. Three-pass resolution + translation
614
- // through the shared helper (R20 lift), then bundle into one
615
- // column_values map and fire the single-round-trip mutation
616
- // per cli-design §5.8.
617
- const resolutionResult = await resolveAndTranslate({
618
- client,
619
- boardId: resolveBoardId,
620
- setEntries,
621
- rawEntries,
622
- dateResolution,
623
- peopleResolution,
624
- tagResolution,
625
- relationResolution,
626
- env: ctx.env,
627
- noCache: globalFlags.noCache,
628
- });
629
- const collectedWarnings = [
754
+ // Live create path (JSON-only). Three-pass resolution +
755
+ // translation through the shared helper (R20 lift), then
756
+ // bundle into one column_values map and fire the single-
757
+ // round-trip mutation per cli-design §5.8. Reaching this
758
+ // block means the v0.7-M43 `file_create` dispatch helper
759
+ // did NOT apply (no file `--set` entries present) — the
760
+ // helper returned above; this remaining path is JSON-only
761
+ // single-round-trip.
762
+ let resolutionResult;
763
+ try {
764
+ resolutionResult = await resolveAndTranslate({
765
+ client,
766
+ boardId: resolveBoardId,
767
+ setEntries,
768
+ rawEntries,
769
+ dateResolution,
770
+ peopleResolution,
771
+ tagResolution,
772
+ relationResolution,
773
+ env: ctx.env,
774
+ noCache: globalFlags.noCache,
775
+ });
776
+ }
777
+ catch (err) {
778
+ // Round-3 P3-1 fix: fold M38 pre-check warnings into the
779
+ // live failure envelope's `details.resolver_warnings`.
780
+ if (err instanceof MondayCliError && m38Warnings.length > 0) {
781
+ throw mergeResolverWarningsIntoError(err, m38Warnings);
782
+ }
783
+ throw err;
784
+ }
785
+ // Round-2 P3-1 fix: include M38 pre-check warnings.
786
+ // Deduped by code+message+token via the same shape as
787
+ // bulk-update's `dedupeWarnings` helper.
788
+ const collectedWarnings = dedupeCreateWarnings([
789
+ ...m38Warnings,
630
790
  ...resolutionResult.warnings,
631
- ];
791
+ ]);
632
792
  const resolvedIds = resolutionResult.resolvedIds;
633
793
  // Live envelope source aggregates four legs (Codex M9 P2 #1):
634
794
  // pre-planner network calls (parent lookup + parent metadata
@@ -638,6 +798,13 @@ export const itemCreateCommand = {
638
798
  // planner there can claim 'none' (no wire call); the class
639
799
  // shape only handles `EnvelopeSource = 'live'|'cache'|'mixed'`.
640
800
  const sourceAgg = new SourceAggregator();
801
+ // v0.6-M38 IMPL round-1 P3-1-equivalent fix: thread the M38
802
+ // pre-check's source/cacheAge into source aggregation so the
803
+ // live envelope's `meta.source` reflects the pre-check wire
804
+ // leg (a `live` BoardMetadata fetch when cache cold).
805
+ if (m38Source !== undefined) {
806
+ sourceAgg.record(m38Source, m38CacheAge);
807
+ }
641
808
  if (resolutionResult.source !== undefined) {
642
809
  sourceAgg.record(resolutionResult.source, resolutionResult.cacheAgeSeconds);
643
810
  }
@@ -832,4 +999,394 @@ const executeCreateSubitem = async (client, inputs) => {
832
999
  response,
833
1000
  };
834
1001
  };
1002
+ /**
1003
+ * Two-leg create-time file dispatch helper. Runs:
1004
+ *
1005
+ * 1. Single upfront {@link precheckLocalFile} on the file `--set`
1006
+ * raw value. Local-only; failure surfaces `usage_error` with
1007
+ * `details.reason: 'file_not_readable'` / `'file_empty'`
1008
+ * BEFORE either wire leg fires (atomicity-before-wire
1009
+ * discipline per cli-design §5.8).
1010
+ * 2. Partitions `inputs.setEntries` by `token`: the entry whose
1011
+ * `token === inputs.m38.token` routes to leg-2; every other
1012
+ * entry routes to leg-1's `column_values`. `inputs.rawEntries`
1013
+ * route to leg-1 verbatim — `--set-raw <file-col>=<json>` is
1014
+ * rejected upstream at `translateRawColumnValue` per D3
1015
+ * permanent rejection, so by this point no raw entry targets
1016
+ * a file column.
1017
+ * 3. **Dry-run branch.** Invokes {@link planCreate} on the
1018
+ * non-file entries (handles resolution + diff cell build),
1019
+ * then appends a synthetic entry-2
1020
+ * `operation: 'add_file_to_column'` carrying `column_id` +
1021
+ * `file_path` (argv-derived) + `filename` + `file_size_bytes`
1022
+ * from the upfront pre-check. Emits both entries together
1023
+ * via `emitDryRun`; no multipart wire round-trip fires.
1024
+ * Entry-2 omits `item_id` (the item doesn't exist yet at
1025
+ * dry-run time).
1026
+ * 4. **Live branch.** `resolveAndTranslate` on the non-file
1027
+ * entries yields the leg-1 `column_values`; leg-1 invokes
1028
+ * `executeCreateItem` / `executeCreateSubitem` depending on
1029
+ * `inputs.createMode.kind`; leg-2 builds a
1030
+ * {@link FileColumnSetEntry} from the pre-check + leg-1's
1031
+ * new item ID and invokes {@link executeFileColumnSet}.
1032
+ * On full success, a single
1033
+ * {@link invalidateBoard} fires (mirroring M38 single-item
1034
+ * + M42 bulk file-dispatch invalidate timing — leg-2
1035
+ * mutates the file column's asset state wire-side).
1036
+ * 5. **Leg-1 failure.** Routes through {@link foldAndRemap} to
1037
+ * surface `column_archived` on cache-served file-column
1038
+ * resolution against an archived column (mirrors the JSON
1039
+ * path's F4 remap above). No orphan handle because no item
1040
+ * was created.
1041
+ * 6. **Leg-2 failure (orphan-warn per D1).** Catches
1042
+ * `MondayCliError`, applies {@link foldAndRemap} to surface
1043
+ * `column_archived` etc., then wraps the remapped error in
1044
+ * a fresh `ApiError('internal_error', ...)` carrying
1045
+ * `details.reason: 'create_then_file_upload_partial_failure'`
1046
+ * + `details.created_item_id` + `details.column_id` +
1047
+ * `details.cause` (JSON projection of the remapped error) +
1048
+ * `details.hint` (retry-leg-2-only / rollback). The board
1049
+ * cache is NOT invalidated on leg-2 failure — leg-1's
1050
+ * `create_item` doesn't affect cached board metadata
1051
+ * (mirrors the JSON-only create path's no-invalidate), and
1052
+ * leg-2 failure means no file mutation occurred wire-side.
1053
+ */
1054
+ const runItemCreateFileDispatch = async (inputs) => {
1055
+ // 1) Upfront local file pre-check. Atomicity-before-wire per
1056
+ // cli-design §5.8: pre-checks fire BEFORE any wire round-trip
1057
+ // so a bad path surfaces `usage_error` (exit 1) with
1058
+ // `details.reason: 'file_not_readable'` / `'file_empty'`
1059
+ // without burning either wire leg. R-v0.6-NEW-1 4th-consumer
1060
+ // site (M31 upload + M38 single-item + M42 file-bulk + here);
1061
+ // 5-consumer graduation threshold not yet hit.
1062
+ const precheck = await precheckLocalFile(inputs.m38.rawValue);
1063
+ // 2) Partition setEntries: the file entry's `token` matches
1064
+ // `inputs.m38.token` (the pre-check identified it); every
1065
+ // other token goes to leg-1's column_values.
1066
+ const nonFileSetEntries = inputs.setEntries.filter((e) => e.token !== inputs.m38.token);
1067
+ // 3) Dry-run branch — D2 closure. Two `planned_changes` entries
1068
+ // without burning either wire leg. planCreate handles
1069
+ // non-file column resolution + diff cells; entry-2 is built
1070
+ // locally from the pre-check.
1071
+ if (inputs.isDryRun) {
1072
+ let planResult;
1073
+ try {
1074
+ planResult = await planCreate({
1075
+ client: inputs.client,
1076
+ mode: inputs.createMode,
1077
+ name: inputs.parsed.name,
1078
+ setEntries: nonFileSetEntries,
1079
+ ...(inputs.rawEntries.length === 0
1080
+ ? {}
1081
+ : { rawEntries: inputs.rawEntries }),
1082
+ dateResolution: inputs.dateResolution,
1083
+ peopleResolution: inputs.peopleResolution,
1084
+ tagResolution: inputs.tagResolution,
1085
+ relationResolution: inputs.relationResolution,
1086
+ env: inputs.ctx.env,
1087
+ noCache: inputs.noCache,
1088
+ });
1089
+ }
1090
+ catch (err) {
1091
+ // Fold M38 pre-check warnings into the dry-run failure
1092
+ // envelope's `details.resolver_warnings`. `mergeResolverWarningsIntoError`
1093
+ // is a no-op on empty `preflightWarnings`, so no length guard
1094
+ // is needed; the JSON path's analogous catch at
1095
+ // `create.ts:944-951` keeps the guard inline for parity with
1096
+ // its older pattern, but the M43 helper collapses it (smaller
1097
+ // branch surface). Non-`MondayCliError` programmer bugs
1098
+ // re-throw unchanged.
1099
+ if (err instanceof MondayCliError) {
1100
+ throw mergeResolverWarningsIntoError(err, inputs.preflightWarnings);
1101
+ }
1102
+ throw err;
1103
+ }
1104
+ // Source aggregates four legs (planner + M38 pre-check +
1105
+ // pre-planner preflight) — mirrors the JSON-path dry-run
1106
+ // aggregation pattern at lines 959-966 above. planCreate may
1107
+ // return `source: 'none'` when its no-set short-circuit fires
1108
+ // (only relevant here if the call had ONLY the file `--set`),
1109
+ // so the standalone `mergeSourceWithPreflight` helper is used
1110
+ // rather than the SourceAggregator class (which doesn't model
1111
+ // 'none').
1112
+ const dryRunSource = mergeSourceWithPreflight(mergeSourceWithPreflight(planResult.source, inputs.metaSource), inputs.preflightSource);
1113
+ const dryRunCacheAge = mergeCacheAge(mergeCacheAge(inputs.metaCacheAgeSeconds, planResult.cacheAgeSeconds), inputs.preflightCacheAgeSeconds);
1114
+ // Entry-2: `add_file_to_column` planned-change. Mirrors M31's
1115
+ // dry-run shape minus `item_id` (the item doesn't exist yet).
1116
+ // `file_path` is the argv-derived raw value per cli-design §6.4;
1117
+ // resolved absolute path lives in pre-check rejections, not in
1118
+ // the success-shaped dry-run envelope.
1119
+ const fileEntry = {
1120
+ operation: 'add_file_to_column',
1121
+ column_id: inputs.m38.columnId,
1122
+ file_path: inputs.m38.rawValue,
1123
+ filename: precheck.filename,
1124
+ file_size_bytes: precheck.fileSizeBytes,
1125
+ };
1126
+ const plannedChanges = [
1127
+ ...planResult.plannedChanges,
1128
+ fileEntry,
1129
+ ];
1130
+ emitDryRun({
1131
+ ctx: inputs.ctx,
1132
+ programOpts: inputs.programOpts,
1133
+ plannedChanges,
1134
+ source: dryRunSource,
1135
+ cacheAgeSeconds: dryRunCacheAge,
1136
+ warnings: dedupeCreateWarnings([
1137
+ ...inputs.preflightWarnings,
1138
+ ...planResult.warnings,
1139
+ ]),
1140
+ apiVersion: inputs.apiVersion,
1141
+ });
1142
+ return;
1143
+ }
1144
+ // 4) Live branch — leg-1 (create_item / create_subitem) then
1145
+ // leg-2 (add_file_to_column). resolveAndTranslate on non-file
1146
+ // entries yields leg-1's column_values; the M38 pre-check
1147
+ // already warmed the column-resolution cache so this leg
1148
+ // typically hits cache (source folds to `mixed` once leg-1
1149
+ // + leg-2 record `live`).
1150
+ let resolutionResult;
1151
+ try {
1152
+ resolutionResult = await resolveAndTranslate({
1153
+ client: inputs.client,
1154
+ boardId: inputs.resolveBoardId,
1155
+ setEntries: nonFileSetEntries,
1156
+ rawEntries: inputs.rawEntries,
1157
+ dateResolution: inputs.dateResolution,
1158
+ peopleResolution: inputs.peopleResolution,
1159
+ tagResolution: inputs.tagResolution,
1160
+ relationResolution: inputs.relationResolution,
1161
+ env: inputs.ctx.env,
1162
+ noCache: inputs.noCache,
1163
+ });
1164
+ }
1165
+ catch (err) {
1166
+ // Same shape as the planCreate catch above — collapse the
1167
+ // `&& length > 0` guard (no-op on empty) for a smaller branch
1168
+ // surface than the JSON path's pattern at
1169
+ // `create.ts:1010-1022`.
1170
+ if (err instanceof MondayCliError) {
1171
+ throw mergeResolverWarningsIntoError(err, inputs.preflightWarnings);
1172
+ }
1173
+ throw err;
1174
+ }
1175
+ // Combined warnings: M38 pre-check + resolveAndTranslate. Deduped
1176
+ // by code+message+token (a stale_cache_refreshed seen at pre-check
1177
+ // AND at resolveAndTranslate for the same token collapses to one
1178
+ // entry). Mirrors the JSON path's dedupeCreateWarnings usage.
1179
+ const collectedWarnings = dedupeCreateWarnings([...inputs.preflightWarnings, ...resolutionResult.warnings]);
1180
+ // Source aggregator across every wire leg that fires (or that
1181
+ // already fired upstream). Records preflight + M38 pre-check +
1182
+ // resolveAndTranslate legs; records `'live'` once at the end of
1183
+ // each successful wire mutation (mergeSource is idempotent for a
1184
+ // constant `'live'` second leg so recording leg-1 + leg-2 separately
1185
+ // is byte-equivalent to recording once).
1186
+ const sourceAgg = new SourceAggregator();
1187
+ // Defensive: the helper is entered only when the action body's
1188
+ // `preCheckM38FileDispatch` returned `kind: 'file_create'`, which
1189
+ // guarantees `metaSource` is defined (the M38 pre-check populates
1190
+ // `source` for every entry it resolves). The `undefined` arm is
1191
+ // unreachable from any callable test surface.
1192
+ /* c8 ignore next 3 */
1193
+ if (inputs.metaSource !== undefined) {
1194
+ sourceAgg.record(inputs.metaSource, inputs.metaCacheAgeSeconds);
1195
+ }
1196
+ if (resolutionResult.source !== undefined) {
1197
+ sourceAgg.record(resolutionResult.source, resolutionResult.cacheAgeSeconds);
1198
+ }
1199
+ if (inputs.preflightSource !== undefined) {
1200
+ sourceAgg.record(inputs.preflightSource, inputs.preflightCacheAgeSeconds);
1201
+ }
1202
+ // resolved_ids — file token + non-file tokens. Mirrors §6.4
1203
+ // mutation-envelope shape: `{ <token>: <resolved_column_id> }`
1204
+ // for every `--set` / `--set-raw` the agent supplied.
1205
+ const resolvedIds = {
1206
+ [inputs.m38.token]: inputs.m38.columnId,
1207
+ ...resolutionResult.resolvedIds,
1208
+ };
1209
+ // Bundle non-file translated values into leg-1's column_values
1210
+ // parameter. `null` when zero non-file entries (mirrors the JSON
1211
+ // path's "no `--set` values to bundle" treatment — Monday accepts
1212
+ // `column_values: null` distinctly from an empty map).
1213
+ const translated = resolutionResult.translated;
1214
+ const columnValues = translated.length === 0 ? null : bundleColumnValues(translated);
1215
+ // Leg-1: create_item or create_subitem. F4 remap on failure
1216
+ // mirrors the JSON path's catch arm (cache-served resolution +
1217
+ // Monday rejecting as `validation_failed` → check live archived
1218
+ // state). No orphan handle on leg-1 failure because no item was
1219
+ // created.
1220
+ let leg1Result;
1221
+ try {
1222
+ if (inputs.createMode.kind === 'subitem') {
1223
+ leg1Result = await executeCreateSubitem(inputs.client, {
1224
+ parentItemId: inputs.createMode.parentItemId,
1225
+ itemName: inputs.parsed.name,
1226
+ columnValues,
1227
+ createLabelsIfMissing: inputs.parsed.createLabelsIfMissing,
1228
+ });
1229
+ }
1230
+ else {
1231
+ leg1Result = await executeCreateItem(inputs.client, {
1232
+ boardId: inputs.createMode.boardId,
1233
+ itemName: inputs.parsed.name,
1234
+ groupId: inputs.createMode.groupId,
1235
+ position: inputs.createMode.position,
1236
+ columnValues,
1237
+ createLabelsIfMissing: inputs.parsed.createLabelsIfMissing,
1238
+ });
1239
+ }
1240
+ }
1241
+ catch (err) {
1242
+ if (err instanceof MondayCliError) {
1243
+ throw await foldAndRemap({
1244
+ err,
1245
+ warnings: collectedWarnings,
1246
+ client: inputs.client,
1247
+ boardId: inputs.resolveBoardId,
1248
+ columnIds: translated.map((t) => t.columnId),
1249
+ env: inputs.ctx.env,
1250
+ noCache: inputs.noCache,
1251
+ resolutionSource: resolutionResult.source ?? 'live',
1252
+ });
1253
+ }
1254
+ // Defensive: every wire fetcher in `src/api/**` raises typed
1255
+ // errors (ApiError / UsageError) which both extend MondayCliError;
1256
+ // a non-typed throw here indicates a programmer bug in the
1257
+ // wire layer, not a Monday-side failure that needs the F4 remap.
1258
+ /* c8 ignore next */
1259
+ throw err;
1260
+ }
1261
+ // Leg-1 fired live; record into the aggregator.
1262
+ sourceAgg.record('live', null);
1263
+ // Leg-2: add_file_to_column via M31's multipart fetcher. On
1264
+ // success, build the create envelope below; on `MondayCliError`,
1265
+ // surface the D1 orphan-warn envelope with the freshly-created
1266
+ // item ID as the recovery handle.
1267
+ const fileEntry = {
1268
+ columnId: inputs.m38.columnId,
1269
+ columnType: 'file',
1270
+ rawValue: inputs.m38.rawValue,
1271
+ filePath: precheck.filePath,
1272
+ filename: precheck.filename,
1273
+ fileSizeBytes: precheck.fileSizeBytes,
1274
+ };
1275
+ try {
1276
+ await executeFileColumnSet({
1277
+ client: inputs.client,
1278
+ multipart: inputs.multipart,
1279
+ itemId: leg1Result.projected.id,
1280
+ entry: fileEntry,
1281
+ signal: inputs.ctx.signal,
1282
+ retries: inputs.retries,
1283
+ });
1284
+ }
1285
+ catch (err) {
1286
+ if (err instanceof MondayCliError) {
1287
+ // foldAndRemap surfaces `column_archived` for cache-served
1288
+ // file-column resolution against an archived column (cli-
1289
+ // design §6.5 stable-code rule; mirrors M42's file-bulk
1290
+ // fail-fast pattern). The remapped error's code/message
1291
+ // surface in `details.cause` so agents can branch on
1292
+ // leg-2's underlying outcome from the orphan-warn envelope.
1293
+ const remapped = await foldAndRemap({
1294
+ err,
1295
+ warnings: collectedWarnings,
1296
+ client: inputs.client,
1297
+ boardId: inputs.resolveBoardId,
1298
+ columnIds: [inputs.m38.columnId],
1299
+ env: inputs.ctx.env,
1300
+ noCache: inputs.noCache,
1301
+ resolutionSource: inputs.metaSource ?? 'live',
1302
+ });
1303
+ // D1 orphan-warn envelope. Always-internal-error outer
1304
+ // shape; the remapped error embeds as `details.cause` JSON
1305
+ // projection so the agent sees `{code, message, details?}`
1306
+ // for the underlying failure. Error.cause threads the
1307
+ // remapped error for stack debugging in `--debug` mode.
1308
+ const causeProjection = {
1309
+ code: remapped.code,
1310
+ message: remapped.message,
1311
+ };
1312
+ // Defensive: M31's wire-error rewraps (and foldAndRemap) always
1313
+ // populate `details` on the returned error in practice; the
1314
+ // undefined-arm exists only to satisfy the optional shape on
1315
+ // the `MondayCliError` type.
1316
+ /* c8 ignore next 3 */
1317
+ if (remapped.details !== undefined) {
1318
+ causeProjection.details = remapped.details;
1319
+ }
1320
+ const createdItemId = leg1Result.projected.id;
1321
+ throw new ApiError('internal_error', `Item ${createdItemId} was created on board ${inputs.resolveBoardId} ` +
1322
+ `but the file upload to column ${inputs.m38.columnId} failed ` +
1323
+ `(${remapped.code}: ${remapped.message}). The item persists on ` +
1324
+ `Monday; retry the file upload alone with \`monday item set ` +
1325
+ `${createdItemId} <file-col>=<path>\` against the orphan, or roll ` +
1326
+ `back with \`monday item delete ${createdItemId} --yes\`.`, {
1327
+ cause: remapped,
1328
+ details: {
1329
+ reason: 'create_then_file_upload_partial_failure',
1330
+ created_item_id: createdItemId,
1331
+ column_id: inputs.m38.columnId,
1332
+ cause: causeProjection,
1333
+ hint: `the item was created (id ${createdItemId}) but the file ` +
1334
+ `upload failed. Retry leg-2 alone with \`monday item set ` +
1335
+ `${createdItemId} <file-col>=<path>\`; or rollback the ` +
1336
+ `orphan with \`monday item delete ${createdItemId} --yes\` ` +
1337
+ `and re-run the original \`monday item create\` once the ` +
1338
+ `underlying cause is fixed.`,
1339
+ },
1340
+ });
1341
+ }
1342
+ // Non-CliError programmer bug — re-throw to the runner's catch-
1343
+ // all (surfaces as `internal_error` whole-call). Non-typed
1344
+ // throws indicate broken contract, not a Monday-side failure
1345
+ // that needs the orphan-warn decoration; routing them through
1346
+ // D1's `create_then_file_upload_partial_failure` discriminator
1347
+ // would falsely promise an orphan-recovery path for a programmer
1348
+ // bug.
1349
+ /* c8 ignore next */
1350
+ throw err;
1351
+ }
1352
+ // Leg-2 fired live; record into the aggregator. `'live'` + `'live'`
1353
+ // is idempotent under mergeSource so this is the second authoritative
1354
+ // wire-leg record (leg-1 already recorded).
1355
+ sourceAgg.record('live', null);
1356
+ // Both legs succeeded — single board-cache invalidate before emit
1357
+ // (mirrors M38 single-item + M42 fail-fast file-bulk invalidate
1358
+ // timing; leg-2 mutated the file column's asset state wire-side).
1359
+ // The invalidate fires BEFORE emitMutation so a cache-unlink
1360
+ // failure surfaces through the runner's catch-all rather than
1361
+ // double-emitting after the success envelope already hit stdout.
1362
+ await invalidateBoard(inputs.resolveBoardId, inputs.ctx.env);
1363
+ // Map the resolver warnings into the envelope `warnings` slot.
1364
+ const warnings = collectedWarnings;
1365
+ // Success envelope — `data: ItemCreateOutput` from leg-1's
1366
+ // projection. `toEmit(leg1Result.response)` threads leg-1's
1367
+ // `complexity` + `apiVersion` slots; `sourceAgg.result()`
1368
+ // overrides `source` + `cacheAgeSeconds` so the aggregator's
1369
+ // result (cache + live blended across every leg) is
1370
+ // authoritative. Leg-2's complexity is intentionally NOT folded
1371
+ // — the contract pins leg-1's projection on `data`, and the
1372
+ // envelope's `meta.complexity` is the create mutation's cost
1373
+ // (multipart leg-2 has no GraphQL complexity surface).
1374
+ // The output asset from leg-2 is attached to the item server-
1375
+ // side; agents read it back via `monday item get <iid> --columns
1376
+ // <file-col>` if they need the projection (the file-column
1377
+ // dispatch's `Asset` slot is documented per item upload's
1378
+ // envelope; the v0.7-M43 success envelope keeps the canonical
1379
+ // ItemCreateOutput shape so JSON-path and file-path envelopes
1380
+ // remain byte-equivalent on `data`).
1381
+ emitMutation({
1382
+ ctx: inputs.ctx,
1383
+ data: leg1Result.projected,
1384
+ schema: itemCreateOutputSchema,
1385
+ programOpts: inputs.programOpts,
1386
+ warnings,
1387
+ ...inputs.toEmit(leg1Result.response),
1388
+ ...sourceAgg.result(),
1389
+ resolvedIds,
1390
+ });
1391
+ };
835
1392
  //# sourceMappingURL=create.js.map