@cat-factory/app 0.85.0 → 0.87.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.
@@ -16,7 +16,7 @@ import { DOC_KINDS } from '~/types/domain'
16
16
  import ContextDocumentPicker from '~/components/documents/ContextDocumentPicker.vue'
17
17
  import ContextIssuePicker from '~/components/tasks/ContextIssuePicker.vue'
18
18
  import { mergePresetOptionLabel, mergePresetThresholds } from '~/utils/mergePreset'
19
- import { pipelineAllowedForFrame } from '~/utils/pipeline'
19
+ import { pipelineAllowedForManualStart } from '~/utils/pipeline'
20
20
 
21
21
  const ui = useUiStore()
22
22
  const board = useBoardStore()
@@ -197,9 +197,10 @@ const selectedModelPresetLabel = computed(() => {
197
197
  })
198
198
 
199
199
  // Hide UI-testing pipelines (`tester-ui` / `visual-confirmation`) when the target frame has no
200
- // UI to exercise — they'd be refused server-side (see utils/pipeline + the backend gate).
200
+ // UI to exercise — they'd be refused server-side (see utils/pipeline + the backend gate). Also
201
+ // hide `'recurring'`-only pipelines: a one-off task start of one is refused at run start.
201
202
  const selectablePipelines = computed(() =>
202
- pipelines.pipelines.filter((p) => pipelineAllowedForFrame(p, frame.value, board.blocks)),
203
+ pipelines.pipelines.filter((p) => pipelineAllowedForManualStart(p, frame.value, board.blocks)),
203
204
  )
204
205
  const pipelineMenu = computed(() => [
205
206
  [
@@ -6,7 +6,7 @@
6
6
  // pipeline is picked, the workspace issue-tracker choice is surfaced inline (it is
7
7
  // where that pipeline files its ticket) and saved alongside.
8
8
  import type { Recurrence, ScheduleTemplate } from '~/types/recurring'
9
- import { pipelineAllowedForFrame } from '~/utils/pipeline'
9
+ import { pipelineAllowedForSchedule } from '~/utils/pipeline'
10
10
 
11
11
  const ui = useUiStore()
12
12
  const board = useBoardStore()
@@ -52,8 +52,9 @@ function defaultRecurrence(): Recurrence {
52
52
  }
53
53
 
54
54
  // Hide UI-testing pipelines when the frame has no UI to exercise — they'd be refused at run start.
55
+ // Also hide `'one-off'`-only pipelines: attaching one to a schedule is refused server-side.
55
56
  const selectablePipelines = computed(() =>
56
- pipelines.pipelines.filter((p) => pipelineAllowedForFrame(p, frame.value, board.blocks)),
57
+ pipelines.pipelines.filter((p) => pipelineAllowedForSchedule(p, frame.value, board.blocks)),
57
58
  )
58
59
  const pipelineMenu = computed(() => [
59
60
  selectablePipelines.value.map((p) => ({
@@ -201,8 +201,32 @@ const ITEM_ICON: Record<string, string> = {
201
201
  </script>
202
202
 
203
203
  <template>
204
+ <!-- ===================== Redacted: repo access denied ===================== -->
205
+ <!-- This service frame is backed by a repo linked via another member's personal access
206
+ token that the signed-in user can't reach. The server scrubbed its contents; the SPA
207
+ shows only the internal id + a "Permission denied" placeholder (never the repo). -->
204
208
  <div
205
- v-if="block"
209
+ v-if="block?.accessDenied"
210
+ class="w-56 overflow-hidden rounded-xl border border-slate-700 bg-slate-900/90 shadow-xl backdrop-blur"
211
+ :data-block-id="block.id"
212
+ data-testid="frame-access-denied"
213
+ >
214
+ <div class="flex items-center gap-2 border-b border-slate-800 px-3 py-2">
215
+ <span class="i-lucide-lock h-4 w-4 shrink-0 text-slate-400" />
216
+ <span class="truncate text-sm font-semibold text-slate-200">{{
217
+ t('board.frame.accessDenied.title')
218
+ }}</span>
219
+ </div>
220
+ <div class="px-3 py-3">
221
+ <p class="text-[11px] leading-snug text-slate-400">
222
+ {{ t('board.frame.accessDenied.hint') }}
223
+ </p>
224
+ <code class="mt-2 block truncate font-mono text-[11px] text-slate-500">{{ block.id }}</code>
225
+ </div>
226
+ </div>
227
+
228
+ <div
229
+ v-else-if="block"
206
230
  class="relative"
207
231
  :data-block-id="block.id"
208
232
  @pointerenter="enterFrame(block.id)"
@@ -2,7 +2,7 @@
2
2
  import { onKeyStroke } from '@vueuse/core'
3
3
  import type { Block } from '~/types/domain'
4
4
  import { blockTypeMeta, STATUS_META } from '~/utils/catalog'
5
- import { pipelineAllowedForFrame } from '~/utils/pipeline'
5
+ import { pipelineAllowedForManualStart } from '~/utils/pipeline'
6
6
  import PipelineProgress from '~/components/pipeline/PipelineProgress.vue'
7
7
 
8
8
  const board = useBoardStore()
@@ -27,11 +27,12 @@ const deps = computed(() =>
27
27
  (block.value?.dependsOn ?? []).map((id) => board.getBlock(id)).filter((b): b is Block => !!b),
28
28
  )
29
29
 
30
- // Hide UI-testing pipelines when this block's frame has no UI to exercise (see the backend gate).
30
+ // Hide UI-testing pipelines when this block's frame has no UI to exercise, and `'recurring'`-only
31
+ // pipelines (a manual run of one is refused server-side) — see the backend gate.
31
32
  const runMenu = computed(() => {
32
33
  const frame = block.value ? board.serviceOf(block.value) : undefined
33
34
  return pipelines.pipelines
34
- .filter((p) => pipelineAllowedForFrame(p, frame, board.blocks))
35
+ .filter((p) => pipelineAllowedForManualStart(p, frame, board.blocks))
35
36
  .map((p) => ({
36
37
  label: p.name,
37
38
  icon: 'i-lucide-play',
@@ -27,6 +27,7 @@ const typeItems = useFrameRepoTypeItems()
27
27
  const ui = useUiStore()
28
28
  const github = useGitHubStore()
29
29
  const board = useBoardStore()
30
+ const services = useServicesStore()
30
31
  const toast = useToast()
31
32
  const { freeFramePosition, focusFrame } = useFramePlacement()
32
33
 
@@ -65,9 +66,12 @@ watch(
65
66
  const needsGitHub = computed(() => github.available === true && !github.connected)
66
67
 
67
68
  // Repos already backing a board service can't be added again — UNLESS they're a
68
- // monorepo, which can host several services (each at its own subdirectory).
69
+ // monorepo, which can host several services (each at its own subdirectory). Derived from
70
+ // the account's service catalog (each service carries the repo it targets), since the repo
71
+ // projection no longer carries a repo→block link.
69
72
  const onBoardIds = computed(
70
- () => new Set(github.repos.filter((r) => r.blockId).map((r) => r.githubId)),
73
+ () =>
74
+ new Set(services.catalog.map((s) => s.repoGithubId).filter((id): id is number => id != null)),
71
75
  )
72
76
 
73
77
  // Map an available repo to a combobox item. The label carries the private/monorepo/
@@ -77,6 +81,9 @@ function toRepoItem(r: GitHubAvailableRepo) {
77
81
  const suffix = [
78
82
  r.private ? t('github.addService.repoLabel.private') : '',
79
83
  r.isMonorepo ? t('github.addService.repoLabel.monorepo') : '',
84
+ // Reachable only via the signed-in user's PAT (not the workspace App) — its frame is
85
+ // hidden from members without their own access.
86
+ r.personal ? t('github.addService.repoLabel.personal') : '',
80
87
  onBoard ? t('github.addService.repoLabel.onBoard') : '',
81
88
  ].join('')
82
89
  return { label: `${r.owner}/${r.name}${suffix}`, value: r.githubId, disabled: onBoard }
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import type { Block, BlockStatus } from '~/types/domain'
3
3
  import { blockTypeMeta, STATUS_META } from '~/utils/catalog'
4
- import { pipelineAllowedForFrame } from '~/utils/pipeline'
4
+ import { pipelineAllowedForManualStart } from '~/utils/pipeline'
5
5
  import TaskContextDocs from '~/components/documents/TaskContextDocs.vue'
6
6
  import TaskContextIssues from '~/components/tasks/TaskContextIssues.vue'
7
7
  import TaskAgentConfig from '~/components/panels/inspector/TaskAgentConfig.vue'
@@ -169,12 +169,13 @@ const taskBranchUrl = computed(() => {
169
169
  return base ? `${base}/tree/${pr.branch}` : null
170
170
  })
171
171
 
172
- // Hide UI-testing pipelines when this block's frame has no UI to exercise they'd be refused
173
- // at run start (see utils/pipeline + the backend gate).
172
+ // Hide UI-testing pipelines when this block's frame has no UI to exercise, and `'recurring'`-only
173
+ // pipelines (a manual run of one is refused server-side) they'd be refused at run start (see
174
+ // utils/pipeline + the backend gate).
174
175
  const runMenu = computed(() => {
175
176
  const frame = block.value ? board.serviceOf(block.value) : undefined
176
177
  return pipelines.pipelines
177
- .filter((p) => pipelineAllowedForFrame(p, frame, board.blocks))
178
+ .filter((p) => pipelineAllowedForManualStart(p, frame, board.blocks))
178
179
  .map((p) => ({
179
180
  label: p.name,
180
181
  icon: 'i-lucide-play',
@@ -4,7 +4,7 @@ import { connectionNeighborIds } from '@cat-factory/contracts'
4
4
  import type { Block } from '~/types/domain'
5
5
  import type { WritebackOverride } from '~/types/tracker'
6
6
  import { mergePresetOptionLabel, mergePresetThresholds } from '~/utils/mergePreset'
7
- import { pipelineAllowedForFrame } from '~/utils/pipeline'
7
+ import { pipelineAllowedForManualStart } from '~/utils/pipeline'
8
8
  import InspectorSection from '~/components/panels/inspector/InspectorSection.vue'
9
9
 
10
10
  const props = defineProps<{ block: Block }>()
@@ -132,11 +132,14 @@ function setModelPreset(id: string) {
132
132
  const selectedPipeline = computed(() =>
133
133
  props.block.pipelineId ? pipelines.getPipeline(props.block.pipelineId) : undefined,
134
134
  )
135
- // Hide UI-testing pipelines when this task's frame has no UI to exercise they'd be refused at
136
- // run start (see utils/pipeline + the backend gate).
135
+ // Hide UI-testing pipelines when this task's frame has no UI to exercise, and `'recurring'`-only
136
+ // pipelines (the task's manual Run control can't start one) they'd be refused at run start
137
+ // (see utils/pipeline + the backend gate).
137
138
  const taskFrame = computed(() => board.serviceOf(props.block))
138
139
  const selectablePipelines = computed(() =>
139
- pipelines.pipelines.filter((p) => pipelineAllowedForFrame(p, taskFrame.value, board.blocks)),
140
+ pipelines.pipelines.filter((p) =>
141
+ pipelineAllowedForManualStart(p, taskFrame.value, board.blocks),
142
+ ),
140
143
  )
141
144
  const pipelineMenu = computed(() => [
142
145
  [
@@ -14,6 +14,7 @@ import type {
14
14
  ResyncRequest,
15
15
  } from '~/types/domain'
16
16
  import { useWorkspaceStore } from '~/stores/workspace'
17
+ import { useServicesStore } from '~/stores/services'
17
18
 
18
19
  /**
19
20
  * GitHub integration state: the workspace's App installation, the projected
@@ -62,9 +63,14 @@ export const useGitHubStore = defineStore('github', () => {
62
63
  return repos.value.find((r) => r.githubId === repoGithubId)
63
64
  }
64
65
 
65
- /** The repo linked to a board block (its backing service repo), if any. */
66
+ /**
67
+ * The repo backing a board service frame, if any — resolved through the account-owned
68
+ * Service bound to the frame (the sole repo↔frame linkage; the projection carries no
69
+ * repo→block column).
70
+ */
66
71
  function repoForBlock(blockId: string): GitHubRepo | undefined {
67
- return repos.value.find((r) => r.blockId === blockId)
72
+ const service = useServicesStore().serviceByFrameBlock[blockId]
73
+ return service?.repoGithubId != null ? repoFor(service.repoGithubId) : undefined
68
74
  }
69
75
 
70
76
  function pullsForRepo(repoGithubId: number): GitHubPullRequest[] {
@@ -20,3 +20,33 @@ export function pipelineAllowedForFrame(
20
20
  ): boolean {
21
21
  return !pipelineHasVisualStep(pipeline) || frameAllowsVisualPipeline(frame, blocks)
22
22
  }
23
+
24
+ // Launch-availability filters, the surface counterpart to the backend's start-origin gate (a
25
+ // `'recurring'`-only pipeline can't be started as a one-off manual task, and a `'one-off'`-only
26
+ // pipeline can't be attached to a schedule). `availability` absent ⇒ `'both'` (unrestricted), so
27
+ // legacy/unset pipelines pass both. Composed with {@link pipelineAllowedForFrame} at each picker.
28
+
29
+ /**
30
+ * Whether `pipeline` may be started as a MANUAL one-off task run (the board/inspector Run menus,
31
+ * the add-task modal, the task run-settings default). Excludes `'recurring'`-only pipelines the
32
+ * backend would refuse.
33
+ */
34
+ export function pipelineAllowedForManualStart(
35
+ pipeline: Pipeline,
36
+ frame: Block | undefined,
37
+ blocks: readonly Block[],
38
+ ): boolean {
39
+ return pipeline.availability !== 'recurring' && pipelineAllowedForFrame(pipeline, frame, blocks)
40
+ }
41
+
42
+ /**
43
+ * Whether `pipeline` may be attached to a RECURRING schedule (the recurring-pipeline modal).
44
+ * Excludes `'one-off'`-only pipelines the backend would refuse.
45
+ */
46
+ export function pipelineAllowedForSchedule(
47
+ pipeline: Pipeline,
48
+ frame: Block | undefined,
49
+ blocks: readonly Block[],
50
+ ): boolean {
51
+ return pipeline.availability !== 'one-off' && pipelineAllowedForFrame(pipeline, frame, blocks)
52
+ }
@@ -281,7 +281,11 @@
281
281
  "dragTask": "Drag task",
282
282
  "dragToResize": "Drag to resize",
283
283
  "addFirstTask": "Add the first task",
284
- "createInitiativeTitle": "Create initiative"
284
+ "createInitiativeTitle": "Create initiative",
285
+ "accessDenied": {
286
+ "title": "Permission denied",
287
+ "hint": "You don't have access to the repository backing this service."
288
+ }
285
289
  },
286
290
  "decisionBadge": {
287
291
  "decisionNeeded": "Decision needed",
@@ -2557,7 +2561,8 @@
2557
2561
  "repoLabel": {
2558
2562
  "private": " (private)",
2559
2563
  "monorepo": " · monorepo",
2560
- "onBoard": " · already on board"
2564
+ "onBoard": " · already on board",
2565
+ "personal": " · personal (your token)"
2561
2566
  },
2562
2567
  "monorepoLabel": "This is a monorepo (hosts more than one service)",
2563
2568
  "monorepoDescription": "Add several services from one repo, each pinned to a subdirectory.",
@@ -251,7 +251,11 @@
251
251
  "dragTask": "Arrastrar tarea",
252
252
  "dragToResize": "Arrastra para cambiar el tamaño",
253
253
  "addFirstTask": "Añade la primera tarea",
254
- "createInitiativeTitle": "Crear iniciativa"
254
+ "createInitiativeTitle": "Crear iniciativa",
255
+ "accessDenied": {
256
+ "title": "Permiso denegado",
257
+ "hint": "No tienes acceso al repositorio que respalda este servicio."
258
+ }
255
259
  },
256
260
  "decisionBadge": {
257
261
  "decisionNeeded": "Decisión necesaria",
@@ -2481,7 +2485,8 @@
2481
2485
  "repoLabel": {
2482
2486
  "private": " (privado)",
2483
2487
  "monorepo": " · monorepo",
2484
- "onBoard": " · ya en el tablero"
2488
+ "onBoard": " · ya en el tablero",
2489
+ "personal": " · personal (tu token)"
2485
2490
  },
2486
2491
  "monorepoLabel": "Es un monorepo (aloja más de un servicio)",
2487
2492
  "monorepoDescription": "Añade varios servicios desde un repositorio, cada uno fijado a un subdirectorio.",
@@ -251,7 +251,11 @@
251
251
  "dragTask": "Faire glisser la tâche",
252
252
  "dragToResize": "Glisser pour redimensionner",
253
253
  "addFirstTask": "Ajouter la première tâche",
254
- "createInitiativeTitle": "Creer une initiative"
254
+ "createInitiativeTitle": "Creer une initiative",
255
+ "accessDenied": {
256
+ "title": "Autorisation refusée",
257
+ "hint": "Vous n'avez pas accès au dépôt sur lequel repose ce service."
258
+ }
255
259
  },
256
260
  "decisionBadge": {
257
261
  "decisionNeeded": "Décision requise",
@@ -2481,7 +2485,8 @@
2481
2485
  "repoLabel": {
2482
2486
  "private": " (privé)",
2483
2487
  "monorepo": " · monorepo",
2484
- "onBoard": " · déjà sur le tableau"
2488
+ "onBoard": " · déjà sur le tableau",
2489
+ "personal": " · personnel (votre jeton)"
2485
2490
  },
2486
2491
  "monorepoLabel": "Ceci est un monorepo (héberge plusieurs services)",
2487
2492
  "monorepoDescription": "Ajoutez plusieurs services depuis un même dépôt, chacun rattaché à un sous-répertoire.",
@@ -251,7 +251,11 @@
251
251
  "dragTask": "גרור משימה",
252
252
  "dragToResize": "גרור לשינוי גודל",
253
253
  "addFirstTask": "הוסף את המשימה הראשונה",
254
- "createInitiativeTitle": "יצירת יוזמה"
254
+ "createInitiativeTitle": "יצירת יוזמה",
255
+ "accessDenied": {
256
+ "title": "הגישה נדחתה",
257
+ "hint": "אין לך גישה למאגר שעליו מבוסס שירות זה."
258
+ }
255
259
  },
256
260
  "decisionBadge": {
257
261
  "decisionNeeded": "נדרשת החלטה",
@@ -2492,7 +2496,8 @@
2492
2496
  "repoLabel": {
2493
2497
  "private": " (פרטי)",
2494
2498
  "monorepo": " · מונורפו",
2495
- "onBoard": " · כבר על הלוח"
2499
+ "onBoard": " · כבר על הלוח",
2500
+ "personal": " · אישי (הטוקן שלך)"
2496
2501
  },
2497
2502
  "monorepoLabel": "זהו מונורפו (מארח יותר משירות אחד)",
2498
2503
  "monorepoDescription": "הוסף כמה שירותים ממאגר אחד, כל אחד מוצמד לתת-ספרייה.",
@@ -251,7 +251,11 @@
251
251
  "dragTask": "タスクをドラッグ",
252
252
  "dragToResize": "ドラッグしてサイズ変更",
253
253
  "addFirstTask": "最初のタスクを追加",
254
- "createInitiativeTitle": "イニシアチブを作成"
254
+ "createInitiativeTitle": "イニシアチブを作成",
255
+ "accessDenied": {
256
+ "title": "アクセスが拒否されました",
257
+ "hint": "このサービスの基盤となるリポジトリにアクセスする権限がありません。"
258
+ }
255
259
  },
256
260
  "decisionBadge": {
257
261
  "decisionNeeded": "判断が必要",
@@ -2494,7 +2498,8 @@
2494
2498
  "repoLabel": {
2495
2499
  "private": " (プライベート)",
2496
2500
  "monorepo": " · モノレポ",
2497
- "onBoard": " · すでにボード上"
2501
+ "onBoard": " · すでにボード上",
2502
+ "personal": " · 個人(自分のトークン)"
2498
2503
  },
2499
2504
  "monorepoLabel": "これはモノレポです (複数のサービスをホストしています)",
2500
2505
  "monorepoDescription": "1つのリポジトリから複数のサービスを追加し、それぞれをサブディレクトリに固定します。",
@@ -251,7 +251,11 @@
251
251
  "dragTask": "Przeciągnij zadanie",
252
252
  "dragToResize": "Przeciągnij, aby zmienić rozmiar",
253
253
  "addFirstTask": "Dodaj pierwsze zadanie",
254
- "createInitiativeTitle": "Utworz inicjatywe"
254
+ "createInitiativeTitle": "Utworz inicjatywe",
255
+ "accessDenied": {
256
+ "title": "Brak uprawnień",
257
+ "hint": "Nie masz dostępu do repozytorium powiązanego z tą usługą."
258
+ }
255
259
  },
256
260
  "decisionBadge": {
257
261
  "decisionNeeded": "Wymagana decyzja",
@@ -2481,7 +2485,8 @@
2481
2485
  "repoLabel": {
2482
2486
  "private": " (prywatne)",
2483
2487
  "monorepo": " · monorepo",
2484
- "onBoard": " · już na tablicy"
2488
+ "onBoard": " · już na tablicy",
2489
+ "personal": " · osobiste (Twój token)"
2485
2490
  },
2486
2491
  "monorepoLabel": "To jest monorepo (zawiera więcej niż jedną usługę)",
2487
2492
  "monorepoDescription": "Dodaj kilka usług z jednego repozytorium, każdą przypiętą do podkatalogu.",
@@ -251,7 +251,11 @@
251
251
  "dragTask": "Görevi sürükle",
252
252
  "dragToResize": "Yeniden boyutlandırmak için sürükle",
253
253
  "addFirstTask": "İlk görevi ekle",
254
- "createInitiativeTitle": "Girisim olustur"
254
+ "createInitiativeTitle": "Girisim olustur",
255
+ "accessDenied": {
256
+ "title": "İzin reddedildi",
257
+ "hint": "Bu hizmetin dayandığı depoya erişiminiz yok."
258
+ }
255
259
  },
256
260
  "decisionBadge": {
257
261
  "decisionNeeded": "Karar gerekiyor",
@@ -2494,7 +2498,8 @@
2494
2498
  "repoLabel": {
2495
2499
  "private": " (özel)",
2496
2500
  "monorepo": " · monorepo",
2497
- "onBoard": " · zaten panoda"
2501
+ "onBoard": " · zaten panoda",
2502
+ "personal": " · kişisel (kendi belirteciniz)"
2498
2503
  },
2499
2504
  "monorepoLabel": "Bu bir monorepo (birden fazla servis barındırır)",
2500
2505
  "monorepoDescription": "Tek depodan, her biri bir alt dizine sabitlenmiş birkaç servis ekleyin.",
@@ -251,7 +251,11 @@
251
251
  "dragTask": "Перетягнути завдання",
252
252
  "dragToResize": "Перетягніть, щоб змінити розмір",
253
253
  "addFirstTask": "Додати перше завдання",
254
- "createInitiativeTitle": "Створити ініціативу"
254
+ "createInitiativeTitle": "Створити ініціативу",
255
+ "accessDenied": {
256
+ "title": "Доступ заборонено",
257
+ "hint": "У вас немає доступу до репозиторію, що стоїть за цією службою."
258
+ }
255
259
  },
256
260
  "decisionBadge": {
257
261
  "decisionNeeded": "Потрібне рішення",
@@ -2481,7 +2485,8 @@
2481
2485
  "repoLabel": {
2482
2486
  "private": " (приватний)",
2483
2487
  "monorepo": " · монорепозиторій",
2484
- "onBoard": " · уже на дошці"
2488
+ "onBoard": " · уже на дошці",
2489
+ "personal": " · особистий (ваш токен)"
2485
2490
  },
2486
2491
  "monorepoLabel": "Це монорепозиторій (містить більше одного сервісу)",
2487
2492
  "monorepoDescription": "Додайте кілька сервісів з одного репозиторію, кожен прикріплений до підкаталогу.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.85.0",
3
+ "version": "0.87.0",
4
4
  "description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -34,7 +34,7 @@
34
34
  "valibot": "^1.4.2",
35
35
  "vue": "^3.5.39",
36
36
  "wretch": "^3.0.9",
37
- "@cat-factory/contracts": "0.92.0"
37
+ "@cat-factory/contracts": "0.94.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@toad-contracts/testing": "0.3.2",